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