TrustManagerImpl.java revision 5c9add3e84fd426fafbec289738f1f09c49aaf90
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.apache.harmony.xnet.provider.jsse; 19 20import java.security.InvalidAlgorithmParameterException; 21import java.security.KeyStore; 22import java.security.KeyStoreException; 23import java.security.cert.CertPath; 24import java.security.cert.CertPathValidator; 25import java.security.cert.CertPathValidatorException; 26import java.security.cert.CertificateException; 27import java.security.cert.CertificateFactory; 28import java.security.cert.PKIXParameters; 29import java.security.cert.TrustAnchor; 30import java.security.cert.X509Certificate; 31import java.util.Arrays; 32import java.util.ArrayList; 33import java.util.Enumeration; 34import java.util.HashSet; 35import java.util.List; 36import java.util.Set; 37import javax.net.ssl.X509TrustManager; 38import 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 */ 48public 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(); 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 anchors 218 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(); 219 X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors); 220 221 // build the whole chain including all the trust anchors 222 List<X509Certificate> wholeChain = new ArrayList<X509Certificate>(); 223 wholeChain.addAll(Arrays.asList(newChain)); 224 for (TrustAnchor trust : trustAnchors) { 225 wholeChain.add(trust.getTrustedCert()); 226 } 227 228 // build the cert path from the array of certs sans trust anchors 229 CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); 230 231 if (host != null) { 232 boolean chainIsNotPinned = true; 233 try { 234 chainIsNotPinned = pinManager.chainIsNotPinned(host, wholeChain); 235 } catch (PinManagerException e) { 236 throw new CertificateException(e); 237 } 238 if (chainIsNotPinned) { 239 EventLogger.writeEvent(90102, chainContainsUserCert(wholeChain)); 240 throw new CertificateException(new CertPathValidatorException( 241 "Certificate path is not properly pinned.", null, certPath, -1)); 242 } 243 } 244 245 if (newChain.length == 0) { 246 // chain was entirely trusted, skip the validator 247 return wholeChain; 248 } 249 250 if (trustAnchors.isEmpty()) { 251 throw new CertificateException(new CertPathValidatorException( 252 "Trust anchor for certification path not found.", null, certPath, -1)); 253 } 254 255 try { 256 PKIXParameters params = new PKIXParameters(trustAnchors); 257 params.setRevocationEnabled(false); 258 validator.validate(certPath, params); 259 // Add intermediate CAs to the index to tolerate sites 260 // that assume that the browser will have cached these. 261 // The server certificate is skipped by skipping the 262 // zeroth element of new chain and note that the root CA 263 // will have been removed in 264 // cleanupCertChainAndFindTrustAnchors. http://b/3404902 265 for (int i = 1; i < newChain.length; i++) { 266 trustedCertificateIndex.index(newChain[i]); 267 } 268 } catch (InvalidAlgorithmParameterException e) { 269 throw new CertificateException(e); 270 } catch (CertPathValidatorException e) { 271 throw new CertificateException(e); 272 } 273 274 return wholeChain; 275 } 276 277 /** 278 * Clean up the certificate chain, returning a cleaned up chain, 279 * which may be a new array instance if elements were removed. 280 * Theoretically, we shouldn't have to do this, but various web 281 * servers in practice are mis-configured to have out-of-order 282 * certificates, expired self-issued root certificate, or CAs with 283 * unsupported signature algorithms such as 284 * md2WithRSAEncryption. This also handles removing old certs 285 * after bridge CA certs. 286 */ 287 private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain, 288 Set<TrustAnchor> trustAnchors) { 289 X509Certificate[] original = chain; 290 291 // 1. Clean the received certificates chain. 292 int currIndex; 293 // Start with the first certificate in the chain, assuming it 294 // is the leaf certificate (server or client cert). 295 for (currIndex = 0; currIndex < chain.length; currIndex++) { 296 // Walk the chain to find a "subject" matching 297 // the "issuer" of the current certificate. In a properly 298 // ordered chain this should be the next cert and be fast. 299 // If not, we reorder things to be as the validator will 300 // expect. 301 boolean foundNext = false; 302 for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) { 303 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) { 304 foundNext = true; 305 // Exchange certificates so that 0 through currIndex + 1 are in proper order 306 if (nextIndex != currIndex + 1) { 307 // don't mutuate original chain, which may be directly from an SSLSession 308 if (chain == original) { 309 chain = original.clone(); 310 } 311 X509Certificate tempCertificate = chain[nextIndex]; 312 chain[nextIndex] = chain[currIndex + 1]; 313 chain[currIndex + 1] = tempCertificate; 314 } 315 break; 316 } 317 } 318 // If we can't find the next in the chain, just give up 319 // and use what we found so far. This drops unrelated 320 // certificates that have nothing to do with the cert 321 // chain. 322 if (!foundNext) { 323 break; 324 } 325 } 326 327 // 2. Find the trust anchor in the chain, if any 328 int anchorIndex; 329 for (anchorIndex = 0; anchorIndex < chain.length; anchorIndex++) { 330 // If the current cert is a TrustAnchor, we can ignore the rest of the chain. 331 // This avoids including "bridge" CA certs that added for legacy compatibility. 332 TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]); 333 if (trustAnchor != null) { 334 trustAnchors.add(trustAnchor); 335 break; 336 } 337 } 338 339 // 3. If the chain is now shorter, copy to an appropriately sized array. 340 int chainLength = anchorIndex; 341 X509Certificate[] newChain = ((chainLength == chain.length) 342 ? chain 343 : Arrays.copyOf(chain, chainLength)); 344 345 // 4. If we didn't find a trust anchor earlier, look for one now 346 if (trustAnchors.isEmpty()) { 347 TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]); 348 if (trustAnchor != null) { 349 trustAnchors.add(trustAnchor); 350 } 351 } 352 return newChain; 353 } 354 355 /** 356 * Check the trustedCertificateIndex for the cert to see if it is 357 * already trusted and failing that check the KeyStore if it is 358 * available. 359 */ 360 private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 361 TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert); 362 if (trustAnchor != null) { 363 return trustAnchor; 364 } 365 if (trustedCertificateStore == null) { 366 // not trusted and no TrustedCertificateStore to check 367 return null; 368 } 369 // probe KeyStore for a cert. AndroidCAStore stores its 370 // contents hashed by cert subject on the filesystem to make 371 // this faster than scanning all key store entries. 372 if (trustedCertificateStore.isTrustAnchor(cert)) { 373 // add new TrustAnchor to params index to avoid 374 // checking filesystem next time around. 375 return trustedCertificateIndex.index(cert); 376 } 377 return null; 378 } 379 380 private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) { 381 TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert); 382 if (trustAnchor != null) { 383 return trustAnchor; 384 } 385 if (trustedCertificateStore == null) { 386 return null; 387 } 388 // we have a KeyStore and the issuer of the last cert in 389 // the chain seems to be missing from the 390 // TrustedCertificateIndex, check the KeyStore for a hit 391 X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert); 392 if (issuer != null) { 393 return trustedCertificateIndex.index(issuer); 394 } 395 return null; 396 } 397 398 @Override public X509Certificate[] getAcceptedIssuers() { 399 return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore); 400 } 401 402 private boolean chainContainsUserCert(List<X509Certificate> chain) { 403 for (X509Certificate cert : chain) { 404 if (trustedCertificateStore.isUserAddedCertificate(cert)) { 405 return true; 406 } 407 } 408 return false; 409 } 410} 411