Home | History | Annotate | Download | only in jsse
      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