View Javadoc

1   package org.eclipse.jetty.util.security;
2   
3   //========================================================================
4   //Copyright (c) Webtide LLC
5   //------------------------------------------------------------------------
6   //All rights reserved. This program and the accompanying materials
7   //are made available under the terms of the Eclipse Public License v1.0
8   //and Apache License v2.0 which accompanies this distribution.
9   //
10  //The Eclipse Public License is available at
11  //http://www.eclipse.org/legal/epl-v10.html
12  //
13  //The Apache License v2.0 is available at
14  //http://www.apache.org/licenses/LICENSE-2.0.txt
15  //
16  //You may elect to redistribute this code under either of these licenses.
17  //========================================================================
18  
19  import java.security.GeneralSecurityException;
20  import java.security.InvalidParameterException;
21  import java.security.KeyStore;
22  import java.security.KeyStoreException;
23  import java.security.Security;
24  import java.security.cert.CRL;
25  import java.security.cert.CertPathBuilder;
26  import java.security.cert.CertPathBuilderResult;
27  import java.security.cert.CertPathValidator;
28  import java.security.cert.CertStore;
29  import java.security.cert.Certificate;
30  import java.security.cert.CertificateException;
31  import java.security.cert.CollectionCertStoreParameters;
32  import java.security.cert.PKIXBuilderParameters;
33  import java.security.cert.X509CertSelector;
34  import java.security.cert.X509Certificate;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Enumeration;
38  import java.util.concurrent.atomic.AtomicLong;
39  
40  import org.eclipse.jetty.util.log.Log;
41  
42  /**
43   * Convenience class to handle validation of certificates, aliases and keystores
44   *
45   * Allows specifying Certificate Revocation List (CRL), as well as enabling
46   * CRL Distribution Points Protocol (CRLDP) certificate extension support,
47   * and also enabling On-Line Certificate Status Protocol (OCSP) support.
48   * 
49   * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
50   * operational, otherwise certificate validation *WILL FAIL* unconditionally.
51   */
52  public class CertificateValidator
53  {
54      private static AtomicLong __aliasCount = new AtomicLong();
55      
56      private KeyStore _trustStore;
57      private Collection<? extends CRL> _crls;
58  
59      /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
60      private int _maxCertPathLength = -1;
61      /** CRL Distribution Points (CRLDP) support */
62      private boolean _enableCRLDP = false;
63      /** On-Line Certificate Status Protocol (OCSP) support */
64      private boolean _enableOCSP = false;
65      /** Location of OCSP Responder */
66      private String _ocspResponderURL;
67      
68      /**
69       * creates an instance of the certificate validator 
70       *
71       * @param trustStore 
72       * @param crls
73       */
74      public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
75      {
76          if (trustStore == null)
77          {
78              throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
79          }
80          
81          _trustStore = trustStore;
82          _crls = crls;
83      }
84      
85      /**
86       * validates all aliases inside of a given keystore
87       * 
88       * @param keyStore
89       * @throws CertificateException
90       */
91      public void validate( KeyStore keyStore ) throws CertificateException
92      {
93          try
94          {
95              Enumeration<String> aliases = keyStore.aliases();
96              
97              for ( ; aliases.hasMoreElements(); )
98              {
99                  String alias = aliases.nextElement();
100                 
101                 validate(keyStore,alias);
102             }
103             
104         }
105         catch ( KeyStoreException kse )
106         {
107             throw new CertificateException("Unable to retrieve aliases from keystore", kse);
108         }
109     }
110     
111 
112     /**
113      * validates a specific alias inside of the keystore being passed in
114      * 
115      * @param keyStore
116      * @param keyAlias
117      * @return the keyAlias if valid
118      * @throws CertificateException
119      */
120     public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
121     {
122         String result = null;
123 
124         if (keyAlias != null)
125         {
126             try
127             {
128                 validate(keyStore, keyStore.getCertificate(keyAlias));
129             }
130             catch (KeyStoreException kse)
131             {
132                 Log.debug(kse);
133                 throw new CertificateException("Unable to validate certificate" +
134                         " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
135             }
136             result = keyAlias;            
137         }
138         
139         return result;
140     }
141     
142     /**
143      * validates a specific certificate inside of the keystore being passed in
144      * 
145      * @param keyStore
146      * @param cert
147      * @throws CertificateException
148      */
149     public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
150     {
151         Certificate[] certChain = null;
152         
153         if (cert != null && cert instanceof X509Certificate)
154         {
155             ((X509Certificate)cert).checkValidity();
156             
157             String certAlias = null;
158             try
159             {
160                 if (keyStore == null)
161                 {
162                     throw new InvalidParameterException("Keystore cannot be null");
163                 }
164 
165                 certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
166                 if (certAlias == null)
167                 {
168                     certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
169                     keyStore.setCertificateEntry(certAlias, cert);
170                 }
171                 
172                 certChain = keyStore.getCertificateChain(certAlias);
173                 if (certChain == null || certChain.length == 0)
174                 {
175                     throw new IllegalStateException("Unable to retrieve certificate chain");
176                 }
177             }
178             catch (KeyStoreException kse)
179             {
180                 Log.debug(kse);
181                 throw new CertificateException("Unable to validate certificate" +
182                         (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
183             }
184             
185             validate(certChain);
186         } 
187     }
188     
189     public void validate(Certificate[] certChain) throws CertificateException
190     {
191         try
192         {
193             ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
194             for (Certificate item : certChain)
195             {
196                 if (item == null)
197                     continue;
198                 
199                 if (!(item instanceof X509Certificate))
200                 {
201                     throw new IllegalStateException("Invalid certificate type in chain");
202                 }
203                 
204                 certList.add((X509Certificate)item);
205             }
206     
207             if (certList.isEmpty())
208             {
209                 throw new IllegalStateException("Invalid certificate chain");
210                 
211             }
212     
213             X509CertSelector certSelect = new X509CertSelector();
214             certSelect.setCertificate(certList.get(0));
215             
216             // Configure certification path builder parameters
217             PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
218             pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
219     
220             // Set maximum certification path length
221             pbParams.setMaxPathLength(_maxCertPathLength);
222     
223             // Enable revocation checking
224             pbParams.setRevocationEnabled(true);
225     
226             // Set static Certificate Revocation List
227             if (_crls != null && !_crls.isEmpty())
228             {
229                 pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
230             }
231     
232             // Enable On-Line Certificate Status Protocol (OCSP) support
233             Security.setProperty("ocsp.enable","true");
234     
235             // Enable Certificate Revocation List Distribution Points (CRLDP) support
236             System.setProperty("com.sun.security.enableCRLDP","true");
237     
238             // Build certification path
239             CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);               
240             
241             // Validate certification path
242             CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
243         }
244         catch (GeneralSecurityException gse)
245         {
246             Log.debug(gse);
247             throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
248         }
249     }
250 
251     public KeyStore getTrustStore()
252     {
253         return _trustStore;
254     }
255 
256     public Collection<? extends CRL> getCrls()
257     {
258         return _crls;
259     }
260 
261     /**
262      * @return Maximum number of intermediate certificates in
263      * the certification path (-1 for unlimited)
264      */
265     public int getMaxCertPathLength()
266     {
267         return _maxCertPathLength;
268     }
269 
270     /* ------------------------------------------------------------ */
271     /**
272      * @param maxCertPathLength
273      *            maximum number of intermediate certificates in
274      *            the certification path (-1 for unlimited)
275      */
276     public void setMaxCertPathLength(int maxCertPathLength)
277     {
278         _maxCertPathLength = maxCertPathLength;
279     }
280     
281     /* ------------------------------------------------------------ */
282     /** 
283      * @return true if CRL Distribution Points support is enabled
284      */
285     public boolean isEnableCRLDP()
286     {
287         return _enableCRLDP;
288     }
289 
290     /* ------------------------------------------------------------ */
291     /** Enables CRL Distribution Points Support
292      * @param enableCRLDP true - turn on, false - turns off
293      */
294     public void setEnableCRLDP(boolean enableCRLDP)
295     {
296         _enableCRLDP = enableCRLDP;
297     }
298 
299     /* ------------------------------------------------------------ */
300     /** 
301      * @return true if On-Line Certificate Status Protocol support is enabled
302      */
303     public boolean isEnableOCSP()
304     {
305         return _enableOCSP;
306     }
307 
308     /* ------------------------------------------------------------ */
309     /** Enables On-Line Certificate Status Protocol support
310      * @param enableOCSP true - turn on, false - turn off
311      */
312     public void setEnableOCSP(boolean enableOCSP)
313     {
314         _enableOCSP = enableOCSP;
315     }
316 
317     /* ------------------------------------------------------------ */
318     /** 
319      * @return Location of the OCSP Responder
320      */
321     public String getOcspResponderURL()
322     {
323         return _ocspResponderURL;
324     }
325 
326     /* ------------------------------------------------------------ */
327     /** Set the location of the OCSP Responder.
328      * @param ocspResponderURL location of the OCSP Responder
329      */
330     public void setOcspResponderURL(String ocspResponderURL)
331     {
332         _ocspResponderURL = ocspResponderURL;
333     }
334 }