Home | History | Annotate | Download | only in security
      1 //
      2 //  ========================================================================
      3 //  Copyright (c) 1995-2014 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
     76      * @param crls
     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
     93      * @throws CertificateException
     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
    120      * @param keyAlias
    121      * @return the keyAlias if valid
    122      * @throws CertificateException
    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
    150      * @param cert
    151      * @throws CertificateException
    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 }
    344