SimpleValidator.java revision 12745:f068a4ffddd2
1/* 2 * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.security.validator; 27 28import java.io.IOException; 29import java.util.*; 30 31import java.security.*; 32import java.security.cert.*; 33 34import javax.security.auth.x500.X500Principal; 35 36import sun.security.x509.X509CertImpl; 37import sun.security.x509.NetscapeCertTypeExtension; 38import sun.security.util.DerValue; 39import sun.security.util.DerInputStream; 40import sun.security.util.ObjectIdentifier; 41 42import sun.security.provider.certpath.AlgorithmChecker; 43import sun.security.provider.certpath.UntrustedChecker; 44 45/** 46 * A simple validator implementation. It is based on code from the JSSE 47 * X509TrustManagerImpl. This implementation is designed for compatibility with 48 * deployed certificates and previous J2SE versions. It will never support 49 * more advanced features and will be deemphasized in favor of the PKIX 50 * validator going forward. 51 * <p> 52 * {@code SimpleValidator} objects are immutable once they have been created. 53 * Please DO NOT add methods that can change the state of an instance once 54 * it has been created. 55 * 56 * @author Andreas Sterbenz 57 */ 58public final class SimpleValidator extends Validator { 59 60 // Constants for the OIDs we need 61 62 static final String OID_BASIC_CONSTRAINTS = "2.5.29.19"; 63 64 static final String OID_NETSCAPE_CERT_TYPE = "2.16.840.1.113730.1.1"; 65 66 static final String OID_KEY_USAGE = "2.5.29.15"; 67 68 static final String OID_EXTENDED_KEY_USAGE = "2.5.29.37"; 69 70 static final String OID_EKU_ANY_USAGE = "2.5.29.37.0"; 71 72 static final ObjectIdentifier OBJID_NETSCAPE_CERT_TYPE = 73 NetscapeCertTypeExtension.NetscapeCertType_Id; 74 75 private static final String NSCT_SSL_CA = 76 NetscapeCertTypeExtension.SSL_CA; 77 78 private static final String NSCT_CODE_SIGNING_CA = 79 NetscapeCertTypeExtension.OBJECT_SIGNING_CA; 80 81 /** 82 * The trusted certificates as: 83 * Map (X500Principal)subject of trusted cert -> List of X509Certificate 84 * The list is used because there may be multiple certificates 85 * with an identical subject DN. 86 */ 87 private final Map<X500Principal, List<X509Certificate>> 88 trustedX500Principals; 89 90 /** 91 * Set of the trusted certificates. Present only for 92 * getTrustedCertificates(). 93 */ 94 private final Collection<X509Certificate> trustedCerts; 95 96 SimpleValidator(String variant, Collection<X509Certificate> trustedCerts) { 97 super(TYPE_SIMPLE, variant); 98 this.trustedCerts = trustedCerts; 99 trustedX500Principals = 100 new HashMap<X500Principal, List<X509Certificate>>(); 101 for (X509Certificate cert : trustedCerts) { 102 X500Principal principal = cert.getSubjectX500Principal(); 103 List<X509Certificate> list = trustedX500Principals.get(principal); 104 if (list == null) { 105 // this actually should be a set, but duplicate entries 106 // are not a problem and we can avoid the Set overhead 107 list = new ArrayList<X509Certificate>(2); 108 trustedX500Principals.put(principal, list); 109 } 110 list.add(cert); 111 } 112 } 113 114 public Collection<X509Certificate> getTrustedCertificates() { 115 return trustedCerts; 116 } 117 118 /** 119 * Perform simple validation of chain. The arguments otherCerts and 120 * parameter are ignored. 121 */ 122 @Override 123 X509Certificate[] engineValidate(X509Certificate[] chain, 124 Collection<X509Certificate> otherCerts, 125 List<byte[]> responseList, 126 AlgorithmConstraints constraints, 127 Object parameter) throws CertificateException { 128 if ((chain == null) || (chain.length == 0)) { 129 throw new CertificateException 130 ("null or zero-length certificate chain"); 131 } 132 133 // make sure chain includes a trusted cert 134 chain = buildTrustedChain(chain); 135 136 @SuppressWarnings("deprecation") 137 Date date = validationDate; 138 if (date == null) { 139 date = new Date(); 140 } 141 142 // create distrusted certificates checker 143 UntrustedChecker untrustedChecker = new UntrustedChecker(); 144 145 // check if anchor is untrusted 146 X509Certificate anchorCert = chain[chain.length - 1]; 147 try { 148 untrustedChecker.check(anchorCert); 149 } catch (CertPathValidatorException cpve) { 150 throw new ValidatorException( 151 "Untrusted certificate: "+ anchorCert.getSubjectX500Principal(), 152 ValidatorException.T_UNTRUSTED_CERT, anchorCert, cpve); 153 } 154 155 // create default algorithm constraints checker 156 TrustAnchor anchor = new TrustAnchor(anchorCert, null); 157 AlgorithmChecker defaultAlgChecker = new AlgorithmChecker(anchor); 158 159 // create application level algorithm constraints checker 160 AlgorithmChecker appAlgChecker = null; 161 if (constraints != null) { 162 appAlgChecker = new AlgorithmChecker(anchor, constraints); 163 } 164 165 // verify top down, starting at the certificate issued by 166 // the trust anchor 167 int maxPathLength = chain.length - 1; 168 for (int i = chain.length - 2; i >= 0; i--) { 169 X509Certificate issuerCert = chain[i + 1]; 170 X509Certificate cert = chain[i]; 171 172 // check untrusted certificate 173 try { 174 // Untrusted checker does not care about the unresolved 175 // critical extensions. 176 untrustedChecker.check(cert, Collections.<String>emptySet()); 177 } catch (CertPathValidatorException cpve) { 178 throw new ValidatorException( 179 "Untrusted certificate: " + cert.getSubjectX500Principal(), 180 ValidatorException.T_UNTRUSTED_CERT, cert, cpve); 181 } 182 183 // check certificate algorithm 184 try { 185 // Algorithm checker does not care about the unresolved 186 // critical extensions. 187 defaultAlgChecker.check(cert, Collections.<String>emptySet()); 188 if (appAlgChecker != null) { 189 appAlgChecker.check(cert, Collections.<String>emptySet()); 190 } 191 } catch (CertPathValidatorException cpve) { 192 throw new ValidatorException 193 (ValidatorException.T_ALGORITHM_DISABLED, cert, cpve); 194 } 195 196 // no validity check for code signing certs 197 if ((variant.equals(VAR_CODE_SIGNING) == false) 198 && (variant.equals(VAR_JCE_SIGNING) == false)) { 199 cert.checkValidity(date); 200 } 201 202 // check name chaining 203 if (cert.getIssuerX500Principal().equals( 204 issuerCert.getSubjectX500Principal()) == false) { 205 throw new ValidatorException 206 (ValidatorException.T_NAME_CHAINING, cert); 207 } 208 209 // check signature 210 try { 211 cert.verify(issuerCert.getPublicKey()); 212 } catch (GeneralSecurityException e) { 213 throw new ValidatorException 214 (ValidatorException.T_SIGNATURE_ERROR, cert, e); 215 } 216 217 // check extensions for CA certs 218 if (i != 0) { 219 maxPathLength = checkExtensions(cert, maxPathLength); 220 } 221 } 222 223 return chain; 224 } 225 226 private int checkExtensions(X509Certificate cert, int maxPathLen) 227 throws CertificateException { 228 Set<String> critSet = cert.getCriticalExtensionOIDs(); 229 if (critSet == null) { 230 critSet = Collections.<String>emptySet(); 231 } 232 233 // Check the basic constraints extension 234 int pathLenConstraint = 235 checkBasicConstraints(cert, critSet, maxPathLen); 236 237 // Check the key usage and extended key usage extensions 238 checkKeyUsage(cert, critSet); 239 240 // check Netscape certificate type extension 241 checkNetscapeCertType(cert, critSet); 242 243 if (!critSet.isEmpty()) { 244 throw new ValidatorException 245 ("Certificate contains unknown critical extensions: " + critSet, 246 ValidatorException.T_CA_EXTENSIONS, cert); 247 } 248 249 return pathLenConstraint; 250 } 251 252 private void checkNetscapeCertType(X509Certificate cert, 253 Set<String> critSet) throws CertificateException { 254 if (variant.equals(VAR_GENERIC)) { 255 // nothing 256 } else if (variant.equals(VAR_TLS_CLIENT) 257 || variant.equals(VAR_TLS_SERVER)) { 258 if (getNetscapeCertTypeBit(cert, NSCT_SSL_CA) == false) { 259 throw new ValidatorException 260 ("Invalid Netscape CertType extension for SSL CA " 261 + "certificate", 262 ValidatorException.T_CA_EXTENSIONS, cert); 263 } 264 critSet.remove(OID_NETSCAPE_CERT_TYPE); 265 } else if (variant.equals(VAR_CODE_SIGNING) 266 || variant.equals(VAR_JCE_SIGNING)) { 267 if (getNetscapeCertTypeBit(cert, NSCT_CODE_SIGNING_CA) == false) { 268 throw new ValidatorException 269 ("Invalid Netscape CertType extension for code " 270 + "signing CA certificate", 271 ValidatorException.T_CA_EXTENSIONS, cert); 272 } 273 critSet.remove(OID_NETSCAPE_CERT_TYPE); 274 } else { 275 throw new CertificateException("Unknown variant " + variant); 276 } 277 } 278 279 /** 280 * Get the value of the specified bit in the Netscape certificate type 281 * extension. If the extension is not present at all, we return true. 282 */ 283 static boolean getNetscapeCertTypeBit(X509Certificate cert, String type) { 284 try { 285 NetscapeCertTypeExtension ext; 286 if (cert instanceof X509CertImpl) { 287 X509CertImpl certImpl = (X509CertImpl)cert; 288 ObjectIdentifier oid = OBJID_NETSCAPE_CERT_TYPE; 289 ext = (NetscapeCertTypeExtension)certImpl.getExtension(oid); 290 if (ext == null) { 291 return true; 292 } 293 } else { 294 byte[] extVal = cert.getExtensionValue(OID_NETSCAPE_CERT_TYPE); 295 if (extVal == null) { 296 return true; 297 } 298 DerInputStream in = new DerInputStream(extVal); 299 byte[] encoded = in.getOctetString(); 300 encoded = new DerValue(encoded).getUnalignedBitString() 301 .toByteArray(); 302 ext = new NetscapeCertTypeExtension(encoded); 303 } 304 Boolean val = ext.get(type); 305 return val.booleanValue(); 306 } catch (IOException e) { 307 return false; 308 } 309 } 310 311 private int checkBasicConstraints(X509Certificate cert, 312 Set<String> critSet, int maxPathLen) throws CertificateException { 313 314 critSet.remove(OID_BASIC_CONSTRAINTS); 315 int constraints = cert.getBasicConstraints(); 316 // reject, if extension missing or not a CA (constraints == -1) 317 if (constraints < 0) { 318 throw new ValidatorException("End user tried to act as a CA", 319 ValidatorException.T_CA_EXTENSIONS, cert); 320 } 321 322 // if the certificate is self-issued, ignore the pathLenConstraint 323 // checking. 324 if (!X509CertImpl.isSelfIssued(cert)) { 325 if (maxPathLen <= 0) { 326 throw new ValidatorException("Violated path length constraints", 327 ValidatorException.T_CA_EXTENSIONS, cert); 328 } 329 330 maxPathLen--; 331 } 332 333 if (maxPathLen > constraints) { 334 maxPathLen = constraints; 335 } 336 337 return maxPathLen; 338 } 339 340 /* 341 * Verify the key usage and extended key usage for intermediate 342 * certificates. 343 */ 344 private void checkKeyUsage(X509Certificate cert, Set<String> critSet) 345 throws CertificateException { 346 347 critSet.remove(OID_KEY_USAGE); 348 // EKU irrelevant in CA certificates 349 critSet.remove(OID_EXTENDED_KEY_USAGE); 350 351 // check key usage extension 352 boolean[] keyUsageInfo = cert.getKeyUsage(); 353 if (keyUsageInfo != null) { 354 // keyUsageInfo[5] is for keyCertSign. 355 if ((keyUsageInfo.length < 6) || (keyUsageInfo[5] == false)) { 356 throw new ValidatorException 357 ("Wrong key usage: expected keyCertSign", 358 ValidatorException.T_CA_EXTENSIONS, cert); 359 } 360 } 361 } 362 363 /** 364 * Build a trusted certificate chain. This method always returns a chain 365 * with a trust anchor as the final cert in the chain. If no trust anchor 366 * could be found, a CertificateException is thrown. 367 */ 368 private X509Certificate[] buildTrustedChain(X509Certificate[] chain) 369 throws CertificateException { 370 List<X509Certificate> c = new ArrayList<X509Certificate>(chain.length); 371 // scan chain starting at EE cert 372 // if a trusted certificate is found, append it and return 373 for (int i = 0; i < chain.length; i++) { 374 X509Certificate cert = chain[i]; 375 X509Certificate trustedCert = getTrustedCertificate(cert); 376 if (trustedCert != null) { 377 c.add(trustedCert); 378 return c.toArray(CHAIN0); 379 } 380 c.add(cert); 381 } 382 383 // check if we can append a trusted cert 384 X509Certificate cert = chain[chain.length - 1]; 385 X500Principal subject = cert.getSubjectX500Principal(); 386 X500Principal issuer = cert.getIssuerX500Principal(); 387 List<X509Certificate> list = trustedX500Principals.get(issuer); 388 if (list != null) { 389 X509Certificate trustedCert = list.iterator().next(); 390 c.add(trustedCert); 391 return c.toArray(CHAIN0); 392 } 393 394 // no trusted cert found, error 395 throw new ValidatorException(ValidatorException.T_NO_TRUST_ANCHOR); 396 } 397 398 /** 399 * Return a trusted certificate that matches the input certificate, 400 * or null if no such certificate can be found. This method also handles 401 * cases where a CA re-issues a trust anchor with the same public key and 402 * same subject and issuer names but a new validity period, etc. 403 */ 404 private X509Certificate getTrustedCertificate(X509Certificate cert) { 405 Principal certSubjectName = cert.getSubjectX500Principal(); 406 List<X509Certificate> list = trustedX500Principals.get(certSubjectName); 407 if (list == null) { 408 return null; 409 } 410 411 Principal certIssuerName = cert.getIssuerX500Principal(); 412 PublicKey certPublicKey = cert.getPublicKey(); 413 414 for (X509Certificate mycert : list) { 415 if (mycert.equals(cert)) { 416 return cert; 417 } 418 if (!mycert.getIssuerX500Principal().equals(certIssuerName)) { 419 continue; 420 } 421 if (!mycert.getPublicKey().equals(certPublicKey)) { 422 continue; 423 } 424 425 // All tests pass, this must be the one to use... 426 return mycert; 427 } 428 return null; 429 } 430 431} 432