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.conscrypt;
19
20import java.net.Socket;
21import java.security.InvalidAlgorithmParameterException;
22import java.security.KeyStore;
23import java.security.KeyStoreException;
24import java.security.cert.CertPath;
25import java.security.cert.CertPathValidator;
26import java.security.cert.CertPathValidatorException;
27import java.security.cert.Certificate;
28import java.security.cert.CertificateException;
29import java.security.cert.CertificateParsingException;
30import java.security.cert.CertificateFactory;
31import java.security.cert.PKIXCertPathChecker;
32import java.security.cert.PKIXParameters;
33import java.security.cert.TrustAnchor;
34import java.security.cert.X509Certificate;
35import java.util.Arrays;
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.Enumeration;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Set;
43import javax.net.ssl.HostnameVerifier;
44import javax.net.ssl.HttpsURLConnection;
45import javax.net.ssl.SSLEngine;
46import javax.net.ssl.SSLParameters;
47import javax.net.ssl.SSLSession;
48import javax.net.ssl.SSLSocket;
49import javax.net.ssl.X509TrustManager;
50
51/**
52 *
53 * TrustManager implementation. The implementation is based on CertPathValidator
54 * PKIX and CertificateFactory X509 implementations. This implementations should
55 * be provided by some certification provider.
56 *
57 * @see javax.net.ssl.X509TrustManager
58 */
59public final class TrustManagerImpl implements X509TrustManager {
60
61    /**
62     * The AndroidCAStore if non-null, null otherwise.
63     */
64    private final KeyStore rootKeyStore;
65
66    /**
67     * The CertPinManager, which validates the chain against a host-to-pin mapping
68     */
69    private CertPinManager pinManager;
70
71    /**
72     * The backing store for the AndroidCAStore if non-null. This will
73     * be null when the rootKeyStore is null, implying we are not
74     * using the AndroidCAStore.
75     */
76    private final TrustedCertificateStore trustedCertificateStore;
77
78    private final CertPathValidator validator;
79
80    /**
81     * An index of TrustAnchor instances that we've seen. Unlike the
82     * TrustedCertificateStore, this may contain intermediate CAs.
83     */
84    private final TrustedCertificateIndex trustedCertificateIndex;
85
86    /**
87     * This is lazily initialized in the AndroidCAStore case since it
88     * forces us to bring all the CAs into memory. In the
89     * non-AndroidCAStore, we initialize this as part of the
90     * constructor.
91     */
92    private final X509Certificate[] acceptedIssuers;
93
94    private final Exception err;
95    private final CertificateFactory factory;
96
97    /**
98     * Creates X509TrustManager based on a keystore
99     *
100     * @param keyStore
101     */
102    public TrustManagerImpl(KeyStore keyStore) {
103        this(keyStore, null);
104    }
105
106    /**
107     * For testing only
108     */
109    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) {
110        this(keyStore, manager, null);
111    }
112
113    /**
114     * For testing only.
115     */
116    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
117                            TrustedCertificateStore certStore) {
118        CertPathValidator validatorLocal = null;
119        CertificateFactory factoryLocal = null;
120        KeyStore rootKeyStoreLocal = null;
121        TrustedCertificateStore trustedCertificateStoreLocal = null;
122        TrustedCertificateIndex trustedCertificateIndexLocal = null;
123        X509Certificate[] acceptedIssuersLocal = null;
124        Exception errLocal = null;
125        try {
126            validatorLocal = CertPathValidator.getInstance("PKIX");
127            factoryLocal = CertificateFactory.getInstance("X509");
128
129            // if we have an AndroidCAStore, we will lazily load CAs
130            if ("AndroidCAStore".equals(keyStore.getType())) {
131                rootKeyStoreLocal = keyStore;
132                trustedCertificateStoreLocal =
133                    (certStore != null) ? certStore : new TrustedCertificateStore();
134                acceptedIssuersLocal = null;
135                trustedCertificateIndexLocal = new TrustedCertificateIndex();
136            } else {
137                rootKeyStoreLocal = null;
138                trustedCertificateStoreLocal = certStore;
139                acceptedIssuersLocal = acceptedIssuers(keyStore);
140                trustedCertificateIndexLocal
141                        = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal));
142            }
143
144        } catch (Exception e) {
145            errLocal = e;
146        }
147
148        if (manager != null) {
149            this.pinManager = manager;
150        } else {
151            try {
152                pinManager = new CertPinManager(trustedCertificateStoreLocal);
153            } catch (PinManagerException e) {
154                throw new SecurityException("Could not initialize CertPinManager", e);
155            }
156        }
157
158        this.rootKeyStore = rootKeyStoreLocal;
159        this.trustedCertificateStore = trustedCertificateStoreLocal;
160        this.validator = validatorLocal;
161        this.factory = factoryLocal;
162        this.trustedCertificateIndex = trustedCertificateIndexLocal;
163        this.acceptedIssuers = acceptedIssuersLocal;
164        this.err = errLocal;
165    }
166
167    private static X509Certificate[] acceptedIssuers(KeyStore ks) {
168        try {
169            // Note that unlike the PKIXParameters code to create a Set of
170            // TrustAnchors from a KeyStore, this version takes from both
171            // TrustedCertificateEntry and PrivateKeyEntry, not just
172            // TrustedCertificateEntry, which is why TrustManagerImpl
173            // cannot just use an PKIXParameters(KeyStore)
174            // constructor.
175
176            // TODO remove duplicates if same cert is found in both a
177            // PrivateKeyEntry and TrustedCertificateEntry
178            List<X509Certificate> trusted = new ArrayList<X509Certificate>();
179            for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) {
180                final String alias = en.nextElement();
181                final X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
182                if (cert != null) {
183                    trusted.add(cert);
184                }
185            }
186            return trusted.toArray(new X509Certificate[trusted.size()]);
187        } catch (KeyStoreException e) {
188            return new X509Certificate[0];
189        }
190    }
191
192    private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) {
193        Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length);
194        for (X509Certificate cert : certs) {
195            trustAnchors.add(new TrustAnchor(cert, null));
196        }
197        return trustAnchors;
198    }
199
200    @Override
201    public void checkClientTrusted(X509Certificate[] chain, String authType)
202            throws CertificateException {
203        checkTrusted(chain, authType, null, true);
204    }
205
206    @Override
207    public void checkServerTrusted(X509Certificate[] chain, String authType)
208            throws CertificateException {
209        checkTrusted(chain, authType, null, false);
210    }
211
212    /**
213     * Validates whether a server is trusted. If hostname is given and non-null it also checks if
214     * chain is pinned appropriately for that host. If null, it does not check for pinned certs.
215     * The return value is a list of the certificates used for making the trust decision.
216     */
217    public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
218                                                    String host) throws CertificateException {
219        return checkTrusted(chain, authType, host, false);
220    }
221
222    public boolean isUserAddedCertificate(X509Certificate cert) {
223        if (trustedCertificateStore == null) {
224            return false;
225        } else {
226            return trustedCertificateStore.isUserAddedCertificate(cert);
227        }
228    }
229
230    /**
231     * Validates whether a server is trusted. If session is given and non-null
232     * it also checks if chain is pinned appropriately for that peer host. If
233     * null, it does not check for pinned certs. The return value is a list of
234     * the certificates used for making the trust decision.
235     */
236    public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
237            SSLSession session) throws CertificateException {
238        return checkTrusted(chain, authType, session.getPeerHost(), false);
239    }
240
241    public void handleTrustStorageUpdate() {
242        if (acceptedIssuers == null) {
243            trustedCertificateIndex.reset();
244        } else {
245            trustedCertificateIndex.reset(trustAnchors(acceptedIssuers));
246        }
247    }
248
249    private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType,
250                                               String host, boolean clientAuth)
251            throws CertificateException {
252        if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
253            throw new IllegalArgumentException("null or zero-length parameter");
254        }
255        if (err != null) {
256            throw new CertificateException(err);
257        }
258
259        // get the cleaned up chain and trust anchor
260        Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one!
261        X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor);
262
263        // add the first trust anchor to the chain, which may be an intermediate
264        List<X509Certificate> wholeChain = new ArrayList<X509Certificate>();
265        wholeChain.addAll(Arrays.asList(newChain));
266        // trustAnchor is actually just a single element
267        for (TrustAnchor trust : trustAnchor) {
268            wholeChain.add(trust.getTrustedCert());
269        }
270
271        // add all the cached certificates from the cert index, avoiding loops
272        // this gives us a full chain from leaf to root, which we use for cert pinning and pass
273        // back out to callers when we return.
274        X509Certificate last = wholeChain.get(wholeChain.size() - 1);
275        while (true) {
276            TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last);
277            // the cachedTrust can be null if there isn't anything in the index or if a user has
278            // trusted a non-self-signed cert.
279            if (cachedTrust == null) {
280                break;
281            }
282
283            // at this point we have a cached trust anchor, but don't know if its one we got from
284            // the server. Extract the cert, compare it to the last element in the chain, and add it
285            // if we haven't seen it before.
286            X509Certificate next = cachedTrust.getTrustedCert();
287            if (next != last) {
288                wholeChain.add(next);
289                last = next;
290            } else {
291                // if next == last then we found a self-signed cert and the chain is done
292                break;
293            }
294        }
295
296        // build the cert path from the array of certs sans trust anchors
297        CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
298
299        if (host != null) {
300            boolean isChainValid = false;
301            try {
302                isChainValid = pinManager.isChainValid(host, wholeChain);
303            } catch (PinManagerException e) {
304                throw new CertificateException(e);
305            }
306            if (!isChainValid) {
307                throw new CertificateException(new CertPathValidatorException(
308                        "Certificate path is not properly pinned.", null, certPath, -1));
309            }
310        }
311
312        if (newChain.length == 0) {
313            // chain was entirely trusted, skip the validator
314            return wholeChain;
315        }
316
317        if (trustAnchor.isEmpty()) {
318            throw new CertificateException(new CertPathValidatorException(
319                    "Trust anchor for certification path not found.", null, certPath, -1));
320        }
321
322        // There's no point in checking trust anchors here, and it will throw off the MD5 check,
323        // so we just hand it the chain without anchors
324        ChainStrengthAnalyzer.check(newChain);
325
326        try {
327            PKIXParameters params = new PKIXParameters(trustAnchor);
328            params.setRevocationEnabled(false);
329            params.addCertPathChecker(new ExtendedKeyUsagePKIXCertPathChecker(clientAuth,
330                                                                              newChain[0]));
331            validator.validate(certPath, params);
332            // Add intermediate CAs to the index to tolerate sites
333            // that assume that the browser will have cached these.
334            // The server certificate is skipped by skipping the
335            // zeroth element of new chain and note that the root CA
336            // will have been removed in
337            // cleanupCertChainAndFindTrustAnchors.  http://b/3404902
338            for (int i = 1; i < newChain.length; i++) {
339                trustedCertificateIndex.index(newChain[i]);
340            }
341        } catch (InvalidAlgorithmParameterException e) {
342            throw new CertificateException(e);
343        } catch (CertPathValidatorException e) {
344            throw new CertificateException(e);
345        }
346
347        return wholeChain;
348    }
349
350    /**
351     * Clean up the certificate chain, returning a cleaned up chain,
352     * which may be a new array instance if elements were removed.
353     * Theoretically, we shouldn't have to do this, but various web
354     * servers in practice are mis-configured to have out-of-order
355     * certificates, expired self-issued root certificate, or CAs with
356     * unsupported signature algorithms such as
357     * md2WithRSAEncryption. This also handles removing old certs
358     * after bridge CA certs.
359     */
360    private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
361                                                                  Set<TrustAnchor> trustAnchors) {
362        X509Certificate[] original = chain;
363
364        // 1. Clean the received certificates chain.
365        int currIndex;
366        // Start with the first certificate in the chain, assuming it
367        // is the leaf certificate (server or client cert).
368        for (currIndex = 0; currIndex < chain.length; currIndex++) {
369            // Walk the chain to find a "subject" matching
370            // the "issuer" of the current certificate. In a properly
371            // ordered chain this should be the next cert and be fast.
372            // If not, we reorder things to be as the validator will
373            // expect.
374            boolean foundNext = false;
375            for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
376                if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
377                    foundNext = true;
378                    // Exchange certificates so that 0 through currIndex + 1 are in proper order
379                    if (nextIndex != currIndex + 1) {
380                        // don't mutuate original chain, which may be directly from an SSLSession
381                        if (chain == original) {
382                            chain = original.clone();
383                        }
384                        X509Certificate tempCertificate = chain[nextIndex];
385                        chain[nextIndex] = chain[currIndex + 1];
386                        chain[currIndex + 1] = tempCertificate;
387                    }
388                    break;
389                }
390            }
391            // If we can't find the next in the chain, just give up
392            // and use what we found so far. This drops unrelated
393            // certificates that have nothing to do with the cert
394            // chain.
395            if (!foundNext) {
396                break;
397            }
398        }
399
400        // 2. Find the trust anchor in the chain, if any
401        int anchorIndex;
402        for (anchorIndex = 0; anchorIndex <= currIndex; anchorIndex++) {
403            // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
404            // This avoids including "bridge" CA certs that added for legacy compatibility.
405            TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]);
406            if (trustAnchor != null) {
407                trustAnchors.add(trustAnchor);
408                break;
409            }
410        }
411
412        // 3. If the chain is now shorter, copy to an appropriately sized array.
413        int chainLength = anchorIndex;
414        X509Certificate[] newChain = ((chainLength == chain.length)
415                                      ? chain
416                                      : Arrays.copyOf(chain, chainLength));
417
418        // 4. If we didn't find a trust anchor earlier, look for one now
419        if (trustAnchors.isEmpty()) {
420            TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]);
421            if (trustAnchor != null) {
422                trustAnchors.add(trustAnchor);
423            }
424        }
425        return newChain;
426    }
427
428    /**
429     * If an EKU extension is present in the end-entity certificate,
430     * it MUST contain an appropriate key usage. For servers, this
431     * includes anyExtendedKeyUsage, serverAuth, or the historical
432     * Server Gated Cryptography options of nsSGC or msSGC.  For
433     * clients, this includes anyExtendedKeyUsage and clientAuth.
434     */
435    private static class ExtendedKeyUsagePKIXCertPathChecker extends PKIXCertPathChecker {
436
437        private static final String EKU_OID = "2.5.29.37";
438
439        private static final String EKU_anyExtendedKeyUsage = "2.5.29.37.0";
440        private static final String EKU_clientAuth = "1.3.6.1.5.5.7.3.2";
441        private static final String EKU_serverAuth = "1.3.6.1.5.5.7.3.1";
442        private static final String EKU_nsSGC = "2.16.840.1.113730.4.1";
443        private static final String EKU_msSGC = "1.3.6.1.4.1.311.10.3.3";
444
445        private static final Set<String> SUPPORTED_EXTENSIONS
446                = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(EKU_OID)));
447
448        private final boolean clientAuth;
449        private final X509Certificate leaf;
450
451        private ExtendedKeyUsagePKIXCertPathChecker(boolean clientAuth, X509Certificate leaf) {
452            this.clientAuth = clientAuth;
453            this.leaf = leaf;
454        }
455
456        @Override
457        public void init(boolean forward) throws CertPathValidatorException {
458        }
459
460        @Override
461        public boolean isForwardCheckingSupported() {
462            return true;
463        }
464
465        @Override
466        public Set<String> getSupportedExtensions() {
467            return SUPPORTED_EXTENSIONS;
468        }
469
470        @Override
471        public void check(Certificate c, Collection<String> unresolvedCritExts)
472                throws CertPathValidatorException {
473            // We only want to validate the EKU on the leaf certificate.
474            if (c != leaf) {
475                return;
476            }
477            List<String> ekuOids;
478            try {
479                ekuOids = leaf.getExtendedKeyUsage();
480            } catch (CertificateParsingException e) {
481                // A malformed EKU is bad news, consider it fatal.
482                throw new CertPathValidatorException(e);
483            }
484            // We are here to check EKU, but there is none.
485            if (ekuOids == null) {
486                return;
487            }
488
489            boolean goodExtendedKeyUsage = false;
490            for (String ekuOid : ekuOids) {
491                // anyExtendedKeyUsage for clients and servers
492                if (ekuOid.equals(EKU_anyExtendedKeyUsage)) {
493                    goodExtendedKeyUsage = true;
494                    break;
495                }
496
497                // clients
498                if (clientAuth) {
499                    if (ekuOid.equals(EKU_clientAuth)) {
500                        goodExtendedKeyUsage = true;
501                        break;
502                    }
503                    continue;
504                }
505
506                // servers
507                if (ekuOid.equals(EKU_serverAuth)) {
508                    goodExtendedKeyUsage = true;
509                    break;
510                }
511                if (ekuOid.equals(EKU_nsSGC)) {
512                    goodExtendedKeyUsage = true;
513                    break;
514                }
515                if (ekuOid.equals(EKU_msSGC)) {
516                    goodExtendedKeyUsage = true;
517                    break;
518                }
519            }
520            if (goodExtendedKeyUsage) {
521                // Mark extendedKeyUsage as resolved if present.
522                unresolvedCritExts.remove(EKU_OID);
523            } else {
524                throw new CertPathValidatorException("End-entity certificate does not have a valid "
525                                                     + "extendedKeyUsage.");
526            }
527        }
528    }
529
530    private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) {
531        TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert);
532        if (trustAnchor != null) {
533            return trustAnchor;
534        }
535        if (trustedCertificateStore == null) {
536            return null;
537        }
538        // we have a KeyStore and the issuer of the last cert in
539        // the chain seems to be missing from the
540        // TrustedCertificateIndex, check the KeyStore for a hit
541        X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
542        if (issuer != null) {
543            return trustedCertificateIndex.index(issuer);
544        }
545        return null;
546    }
547
548    /**
549     * Check the trustedCertificateIndex for the cert to see if it is
550     * already trusted and failing that check the KeyStore if it is
551     * available.
552     */
553    private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
554        TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert);
555        if (trustAnchor != null) {
556            return trustAnchor;
557        }
558        if (trustedCertificateStore == null) {
559            // not trusted and no TrustedCertificateStore to check
560            return null;
561        }
562        // probe KeyStore for a cert. AndroidCAStore stores its
563        // contents hashed by cert subject on the filesystem to make
564        // this faster than scanning all key store entries.
565        X509Certificate systemCert = trustedCertificateStore.getTrustAnchor(cert);
566        if (systemCert != null) {
567            // add new TrustAnchor to params index to avoid
568            // checking filesystem next time around.
569            return trustedCertificateIndex.index(systemCert);
570        }
571        return null;
572    }
573
574    @Override
575    public X509Certificate[] getAcceptedIssuers() {
576        return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore);
577    }
578}
579