View Javadoc

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