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 18 package org.apache.harmony.xnet.provider.jsse; 19 20 import java.security.InvalidAlgorithmParameterException; 21 import java.security.KeyStore; 22 import java.security.KeyStoreException; 23 import java.security.cert.CertPath; 24 import java.security.cert.CertPathValidator; 25 import java.security.cert.CertPathValidatorException; 26 import java.security.cert.CertificateException; 27 import java.security.cert.CertificateFactory; 28 import java.security.cert.PKIXParameters; 29 import java.security.cert.TrustAnchor; 30 import java.security.cert.X509Certificate; 31 import java.util.Arrays; 32 import java.util.ArrayList; 33 import java.util.Enumeration; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Set; 37 import javax.net.ssl.X509TrustManager; 38 import libcore.io.EventLogger; 39 40 /** 41 * 42 * TrustManager implementation. The implementation is based on CertPathValidator 43 * PKIX and CertificateFactory X509 implementations. This implementations should 44 * be provided by some certification provider. 45 * 46 * @see javax.net.ssl.X509TrustManager 47 */ 48 public final class TrustManagerImpl implements X509TrustManager { 49 50 /** 51 * The AndroidCAStore if non-null, null otherwise. 52 */ 53 private final KeyStore rootKeyStore; 54 55 /** 56 * The CertPinManager, which validates the chain against a host-to-pin mapping 57 */ 58 private CertPinManager pinManager; 59 60 /** 61 * The backing store for the AndroidCAStore if non-null. This will 62 * be null when the rootKeyStore is null, implying we are not 63 * using the AndroidCAStore. 64 */ 65 private final TrustedCertificateStore trustedCertificateStore; 66 67 private final CertPathValidator validator; 68 69 /** 70 * An index of TrustAnchor instances that we've seen. Unlike the 71 * TrustedCertificateStore, this may contain intermediate CAs. 72 */ 73 private final TrustedCertificateIndex trustedCertificateIndex; 74 75 /** 76 * This is lazily initialized in the AndroidCAStore case since it 77 * forces us to bring all the CAs into memory. In the 78 * non-AndroidCAStore, we initialize this as part of the 79 * constructor. 80 */ 81 private final X509Certificate[] acceptedIssuers; 82 83 private final Exception err; 84 private final CertificateFactory factory; 85 86 /** 87 * Creates X509TrustManager based on a keystore 88 * 89 * @param ks 90 */ 91 public TrustManagerImpl(KeyStore keyStore) { 92 this(keyStore, null); 93 } 94 95 /** 96 * For testing only 97 */ 98 public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) { 99 CertPathValidator validatorLocal = null; 100 CertificateFactory factoryLocal = null; 101 KeyStore rootKeyStoreLocal = null; 102 TrustedCertificateStore trustedCertificateStoreLocal = null; 103 TrustedCertificateIndex trustedCertificateIndexLocal = null; 104 X509Certificate[] acceptedIssuersLocal = null; 105 Exception errLocal = null; 106 try { 107 validatorLocal = CertPathValidator.getInstance("PKIX"); 108 factoryLocal = CertificateFactory.getInstance("X509"); 109 110 // if we have an AndroidCAStore, we will lazily load CAs 111 if ("AndroidCAStore".equals(keyStore.getType())) { 112 rootKeyStoreLocal = keyStore; 113 trustedCertificateStoreLocal = new TrustedCertificateStore(); 114 acceptedIssuersLocal = null; 115 trustedCertificateIndexLocal = new TrustedCertificateIndex(); 116 } else { 117 rootKeyStoreLocal = null; 118 trustedCertificateStoreLocal = null; 119 acceptedIssuersLocal = acceptedIssuers(keyStore); 120 trustedCertificateIndexLocal 121 = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal)); 122 } 123 124 } catch (Exception e) { 125 errLocal = e; 126 } 127 128 if (manager != null) { 129 this.pinManager = manager; 130 } else { 131 try { 132 pinManager = new CertPinManager(trustedCertificateStoreLocal); 133 } catch (PinManagerException e) { 134 throw new SecurityException("Could not initialize CertPinManager", e); 135 } 136 } 137 138 this.rootKeyStore = rootKeyStoreLocal; 139 this.trustedCertificateStore = trustedCertificateStoreLocal; 140 this.validator = validatorLocal; 141 this.factory = factoryLocal; 142 this.trustedCertificateIndex = trustedCertificateIndexLocal; 143 this.acceptedIssuers = acceptedIssuersLocal; 144 this.err = errLocal; 145 } 146 147 private static X509Certificate[] acceptedIssuers(KeyStore ks) { 148 try { 149 // Note that unlike the PKIXParameters code to create a Set of 150 // TrustAnchors from a KeyStore, this version takes from both 151 // TrustedCertificateEntry and PrivateKeyEntry, not just 152 // TrustedCertificateEntry, which is why TrustManagerImpl 153 // cannot just use an PKIXParameters(KeyStore) 154 // constructor. 155 156 // TODO remove duplicates if same cert is found in both a 157 // PrivateKeyEntry and TrustedCertificateEntry 158 List<X509Certificate> trusted = new ArrayList<X509Certificate>(); 159 for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) { 160 final String alias = en.nextElement(); 161 final X509Certificate cert = (X509Certificate) ks.getCertificate(alias); 162 if (cert != null) { 163 trusted.add(cert); 164 } 165 } 166 return trusted.toArray(new X509Certificate[trusted.size()]); 167 } catch (KeyStoreException e) { 168 return new X509Certificate[0]; 169 } 170 } 171 172 private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) { 173 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length); 174 for (X509Certificate cert : certs) { 175 trustAnchors.add(new TrustAnchor(cert, null)); 176 } 177 return trustAnchors; 178 } 179 180 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 181 throws CertificateException { 182 checkTrusted(chain, authType, null); 183 } 184 185 @Override public void checkServerTrusted(X509Certificate[] chain, String authType) 186 throws CertificateException { 187 checkTrusted(chain, authType, null); 188 } 189 190 /** 191 * Validates whether a server is trusted. If hostname is given and non-null it also checks if 192 * chain is pinned appropriately for that host. If null, it does not check for pinned certs. 193 * The return value is a list of the certificates used for making the trust decision. 194 */ 195 public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, 196 String host) throws CertificateException { 197 return checkTrusted(chain, authType, host); 198 } 199 200 public void handleTrustStorageUpdate() { 201 if (acceptedIssuers == null) { 202 trustedCertificateIndex.reset(); 203 } else { 204 trustedCertificateIndex.reset(trustAnchors(acceptedIssuers)); 205 } 206 } 207 208 private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType, String host) 209 throws CertificateException { 210 if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { 211 throw new IllegalArgumentException("null or zero-length parameter"); 212 } 213 if (err != null) { 214 throw new CertificateException(err); 215 } 216 217 // get the cleaned up chain and trust anchor 218 Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one! 219 X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor); 220 221 // add the first trust anchor to the chain, which may be an intermediate 222 List<X509Certificate> wholeChain = new ArrayList<X509Certificate>(); 223 wholeChain.addAll(Arrays.asList(newChain)); 224 // trustAnchor is actually just a single element 225 for (TrustAnchor trust : trustAnchor) { 226 wholeChain.add(trust.getTrustedCert()); 227 } 228 229 // add all the cached certificates from the cert index, avoiding loops 230 // this gives us a full chain from leaf to root, which we use for cert pinning and pass 231 // back out to callers when we return. 232 X509Certificate last = wholeChain.get(wholeChain.size() - 1); 233 while (true) { 234 TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last); 235 // the cachedTrust can be null if there isn't anything in the index or if a user has 236 // trusted a non-self-signed cert. 237 if (cachedTrust == null) { 238 break; 239 } 240 241 // at this point we have a cached trust anchor, but don't know if its one we got from 242 // the server. Extract the cert, compare it to the last element in the chain, and add it 243 // if we haven't seen it before. 244 X509Certificate next = cachedTrust.getTrustedCert(); 245 if (next != last) { 246 wholeChain.add(next); 247 last = next; 248 } else { 249 // if next == last then we found a self-signed cert and the chain is done 250 break; 251 } 252 } 253 254 // build the cert path from the array of certs sans trust anchors 255 CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); 256 257 if (host != null) { 258 boolean chainIsNotPinned = true; 259 try { 260 chainIsNotPinned = pinManager.chainIsNotPinned(host, wholeChain); 261 } catch (PinManagerException e) { 262 throw new CertificateException(e); 263 } 264 if (chainIsNotPinned) { 265 throw new CertificateException(new CertPathValidatorException( 266 "Certificate path is not properly pinned.", null, certPath, -1)); 267 } 268 } 269 270 if (newChain.length == 0) { 271 // chain was entirely trusted, skip the validator 272 return wholeChain; 273 } 274 275 if (trustAnchor.isEmpty()) { 276 throw new CertificateException(new CertPathValidatorException( 277 "Trust anchor for certification path not found.", null, certPath, -1)); 278 } 279 280 try { 281 PKIXParameters params = new PKIXParameters(trustAnchor); 282 params.setRevocationEnabled(false); 283 validator.validate(certPath, params); 284 // Add intermediate CAs to the index to tolerate sites 285 // that assume that the browser will have cached these. 286 // The server certificate is skipped by skipping the 287 // zeroth element of new chain and note that the root CA 288 // will have been removed in 289 // cleanupCertChainAndFindTrustAnchors. http://b/3404902 290 for (int i = 1; i < newChain.length; i++) { 291 trustedCertificateIndex.index(newChain[i]); 292 } 293 } catch (InvalidAlgorithmParameterException e) { 294 throw new CertificateException(e); 295 } catch (CertPathValidatorException e) { 296 throw new CertificateException(e); 297 } 298 299 return wholeChain; 300 } 301 302 /** 303 * Clean up the certificate chain, returning a cleaned up chain, 304 * which may be a new array instance if elements were removed. 305 * Theoretically, we shouldn't have to do this, but various web 306 * servers in practice are mis-configured to have out-of-order 307 * certificates, expired self-issued root certificate, or CAs with 308 * unsupported signature algorithms such as 309 * md2WithRSAEncryption. This also handles removing old certs 310 * after bridge CA certs. 311 */ 312 private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain, 313 Set<TrustAnchor> trustAnchors) { 314 X509Certificate[] original = chain; 315 316 // 1. Clean the received certificates chain. 317 int currIndex; 318 // Start with the first certificate in the chain, assuming it 319 // is the leaf certificate (server or client cert). 320 for (currIndex = 0; currIndex < chain.length; currIndex++) { 321 // Walk the chain to find a "subject" matching 322 // the "issuer" of the current certificate. In a properly 323 // ordered chain this should be the next cert and be fast. 324 // If not, we reorder things to be as the validator will 325 // expect. 326 boolean foundNext = false; 327 for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) { 328 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) { 329 foundNext = true; 330 // Exchange certificates so that 0 through currIndex + 1 are in proper order 331 if (nextIndex != currIndex + 1) { 332 // don't mutuate original chain, which may be directly from an SSLSession 333 if (chain == original) { 334 chain = original.clone(); 335 } 336 X509Certificate tempCertificate = chain[nextIndex]; 337 chain[nextIndex] = chain[currIndex + 1]; 338 chain[currIndex + 1] = tempCertificate; 339 } 340 break; 341 } 342 } 343 // If we can't find the next in the chain, just give up 344 // and use what we found so far. This drops unrelated 345 // certificates that have nothing to do with the cert 346 // chain. 347 if (!foundNext) { 348 break; 349 } 350 } 351 352 // 2. Find the trust anchor in the chain, if any 353 int anchorIndex; 354 for (anchorIndex = 0; anchorIndex < chain.length; anchorIndex++) { 355 // If the current cert is a TrustAnchor, we can ignore the rest of the chain. 356 // This avoids including "bridge" CA certs that added for legacy compatibility. 357 TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]); 358 if (trustAnchor != null) { 359 trustAnchors.add(trustAnchor); 360 break; 361 } 362 } 363 364 // 3. If the chain is now shorter, copy to an appropriately sized array. 365 int chainLength = anchorIndex; 366 X509Certificate[] newChain = ((chainLength == chain.length) 367 ? chain 368 : Arrays.copyOf(chain, chainLength)); 369 370 // 4. If we didn't find a trust anchor earlier, look for one now 371 if (trustAnchors.isEmpty()) { 372 TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]); 373 if (trustAnchor != null) { 374 trustAnchors.add(trustAnchor); 375 } 376 } 377 return newChain; 378 } 379 380 /** 381 * Check the trustedCertificateIndex for the cert to see if it is 382 * already trusted and failing that check the KeyStore if it is 383 * available. 384 */ 385 private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 386 TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert); 387 if (trustAnchor != null) { 388 return trustAnchor; 389 } 390 if (trustedCertificateStore == null) { 391 // not trusted and no TrustedCertificateStore to check 392 return null; 393 } 394 // probe KeyStore for a cert. AndroidCAStore stores its 395 // contents hashed by cert subject on the filesystem to make 396 // this faster than scanning all key store entries. 397 if (trustedCertificateStore.isTrustAnchor(cert)) { 398 // add new TrustAnchor to params index to avoid 399 // checking filesystem next time around. 400 return trustedCertificateIndex.index(cert); 401 } 402 return null; 403 } 404 405 private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) { 406 TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert); 407 if (trustAnchor != null) { 408 return trustAnchor; 409 } 410 if (trustedCertificateStore == null) { 411 return null; 412 } 413 // we have a KeyStore and the issuer of the last cert in 414 // the chain seems to be missing from the 415 // TrustedCertificateIndex, check the KeyStore for a hit 416 X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert); 417 if (issuer != null) { 418 return trustedCertificateIndex.index(issuer); 419 } 420 return null; 421 } 422 423 @Override public X509Certificate[] getAcceptedIssuers() { 424 return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore); 425 } 426 } 427