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     public void handleTrustStorageUpdate() {
    167         if (acceptedIssuers == null) {
    168             trustedCertificateIndex.reset();
    169         } else {
    170             trustedCertificateIndex.reset(trustAnchors(acceptedIssuers));
    171         }
    172     }
    173 
    174     private void checkTrusted(X509Certificate[] chain, String authType)
    175             throws CertificateException {
    176         if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
    177             throw new IllegalArgumentException("null or zero-length parameter");
    178         }
    179         if (err != null) {
    180             throw new CertificateException(err);
    181         }
    182 
    183         Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
    184         X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors);
    185         if (newChain.length == 0) {
    186             // chain was entirely trusted, skip the validator
    187             return;
    188         }
    189 
    190         CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
    191         if (trustAnchors.isEmpty()) {
    192             throw new CertificateException(new CertPathValidatorException(
    193                     "Trust anchor for certification path not found.", null, certPath, -1));
    194         }
    195 
    196         try {
    197             PKIXParameters params = new PKIXParameters(trustAnchors);
    198             params.setRevocationEnabled(false);
    199             validator.validate(certPath, params);
    200             // Add intermediate CAs to the index to tolerate sites
    201             // that assume that the browser will have cached these.
    202             // The server certificate is skipped by skipping the
    203             // zeroth element of new chain and note that the root CA
    204             // will have been removed in
    205             // cleanupCertChainAndFindTrustAnchors.  http://b/3404902
    206             for (int i = 1; i < newChain.length; i++) {
    207                 trustedCertificateIndex.index(newChain[i]);
    208             }
    209         } catch (InvalidAlgorithmParameterException e) {
    210             throw new CertificateException(e);
    211         } catch (CertPathValidatorException e) {
    212             throw new CertificateException(e);
    213         }
    214     }
    215 
    216     /**
    217      * Clean up the certificate chain, returning a cleaned up chain,
    218      * which may be a new array instance if elements were removed.
    219      * Theoretically, we shouldn't have to do this, but various web
    220      * servers in practice are mis-configured to have out-of-order
    221      * certificates, expired self-issued root certificate, or CAs with
    222      * unsupported signature algorithms such as
    223      * md2WithRSAEncryption. This also handles removing old certs
    224      * after bridge CA certs.
    225      */
    226     private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
    227                                                                   Set<TrustAnchor> trustAnchors) {
    228         X509Certificate[] original = chain;
    229 
    230         // 1. Clean the received certificates chain.
    231         int currIndex;
    232         // Start with the first certificate in the chain, assuming it
    233         // is the leaf certificate (server or client cert).
    234         for (currIndex = 0; currIndex < chain.length; currIndex++) {
    235             // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
    236             // This avoids including "bridge" CA certs that added for legacy compatability.
    237             TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[currIndex]);
    238             if (trustAnchor != null) {
    239                 trustAnchors.add(trustAnchor);
    240                 currIndex--;
    241                 break;
    242             }
    243             // Walk the rest of the chain to find a "subject" matching
    244             // the "issuer" of the current certificate. In a properly
    245             // order chain this should be the next cert and be fast.
    246             // If not, we reorder things to be as the validator will
    247             // expect.
    248             boolean foundNext = false;
    249             for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
    250                 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
    251                     foundNext = true;
    252                     // Exchange certificates so that 0 through currIndex + 1 are in proper order
    253                     if (nextIndex != currIndex + 1) {
    254                         // don't mutuate original chain, which may be directly from an SSLSession
    255                         if (chain == original) {
    256                             chain = original.clone();
    257                         }
    258                         X509Certificate tempCertificate = chain[nextIndex];
    259                         chain[nextIndex] = chain[currIndex + 1];
    260                         chain[currIndex + 1] = tempCertificate;
    261                     }
    262                     break;
    263                 }
    264             }
    265             // If we can't find the next in the chain, just give up
    266             // and use what we found so far. This drops unrelated
    267             // certificates that have nothing to do with the cert
    268             // chain.
    269             if (!foundNext) {
    270                 break;
    271             }
    272         }
    273 
    274         // 2. If the chain is now shorter, copy to an appropriately sized array.
    275         int chainLength = currIndex + 1;
    276         X509Certificate[] newChain = ((chainLength == chain.length)
    277                                       ? chain
    278                                       : Arrays.copyOf(chain, chainLength));
    279 
    280         // 3. If no TrustAnchor was found in cleanup, look for one now
    281         if (trustAnchors.isEmpty()) {
    282             TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[chainLength-1]);
    283             if (trustAnchor != null) {
    284                 trustAnchors.add(trustAnchor);
    285             }
    286         }
    287         return newChain;
    288     }
    289 
    290     /**
    291      * Check the trustedCertificateIndex for the cert to see if it is
    292      * already trusted and failing that check the KeyStore if it is
    293      * available.
    294      */
    295     private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
    296         TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert);
    297         if (trustAnchor != null) {
    298             return trustAnchor;
    299         }
    300         if (trustedCertificateStore == null) {
    301             // not trusted and no TrustedCertificateStore to check
    302             return null;
    303         }
    304         // probe KeyStore for a cert. AndroidCAStore stores its
    305         // contents hashed by cert subject on the filesystem to make
    306         // this faster than scanning all key store entries.
    307         if (trustedCertificateStore.isTrustAnchor(cert)) {
    308             // add new TrustAnchor to params index to avoid
    309             // checking filesystem next time around.
    310             return trustedCertificateIndex.index(cert);
    311         }
    312         return null;
    313     }
    314 
    315     private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) {
    316         TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert);
    317         if (trustAnchor != null) {
    318             return trustAnchor;
    319         }
    320         if (trustedCertificateStore == null) {
    321             return null;
    322         }
    323         // we have a KeyStore and the issuer of the last cert in
    324         // the chain seems to be missing from the
    325         // TrustedCertificateIndex, check the KeyStore for a hit
    326         X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
    327         if (issuer != null) {
    328             return trustedCertificateIndex.index(issuer);
    329         }
    330         return null;
    331     }
    332 
    333     @Override public X509Certificate[] getAcceptedIssuers() {
    334         return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore);
    335     }
    336 }
    337