package org.eclipse.jetty.util.security; //======================================================================== //Copyright (c) Webtide LLC //------------------------------------------------------------------------ //All rights reserved. This program and the accompanying materials //are made available under the terms of the Eclipse Public License v1.0 //and Apache License v2.0 which accompanies this distribution. // //The Eclipse Public License is available at //http://www.eclipse.org/legal/epl-v10.html // //The Apache License v2.0 is available at //http://www.apache.org/licenses/LICENSE-2.0.txt // //You may elect to redistribute this code under either of these licenses. //======================================================================== import java.security.GeneralSecurityException; import java.security.InvalidParameterException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Security; import java.security.cert.CRL; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderResult; import java.security.cert.CertPathValidator; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * Convenience class to handle validation of certificates, aliases and keystores * * Allows specifying Certificate Revocation List (CRL), as well as enabling * CRL Distribution Points Protocol (CRLDP) certificate extension support, * and also enabling On-Line Certificate Status Protocol (OCSP) support. * * IMPORTANT: at least one of the above mechanisms *MUST* be configured and * operational, otherwise certificate validation *WILL FAIL* unconditionally. */ public class CertificateValidator { private static final Logger LOG = Log.getLogger(CertificateValidator.class); private static AtomicLong __aliasCount = new AtomicLong(); private KeyStore _trustStore; private Collection<? extends CRL> _crls; /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */ private int _maxCertPathLength = -1; /** CRL Distribution Points (CRLDP) support */ private boolean _enableCRLDP = false; /** On-Line Certificate Status Protocol (OCSP) support */ private boolean _enableOCSP = false; /** Location of OCSP Responder */ private String _ocspResponderURL; /** * creates an instance of the certificate validator * * @param trustStore * @param crls */ public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls) { if (trustStore == null) { throw new InvalidParameterException("TrustStore must be specified for CertificateValidator."); } _trustStore = trustStore; _crls = crls; } /** * validates all aliases inside of a given keystore * * @param keyStore * @throws CertificateException */ public void validate( KeyStore keyStore ) throws CertificateException { try { Enumeration<String> aliases = keyStore.aliases(); for ( ; aliases.hasMoreElements(); ) { String alias = aliases.nextElement(); validate(keyStore,alias); } } catch ( KeyStoreException kse ) { throw new CertificateException("Unable to retrieve aliases from keystore", kse); } } /** * validates a specific alias inside of the keystore being passed in * * @param keyStore * @param keyAlias * @return the keyAlias if valid * @throws CertificateException */ public String validate(KeyStore keyStore, String keyAlias) throws CertificateException { String result = null; if (keyAlias != null) { try { validate(keyStore, keyStore.getCertificate(keyAlias)); } catch (KeyStoreException kse) { LOG.debug(kse); throw new CertificateException("Unable to validate certificate" + " for alias [" + keyAlias + "]: " + kse.getMessage(), kse); } result = keyAlias; } return result; } /** * validates a specific certificate inside of the keystore being passed in * * @param keyStore * @param cert * @throws CertificateException */ public void validate(KeyStore keyStore, Certificate cert) throws CertificateException { Certificate[] certChain = null; if (cert != null && cert instanceof X509Certificate) { ((X509Certificate)cert).checkValidity(); String certAlias = null; try { if (keyStore == null) { throw new InvalidParameterException("Keystore cannot be null"); } certAlias = keyStore.getCertificateAlias((X509Certificate)cert); if (certAlias == null) { certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet()); keyStore.setCertificateEntry(certAlias, cert); } certChain = keyStore.getCertificateChain(certAlias); if (certChain == null || certChain.length == 0) { throw new IllegalStateException("Unable to retrieve certificate chain"); } } catch (KeyStoreException kse) { LOG.debug(kse); throw new CertificateException("Unable to validate certificate" + (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse); } validate(certChain); } } public void validate(Certificate[] certChain) throws CertificateException { try { ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(); for (Certificate item : certChain) { if (item == null) continue; if (!(item instanceof X509Certificate)) { throw new IllegalStateException("Invalid certificate type in chain"); } certList.add((X509Certificate)item); } if (certList.isEmpty()) { throw new IllegalStateException("Invalid certificate chain"); } X509CertSelector certSelect = new X509CertSelector(); certSelect.setCertificate(certList.get(0)); // Configure certification path builder parameters PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect); pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList))); // Set maximum certification path length pbParams.setMaxPathLength(_maxCertPathLength); // Enable revocation checking pbParams.setRevocationEnabled(true); // Set static Certificate Revocation List if (_crls != null && !_crls.isEmpty()) { pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls))); } // Enable On-Line Certificate Status Protocol (OCSP) support if (_enableOCSP) { Security.setProperty("ocsp.enable","true"); } // Enable Certificate Revocation List Distribution Points (CRLDP) support if (_enableCRLDP) { System.setProperty("com.sun.security.enableCRLDP","true"); } // Build certification path CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams); // Validate certification path CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams); } catch (GeneralSecurityException gse) { LOG.debug(gse); throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse); } } public KeyStore getTrustStore() { return _trustStore; } public Collection<? extends CRL> getCrls() { return _crls; } /** * @return Maximum number of intermediate certificates in * the certification path (-1 for unlimited) */ public int getMaxCertPathLength() { return _maxCertPathLength; } /* ------------------------------------------------------------ */ /** * @param maxCertPathLength * maximum number of intermediate certificates in * the certification path (-1 for unlimited) */ public void setMaxCertPathLength(int maxCertPathLength) { _maxCertPathLength = maxCertPathLength; } /* ------------------------------------------------------------ */ /** * @return true if CRL Distribution Points support is enabled */ public boolean isEnableCRLDP() { return _enableCRLDP; } /* ------------------------------------------------------------ */ /** Enables CRL Distribution Points Support * @param enableCRLDP true - turn on, false - turns off */ public void setEnableCRLDP(boolean enableCRLDP) { _enableCRLDP = enableCRLDP; } /* ------------------------------------------------------------ */ /** * @return true if On-Line Certificate Status Protocol support is enabled */ public boolean isEnableOCSP() { return _enableOCSP; } /* ------------------------------------------------------------ */ /** Enables On-Line Certificate Status Protocol support * @param enableOCSP true - turn on, false - turn off */ public void setEnableOCSP(boolean enableOCSP) { _enableOCSP = enableOCSP; } /* ------------------------------------------------------------ */ /** * @return Location of the OCSP Responder */ public String getOcspResponderURL() { return _ocspResponderURL; } /* ------------------------------------------------------------ */ /** Set the location of the OCSP Responder. * @param ocspResponderURL location of the OCSP Responder */ public void setOcspResponderURL(String ocspResponderURL) { _ocspResponderURL = ocspResponderURL; } }