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