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