1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.conscrypt; 19 20import java.net.Socket; 21import java.security.InvalidAlgorithmParameterException; 22import java.security.KeyStore; 23import java.security.KeyStoreException; 24import java.security.cert.CertPath; 25import java.security.cert.CertPathValidator; 26import java.security.cert.CertPathValidatorException; 27import java.security.cert.Certificate; 28import java.security.cert.CertificateException; 29import java.security.cert.CertificateParsingException; 30import java.security.cert.CertificateFactory; 31import java.security.cert.PKIXCertPathChecker; 32import java.security.cert.PKIXParameters; 33import java.security.cert.TrustAnchor; 34import java.security.cert.X509Certificate; 35import java.util.Arrays; 36import java.util.ArrayList; 37import java.util.Collection; 38import java.util.Collections; 39import java.util.Enumeration; 40import java.util.HashSet; 41import java.util.List; 42import java.util.Set; 43import javax.net.ssl.HostnameVerifier; 44import javax.net.ssl.HttpsURLConnection; 45import javax.net.ssl.SSLEngine; 46import javax.net.ssl.SSLParameters; 47import javax.net.ssl.SSLSession; 48import javax.net.ssl.SSLSocket; 49import javax.net.ssl.X509TrustManager; 50 51/** 52 * 53 * TrustManager implementation. The implementation is based on CertPathValidator 54 * PKIX and CertificateFactory X509 implementations. This implementations should 55 * be provided by some certification provider. 56 * 57 * @see javax.net.ssl.X509TrustManager 58 */ 59public final class TrustManagerImpl implements X509TrustManager { 60 61 /** 62 * The AndroidCAStore if non-null, null otherwise. 63 */ 64 private final KeyStore rootKeyStore; 65 66 /** 67 * The CertPinManager, which validates the chain against a host-to-pin mapping 68 */ 69 private CertPinManager pinManager; 70 71 /** 72 * The backing store for the AndroidCAStore if non-null. This will 73 * be null when the rootKeyStore is null, implying we are not 74 * using the AndroidCAStore. 75 */ 76 private final TrustedCertificateStore trustedCertificateStore; 77 78 private final CertPathValidator validator; 79 80 /** 81 * An index of TrustAnchor instances that we've seen. Unlike the 82 * TrustedCertificateStore, this may contain intermediate CAs. 83 */ 84 private final TrustedCertificateIndex trustedCertificateIndex; 85 86 /** 87 * This is lazily initialized in the AndroidCAStore case since it 88 * forces us to bring all the CAs into memory. In the 89 * non-AndroidCAStore, we initialize this as part of the 90 * constructor. 91 */ 92 private final X509Certificate[] acceptedIssuers; 93 94 private final Exception err; 95 private final CertificateFactory factory; 96 97 /** 98 * Creates X509TrustManager based on a keystore 99 * 100 * @param keyStore 101 */ 102 public TrustManagerImpl(KeyStore keyStore) { 103 this(keyStore, null); 104 } 105 106 /** 107 * For testing only 108 */ 109 public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) { 110 this(keyStore, manager, null); 111 } 112 113 /** 114 * For testing only. 115 */ 116 public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, 117 TrustedCertificateStore certStore) { 118 CertPathValidator validatorLocal = null; 119 CertificateFactory factoryLocal = null; 120 KeyStore rootKeyStoreLocal = null; 121 TrustedCertificateStore trustedCertificateStoreLocal = null; 122 TrustedCertificateIndex trustedCertificateIndexLocal = null; 123 X509Certificate[] acceptedIssuersLocal = null; 124 Exception errLocal = null; 125 try { 126 validatorLocal = CertPathValidator.getInstance("PKIX"); 127 factoryLocal = CertificateFactory.getInstance("X509"); 128 129 // if we have an AndroidCAStore, we will lazily load CAs 130 if ("AndroidCAStore".equals(keyStore.getType())) { 131 rootKeyStoreLocal = keyStore; 132 trustedCertificateStoreLocal = 133 (certStore != null) ? certStore : new TrustedCertificateStore(); 134 acceptedIssuersLocal = null; 135 trustedCertificateIndexLocal = new TrustedCertificateIndex(); 136 } else { 137 rootKeyStoreLocal = null; 138 trustedCertificateStoreLocal = certStore; 139 acceptedIssuersLocal = acceptedIssuers(keyStore); 140 trustedCertificateIndexLocal 141 = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal)); 142 } 143 144 } catch (Exception e) { 145 errLocal = e; 146 } 147 148 if (manager != null) { 149 this.pinManager = manager; 150 } else { 151 try { 152 pinManager = new CertPinManager(trustedCertificateStoreLocal); 153 } catch (PinManagerException e) { 154 throw new SecurityException("Could not initialize CertPinManager", e); 155 } 156 } 157 158 this.rootKeyStore = rootKeyStoreLocal; 159 this.trustedCertificateStore = trustedCertificateStoreLocal; 160 this.validator = validatorLocal; 161 this.factory = factoryLocal; 162 this.trustedCertificateIndex = trustedCertificateIndexLocal; 163 this.acceptedIssuers = acceptedIssuersLocal; 164 this.err = errLocal; 165 } 166 167 private static X509Certificate[] acceptedIssuers(KeyStore ks) { 168 try { 169 // Note that unlike the PKIXParameters code to create a Set of 170 // TrustAnchors from a KeyStore, this version takes from both 171 // TrustedCertificateEntry and PrivateKeyEntry, not just 172 // TrustedCertificateEntry, which is why TrustManagerImpl 173 // cannot just use an PKIXParameters(KeyStore) 174 // constructor. 175 176 // TODO remove duplicates if same cert is found in both a 177 // PrivateKeyEntry and TrustedCertificateEntry 178 List<X509Certificate> trusted = new ArrayList<X509Certificate>(); 179 for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) { 180 final String alias = en.nextElement(); 181 final X509Certificate cert = (X509Certificate) ks.getCertificate(alias); 182 if (cert != null) { 183 trusted.add(cert); 184 } 185 } 186 return trusted.toArray(new X509Certificate[trusted.size()]); 187 } catch (KeyStoreException e) { 188 return new X509Certificate[0]; 189 } 190 } 191 192 private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) { 193 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length); 194 for (X509Certificate cert : certs) { 195 trustAnchors.add(new TrustAnchor(cert, null)); 196 } 197 return trustAnchors; 198 } 199 200 @Override 201 public void checkClientTrusted(X509Certificate[] chain, String authType) 202 throws CertificateException { 203 checkTrusted(chain, authType, null, true); 204 } 205 206 @Override 207 public void checkServerTrusted(X509Certificate[] chain, String authType) 208 throws CertificateException { 209 checkTrusted(chain, authType, null, false); 210 } 211 212 /** 213 * Validates whether a server is trusted. If hostname is given and non-null it also checks if 214 * chain is pinned appropriately for that host. If null, it does not check for pinned certs. 215 * The return value is a list of the certificates used for making the trust decision. 216 */ 217 public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, 218 String host) throws CertificateException { 219 return checkTrusted(chain, authType, host, false); 220 } 221 222 public boolean isUserAddedCertificate(X509Certificate cert) { 223 if (trustedCertificateStore == null) { 224 return false; 225 } else { 226 return trustedCertificateStore.isUserAddedCertificate(cert); 227 } 228 } 229 230 /** 231 * Validates whether a server is trusted. If session is given and non-null 232 * it also checks if chain is pinned appropriately for that peer host. If 233 * null, it does not check for pinned certs. The return value is a list of 234 * the certificates used for making the trust decision. 235 */ 236 public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, 237 SSLSession session) throws CertificateException { 238 return checkTrusted(chain, authType, session.getPeerHost(), false); 239 } 240 241 public void handleTrustStorageUpdate() { 242 if (acceptedIssuers == null) { 243 trustedCertificateIndex.reset(); 244 } else { 245 trustedCertificateIndex.reset(trustAnchors(acceptedIssuers)); 246 } 247 } 248 249 private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType, 250 String host, boolean clientAuth) 251 throws CertificateException { 252 if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { 253 throw new IllegalArgumentException("null or zero-length parameter"); 254 } 255 if (err != null) { 256 throw new CertificateException(err); 257 } 258 259 // get the cleaned up chain and trust anchor 260 Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one! 261 X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor); 262 263 // add the first trust anchor to the chain, which may be an intermediate 264 List<X509Certificate> wholeChain = new ArrayList<X509Certificate>(); 265 wholeChain.addAll(Arrays.asList(newChain)); 266 // trustAnchor is actually just a single element 267 for (TrustAnchor trust : trustAnchor) { 268 wholeChain.add(trust.getTrustedCert()); 269 } 270 271 // add all the cached certificates from the cert index, avoiding loops 272 // this gives us a full chain from leaf to root, which we use for cert pinning and pass 273 // back out to callers when we return. 274 X509Certificate last = wholeChain.get(wholeChain.size() - 1); 275 while (true) { 276 TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last); 277 // the cachedTrust can be null if there isn't anything in the index or if a user has 278 // trusted a non-self-signed cert. 279 if (cachedTrust == null) { 280 break; 281 } 282 283 // at this point we have a cached trust anchor, but don't know if its one we got from 284 // the server. Extract the cert, compare it to the last element in the chain, and add it 285 // if we haven't seen it before. 286 X509Certificate next = cachedTrust.getTrustedCert(); 287 if (next != last) { 288 wholeChain.add(next); 289 last = next; 290 } else { 291 // if next == last then we found a self-signed cert and the chain is done 292 break; 293 } 294 } 295 296 // build the cert path from the array of certs sans trust anchors 297 CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); 298 299 if (host != null) { 300 boolean isChainValid = false; 301 try { 302 isChainValid = pinManager.isChainValid(host, wholeChain); 303 } catch (PinManagerException e) { 304 throw new CertificateException(e); 305 } 306 if (!isChainValid) { 307 throw new CertificateException(new CertPathValidatorException( 308 "Certificate path is not properly pinned.", null, certPath, -1)); 309 } 310 } 311 312 if (newChain.length == 0) { 313 // chain was entirely trusted, skip the validator 314 return wholeChain; 315 } 316 317 if (trustAnchor.isEmpty()) { 318 throw new CertificateException(new CertPathValidatorException( 319 "Trust anchor for certification path not found.", null, certPath, -1)); 320 } 321 322 // There's no point in checking trust anchors here, and it will throw off the MD5 check, 323 // so we just hand it the chain without anchors 324 ChainStrengthAnalyzer.check(newChain); 325 326 try { 327 PKIXParameters params = new PKIXParameters(trustAnchor); 328 params.setRevocationEnabled(false); 329 params.addCertPathChecker(new ExtendedKeyUsagePKIXCertPathChecker(clientAuth, 330 newChain[0])); 331 validator.validate(certPath, params); 332 // Add intermediate CAs to the index to tolerate sites 333 // that assume that the browser will have cached these. 334 // The server certificate is skipped by skipping the 335 // zeroth element of new chain and note that the root CA 336 // will have been removed in 337 // cleanupCertChainAndFindTrustAnchors. http://b/3404902 338 for (int i = 1; i < newChain.length; i++) { 339 trustedCertificateIndex.index(newChain[i]); 340 } 341 } catch (InvalidAlgorithmParameterException e) { 342 throw new CertificateException(e); 343 } catch (CertPathValidatorException e) { 344 throw new CertificateException(e); 345 } 346 347 return wholeChain; 348 } 349 350 /** 351 * Clean up the certificate chain, returning a cleaned up chain, 352 * which may be a new array instance if elements were removed. 353 * Theoretically, we shouldn't have to do this, but various web 354 * servers in practice are mis-configured to have out-of-order 355 * certificates, expired self-issued root certificate, or CAs with 356 * unsupported signature algorithms such as 357 * md2WithRSAEncryption. This also handles removing old certs 358 * after bridge CA certs. 359 */ 360 private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain, 361 Set<TrustAnchor> trustAnchors) { 362 X509Certificate[] original = chain; 363 364 // 1. Clean the received certificates chain. 365 int currIndex; 366 // Start with the first certificate in the chain, assuming it 367 // is the leaf certificate (server or client cert). 368 for (currIndex = 0; currIndex < chain.length; currIndex++) { 369 // Walk the chain to find a "subject" matching 370 // the "issuer" of the current certificate. In a properly 371 // ordered chain this should be the next cert and be fast. 372 // If not, we reorder things to be as the validator will 373 // expect. 374 boolean foundNext = false; 375 for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) { 376 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) { 377 foundNext = true; 378 // Exchange certificates so that 0 through currIndex + 1 are in proper order 379 if (nextIndex != currIndex + 1) { 380 // don't mutuate original chain, which may be directly from an SSLSession 381 if (chain == original) { 382 chain = original.clone(); 383 } 384 X509Certificate tempCertificate = chain[nextIndex]; 385 chain[nextIndex] = chain[currIndex + 1]; 386 chain[currIndex + 1] = tempCertificate; 387 } 388 break; 389 } 390 } 391 // If we can't find the next in the chain, just give up 392 // and use what we found so far. This drops unrelated 393 // certificates that have nothing to do with the cert 394 // chain. 395 if (!foundNext) { 396 break; 397 } 398 } 399 400 // 2. Find the trust anchor in the chain, if any 401 int anchorIndex; 402 for (anchorIndex = 0; anchorIndex <= currIndex; anchorIndex++) { 403 // If the current cert is a TrustAnchor, we can ignore the rest of the chain. 404 // This avoids including "bridge" CA certs that added for legacy compatibility. 405 TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]); 406 if (trustAnchor != null) { 407 trustAnchors.add(trustAnchor); 408 break; 409 } 410 } 411 412 // 3. If the chain is now shorter, copy to an appropriately sized array. 413 int chainLength = anchorIndex; 414 X509Certificate[] newChain = ((chainLength == chain.length) 415 ? chain 416 : Arrays.copyOf(chain, chainLength)); 417 418 // 4. If we didn't find a trust anchor earlier, look for one now 419 if (trustAnchors.isEmpty()) { 420 TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]); 421 if (trustAnchor != null) { 422 trustAnchors.add(trustAnchor); 423 } 424 } 425 return newChain; 426 } 427 428 /** 429 * If an EKU extension is present in the end-entity certificate, 430 * it MUST contain an appropriate key usage. For servers, this 431 * includes anyExtendedKeyUsage, serverAuth, or the historical 432 * Server Gated Cryptography options of nsSGC or msSGC. For 433 * clients, this includes anyExtendedKeyUsage and clientAuth. 434 */ 435 private static class ExtendedKeyUsagePKIXCertPathChecker extends PKIXCertPathChecker { 436 437 private static final String EKU_OID = "2.5.29.37"; 438 439 private static final String EKU_anyExtendedKeyUsage = "2.5.29.37.0"; 440 private static final String EKU_clientAuth = "1.3.6.1.5.5.7.3.2"; 441 private static final String EKU_serverAuth = "1.3.6.1.5.5.7.3.1"; 442 private static final String EKU_nsSGC = "2.16.840.1.113730.4.1"; 443 private static final String EKU_msSGC = "1.3.6.1.4.1.311.10.3.3"; 444 445 private static final Set<String> SUPPORTED_EXTENSIONS 446 = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(EKU_OID))); 447 448 private final boolean clientAuth; 449 private final X509Certificate leaf; 450 451 private ExtendedKeyUsagePKIXCertPathChecker(boolean clientAuth, X509Certificate leaf) { 452 this.clientAuth = clientAuth; 453 this.leaf = leaf; 454 } 455 456 @Override 457 public void init(boolean forward) throws CertPathValidatorException { 458 } 459 460 @Override 461 public boolean isForwardCheckingSupported() { 462 return true; 463 } 464 465 @Override 466 public Set<String> getSupportedExtensions() { 467 return SUPPORTED_EXTENSIONS; 468 } 469 470 @Override 471 public void check(Certificate c, Collection<String> unresolvedCritExts) 472 throws CertPathValidatorException { 473 // We only want to validate the EKU on the leaf certificate. 474 if (c != leaf) { 475 return; 476 } 477 List<String> ekuOids; 478 try { 479 ekuOids = leaf.getExtendedKeyUsage(); 480 } catch (CertificateParsingException e) { 481 // A malformed EKU is bad news, consider it fatal. 482 throw new CertPathValidatorException(e); 483 } 484 // We are here to check EKU, but there is none. 485 if (ekuOids == null) { 486 return; 487 } 488 489 boolean goodExtendedKeyUsage = false; 490 for (String ekuOid : ekuOids) { 491 // anyExtendedKeyUsage for clients and servers 492 if (ekuOid.equals(EKU_anyExtendedKeyUsage)) { 493 goodExtendedKeyUsage = true; 494 break; 495 } 496 497 // clients 498 if (clientAuth) { 499 if (ekuOid.equals(EKU_clientAuth)) { 500 goodExtendedKeyUsage = true; 501 break; 502 } 503 continue; 504 } 505 506 // servers 507 if (ekuOid.equals(EKU_serverAuth)) { 508 goodExtendedKeyUsage = true; 509 break; 510 } 511 if (ekuOid.equals(EKU_nsSGC)) { 512 goodExtendedKeyUsage = true; 513 break; 514 } 515 if (ekuOid.equals(EKU_msSGC)) { 516 goodExtendedKeyUsage = true; 517 break; 518 } 519 } 520 if (goodExtendedKeyUsage) { 521 // Mark extendedKeyUsage as resolved if present. 522 unresolvedCritExts.remove(EKU_OID); 523 } else { 524 throw new CertPathValidatorException("End-entity certificate does not have a valid " 525 + "extendedKeyUsage."); 526 } 527 } 528 } 529 530 private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) { 531 TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert); 532 if (trustAnchor != null) { 533 return trustAnchor; 534 } 535 if (trustedCertificateStore == null) { 536 return null; 537 } 538 // we have a KeyStore and the issuer of the last cert in 539 // the chain seems to be missing from the 540 // TrustedCertificateIndex, check the KeyStore for a hit 541 X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert); 542 if (issuer != null) { 543 return trustedCertificateIndex.index(issuer); 544 } 545 return null; 546 } 547 548 /** 549 * Check the trustedCertificateIndex for the cert to see if it is 550 * already trusted and failing that check the KeyStore if it is 551 * available. 552 */ 553 private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 554 TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert); 555 if (trustAnchor != null) { 556 return trustAnchor; 557 } 558 if (trustedCertificateStore == null) { 559 // not trusted and no TrustedCertificateStore to check 560 return null; 561 } 562 // probe KeyStore for a cert. AndroidCAStore stores its 563 // contents hashed by cert subject on the filesystem to make 564 // this faster than scanning all key store entries. 565 X509Certificate systemCert = trustedCertificateStore.getTrustAnchor(cert); 566 if (systemCert != null) { 567 // add new TrustAnchor to params index to avoid 568 // checking filesystem next time around. 569 return trustedCertificateIndex.index(systemCert); 570 } 571 return null; 572 } 573 574 @Override 575 public X509Certificate[] getAcceptedIssuers() { 576 return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore); 577 } 578} 579