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 
     39 /**
     40  *
     41  * TrustManager implementation. The implementation is based on CertPathValidator
     42  * PKIX and CertificateFactory X509 implementations. This implementations should
     43  * be provided by some certification provider.
     44  *
     45  * @see javax.net.ssl.X509TrustManager
     46  */
     47 public final class TrustManagerImpl implements X509TrustManager {
     48 
     49     /**
     50      * The AndroidCAStore if non-null, null otherwise.
     51      */
     52     private final KeyStore rootKeyStore;
     53 
     54     /**
     55      * The backing store for the AndroidCAStore if non-null. This will
     56      * be null when the rootKeyStore is null, implying we are not
     57      * using the AndroidCAStore.
     58      */
     59     private final TrustedCertificateStore trustedCertificateStore;
     60 
     61     private final CertPathValidator validator;
     62 
     63     /**
     64      * An index of TrustAnchor instances that we've seen. Unlike the
     65      * TrustedCertificateStore, this may contain intermediate CAs.
     66      */
     67     private final TrustedCertificateIndex trustedCertificateIndex;
     68 
     69     /**
     70      * This is lazily initialized in the AndroidCAStore case since it
     71      * forces us to bring all the CAs into memory. In the
     72      * non-AndroidCAStore, we initialize this as part of the
     73      * constructor.
     74      */
     75     private final X509Certificate[] acceptedIssuers;
     76 
     77     private final Exception err;
     78     private final CertificateFactory factory;
     79 
     80     /**
     81      * Creates X509TrustManager based on a keystore
     82      *
     83      * @param ks
     84      */
     85     public TrustManagerImpl(KeyStore keyStore) {
     86         CertPathValidator validatorLocal = null;
     87         CertificateFactory factoryLocal = null;
     88         KeyStore rootKeyStoreLocal = null;
     89         TrustedCertificateStore trustedCertificateStoreLocal = null;
     90         TrustedCertificateIndex trustedCertificateIndexLocal = null;
     91         X509Certificate[] acceptedIssuersLocal = null;
     92         Exception errLocal = null;
     93         try {
     94             validatorLocal = CertPathValidator.getInstance("PKIX");
     95             factoryLocal = CertificateFactory.getInstance("X509");
     96 
     97             // if we have an AndroidCAStore, we will lazily load CAs
     98             if ("AndroidCAStore".equals(keyStore.getType())) {
     99                 rootKeyStoreLocal = keyStore;
    100                 trustedCertificateStoreLocal = new TrustedCertificateStore();
    101                 acceptedIssuersLocal = null;
    102                 trustedCertificateIndexLocal = new TrustedCertificateIndex();
    103             } else {
    104                 rootKeyStoreLocal = null;
    105                 trustedCertificateStoreLocal = null;
    106                 acceptedIssuersLocal = acceptedIssuers(keyStore);
    107                 trustedCertificateIndexLocal
    108                         = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal));
    109             }
    110 
    111         } catch (Exception e) {
    112             errLocal = e;
    113         }
    114         this.rootKeyStore = rootKeyStoreLocal;
    115         this.trustedCertificateStore = trustedCertificateStoreLocal;
    116         this.validator = validatorLocal;
    117         this.factory = factoryLocal;
    118         this.trustedCertificateIndex = trustedCertificateIndexLocal;
    119         this.acceptedIssuers = acceptedIssuersLocal;
    120         this.err = errLocal;
    121     }
    122 
    123     private static X509Certificate[] acceptedIssuers(KeyStore ks) {
    124         try {
    125             // Note that unlike the PKIXParameters code to create a Set of
    126             // TrustAnchors from a KeyStore, this version takes from both
    127             // TrustedCertificateEntry and PrivateKeyEntry, not just
    128             // TrustedCertificateEntry, which is why TrustManagerImpl
    129             // cannot just use an PKIXParameters(KeyStore)
    130             // constructor.
    131 
    132             // TODO remove duplicates if same cert is found in both a
    133             // PrivateKeyEntry and TrustedCertificateEntry
    134             List<X509Certificate> trusted = new ArrayList<X509Certificate>();
    135             for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) {
    136                 final String alias = en.nextElement();
    137                 final X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
    138                 if (cert != null) {
    139                     trusted.add(cert);
    140                 }
    141             }
    142             return trusted.toArray(new X509Certificate[trusted.size()]);
    143         } catch (KeyStoreException e) {
    144             return new X509Certificate[0];
    145         }
    146     }
    147 
    148     private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) {
    149         Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length);
    150         for (X509Certificate cert : certs) {
    151             trustAnchors.add(new TrustAnchor(cert, null));
    152         }
    153         return trustAnchors;
    154     }
    155 
    156     @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
    157             throws CertificateException {
    158         checkTrusted(chain, authType);
    159     }
    160 
    161     @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
    162             throws CertificateException {
    163         checkTrusted(chain, authType);
    164     }
    165 
    166     private void checkTrusted(X509Certificate[] chain, String authType)
    167             throws CertificateException {
    168         if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
    169             throw new IllegalArgumentException("null or zero-length parameter");
    170         }
    171         if (err != null) {
    172             throw new CertificateException(err);
    173         }
    174 
    175         Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
    176         X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors);
    177         if (newChain.length == 0) {
    178             // chain was entirely trusted, skip the validator
    179             return;
    180         }
    181 
    182         CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
    183         if (trustAnchors.isEmpty()) {
    184             throw new CertificateException(new CertPathValidatorException(
    185                     "Trust anchor for certification path not found.", null, certPath, -1));
    186         }
    187 
    188         try {
    189             PKIXParameters params = new PKIXParameters(trustAnchors);
    190             params.setRevocationEnabled(false);
    191             validator.validate(certPath, params);
    192             // Add intermediate CAs to the index to tolerate sites
    193             // that assume that the browser will have cached these.
    194             // The server certificate is skipped by skipping the
    195             // zeroth element of new chain and note that the root CA
    196             // will have been removed in
    197             // cleanupCertChainAndFindTrustAnchors.  http://b/3404902
    198             for (int i = 1; i < newChain.length; i++) {
    199                 trustedCertificateIndex.index(newChain[i]);
    200             }
    201         } catch (InvalidAlgorithmParameterException e) {
    202             throw new CertificateException(e);
    203         } catch (CertPathValidatorException e) {
    204             throw new CertificateException(e);
    205         }
    206     }
    207 
    208     /**
    209      * Clean up the certificate chain, returning a cleaned up chain,
    210      * which may be a new array instance if elements were removed.
    211      * Theoretically, we shouldn't have to do this, but various web
    212      * servers in practice are mis-configured to have out-of-order
    213      * certificates, expired self-issued root certificate, or CAs with
    214      * unsupported signature algorithms such as
    215      * md2WithRSAEncryption. This also handles removing old certs
    216      * after bridge CA certs.
    217      */
    218     private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
    219                                                                   Set<TrustAnchor> trustAnchors) {
    220         X509Certificate[] original = chain;
    221 
    222         // 1. Clean the received certificates chain.
    223         int currIndex;
    224         // Start with the first certificate in the chain, assuming it
    225         // is the leaf certificate (server or client cert).
    226         for (currIndex = 0; currIndex < chain.length; currIndex++) {
    227             // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
    228             // This avoids including "bridge" CA certs that added for legacy compatability.
    229             TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[currIndex]);
    230             if (trustAnchor != null) {
    231                 trustAnchors.add(trustAnchor);
    232                 currIndex--;
    233                 break;
    234             }
    235             // Walk the rest of the chain to find a "subject" matching
    236             // the "issuer" of the current certificate. In a properly
    237             // order chain this should be the next cert and be fast.
    238             // If not, we reorder things to be as the validator will
    239             // expect.
    240             boolean foundNext = false;
    241             for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
    242                 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
    243                     foundNext = true;
    244                     // Exchange certificates so that 0 through currIndex + 1 are in proper order
    245                     if (nextIndex != currIndex + 1) {
    246                         // don't mutuate original chain, which may be directly from an SSLSession
    247                         if (chain == original) {
    248                             chain = original.clone();
    249                         }
    250                         X509Certificate tempCertificate = chain[nextIndex];
    251                         chain[nextIndex] = chain[currIndex + 1];
    252                         chain[currIndex + 1] = tempCertificate;
    253                     }
    254                     break;
    255                 }
    256             }
    257             // If we can't find the next in the chain, just give up
    258             // and use what we found so far. This drops unrelated
    259             // certificates that have nothing to do with the cert
    260             // chain.
    261             if (!foundNext) {
    262                 break;
    263             }
    264         }
    265 
    266         // 2. If the chain is now shorter, copy to an appropriately sized array.
    267         int chainLength = currIndex + 1;
    268         X509Certificate[] newChain = ((chainLength == chain.length)
    269                                       ? chain
    270                                       : Arrays.copyOf(chain, chainLength));
    271 
    272         // 3. If no TrustAnchor was found in cleanup, look for one now
    273         if (trustAnchors.isEmpty()) {
    274             TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[chainLength-1]);
    275             if (trustAnchor != null) {
    276                 trustAnchors.add(trustAnchor);
    277             }
    278         }
    279         return newChain;
    280     }
    281 
    282     /**
    283      * Check the trustedCertificateIndex for the cert to see if it is
    284      * already trusted and failing that check the KeyStore if it is
    285      * available.
    286      */
    287     private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
    288         TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert);
    289         if (trustAnchor != null) {
    290             return trustAnchor;
    291         }
    292         if (trustedCertificateStore == null) {
    293             // not trusted and no TrustedCertificateStore to check
    294             return null;
    295         }
    296         // probe KeyStore for a cert. AndroidCAStore stores its
    297         // contents hashed by cert subject on the filesystem to make
    298         // this faster than scanning all key store entries.
    299         if (trustedCertificateStore.isTrustAnchor(cert)) {
    300             // add new TrustAnchor to params index to avoid
    301             // checking filesystem next time around.
    302             return trustedCertificateIndex.index(cert);
    303         }
    304         return null;
    305     }
    306 
    307     private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) {
    308         TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert);
    309         if (trustAnchor != null) {
    310             return trustAnchor;
    311         }
    312         if (trustedCertificateStore == null) {
    313             return null;
    314         }
    315         // we have a KeyStore and the issuer of the last cert in
    316         // the chain seems to be missing from the
    317         // TrustedCertificateIndex, check the KeyStore for a hit
    318         X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
    319         if (issuer != null) {
    320             return trustedCertificateIndex.index(issuer);
    321         }
    322         return null;
    323     }
    324 
    325     @Override public X509Certificate[] getAcceptedIssuers() {
    326         return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore);
    327     }
    328 }
    329