TrustManagerImpl.java revision 3258b52429c7768ea91bda93c5a15257cdd390e5
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;
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 */
47public final class TrustManagerImpl implements X509TrustManager {
48
49    private final CertPathValidator validator;
50
51    private final IndexedPKIXParameters params;
52
53    private final X509Certificate[] acceptedIssuers;
54
55    private final Exception err;
56
57    private final CertificateFactory factory;
58
59    public final IndexedPKIXParameters getIndexedPKIXParameters() {
60        return params;
61    }
62
63    /**
64     * Creates trust manager implementation
65     *
66     * @param ks
67     */
68    public TrustManagerImpl(KeyStore ks) {
69        CertPathValidator validatorLocal = null;
70        CertificateFactory factoryLocal = null;
71        IndexedPKIXParameters paramsLocal = null;
72        X509Certificate[] acceptedIssuersLocal = null;
73        Exception errLocal = null;
74        try {
75            validatorLocal = CertPathValidator.getInstance("PKIX");
76            factoryLocal = CertificateFactory.getInstance("X509");
77            acceptedIssuersLocal = acceptedIssuers(ks);
78            paramsLocal = new IndexedPKIXParameters(trustAnchors(acceptedIssuersLocal));
79            paramsLocal.setRevocationEnabled(false);
80        } catch (Exception e) {
81            errLocal = e;
82        }
83        this.validator = validatorLocal;
84        this.factory = factoryLocal;
85        this.params = paramsLocal;
86        this.acceptedIssuers = (acceptedIssuersLocal != null
87                                ? acceptedIssuersLocal
88                                : new X509Certificate[0]);
89        this.err = errLocal;
90    }
91
92    private static X509Certificate[] acceptedIssuers(KeyStore ks) throws KeyStoreException {
93        // Note that unlike the PKIXParameters code to create a Set of
94        // TrustAnchors from a KeyStore, this version takes from both
95        // TrustedCertificateEntry and PrivateKeyEntry, not just
96        // TrustedCertificateEntry, which is why TrustManagerImpl
97        // cannot just use an PKIXParameters(KeyStore)
98        // constructor.
99
100        // TODO remove duplicates if same cert is found in both a
101        // PrivateKeyEntry and TrustedCertificateEntry
102        List<X509Certificate> trusted = new ArrayList<X509Certificate>();
103        for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) {
104            final String alias = en.nextElement();
105            final X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
106            if (cert != null) {
107                trusted.add(cert);
108            }
109        }
110        return trusted.toArray(new X509Certificate[trusted.size()]);
111    }
112
113    private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) {
114        Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length);
115        for (X509Certificate cert : certs) {
116            trustAnchors.add(new TrustAnchor(cert, null));
117        }
118        return trustAnchors;
119    }
120
121    /**
122     * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],
123     *      String)
124     */
125    @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
126            throws CertificateException {
127        checkTrusted(chain, authType);
128    }
129
130    /**
131     * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],
132     *      String)
133     */
134    @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
135            throws CertificateException {
136        checkTrusted(chain, authType);
137    }
138
139    private void checkTrusted(X509Certificate[] chain, String authType)
140            throws CertificateException {
141        if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
142            throw new IllegalArgumentException("null or zero-length parameter");
143        }
144        if (err != null) {
145            throw new CertificateException(err);
146        }
147
148        X509Certificate[] newChain = cleanupCertChain(chain);
149        if (newChain.length == 0) {
150            // chain was entirely trusted, skip the validator
151            return;
152        }
153        CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
154        if (!Arrays.equals(chain[0].getEncoded(),
155                           certPath.getCertificates().get(0).getEncoded())) {
156            // Sanity check failed (shouldn't ever happen, but we
157            // are using pretty remote code)
158            throw new CertificateException("Certificate chain error");
159        }
160        try {
161            validator.validate(certPath, params);
162            // Add intermediate CAs to the index to tolerate sites
163            // that assume that the browser will have cached these.
164            // The server certificate is skipped by skipping the
165            // zeroth element of new chain and note that the root CA
166            // will have been removed in cleanupCertChain.
167            // http://b/3404902
168            for (int i = 1; i < newChain.length; i++) {
169                params.index(new TrustAnchor(newChain[i], null));
170            }
171        } catch (InvalidAlgorithmParameterException e) {
172            throw new CertificateException(e);
173        } catch (CertPathValidatorException e) {
174            throw new CertificateException(e);
175        }
176    }
177
178    /**
179     * Clean up the certificate chain, returning a cleaned up chain,
180     * which may be a new array instance if elements were removed.
181     * Theoretically, we shouldn't have to do this, but various web
182     * servers in practice are mis-configured to have out-of-order
183     * certificates, expired self-issued root certificate, or CAs with
184     * unsupported signature algorithms such as
185     * md2WithRSAEncryption. This also handles removing old certs
186     * after bridge CA certs.
187     */
188    private X509Certificate[] cleanupCertChain(X509Certificate[] chain) {
189        X509Certificate[] original = chain;
190
191        // 1. Clean the received certificates chain.
192        int currIndex;
193        // Start with the first certificate in the chain, assuming it
194        // is the leaf certificate (server or client cert).
195        for (currIndex = 0; currIndex < chain.length; currIndex++) {
196            // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
197            // This avoids including "bridge" CA certs that added for legacy compatability.
198            if (params.isTrustAnchor(chain[currIndex])) {
199                currIndex--;
200                break;
201            }
202            // Walk the rest of the chain to find a "subject" matching
203            // the "issuer" of the current certificate. In a properly
204            // order chain this should be the next cert and be fast.
205            // If not, we reorder things to be as the validator will
206            // expect.
207            boolean foundNext = false;
208            for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
209                if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
210                    foundNext = true;
211                    // Exchange certificates so that 0 through currIndex + 1 are in proper order
212                    if (nextIndex != currIndex + 1) {
213                        // don't mutuate original chain, which may be directly from an SSLSession
214                        if (chain == original) {
215                            chain = original.clone();
216                        }
217                        X509Certificate tempCertificate = chain[nextIndex];
218                        chain[nextIndex] = chain[currIndex + 1];
219                        chain[currIndex + 1] = tempCertificate;
220                    }
221                    break;
222                }
223            }
224            // If we can't find the next in the chain, just give up
225            // and use what we found so far. This drops unrelated
226            // certificates that have nothing to do with the cert
227            // chain.
228            if (!foundNext) {
229                break;
230            }
231        }
232
233        // 2. If the chain is now shorter, copy to an appropriately sized array.
234        int chainLength = currIndex + 1;
235        if (chainLength == chain.length) {
236            return chain;
237        }
238        return Arrays.copyOf(chain, chainLength);
239    }
240
241    /**
242     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
243     */
244    @Override public X509Certificate[] getAcceptedIssuers() {
245        return acceptedIssuers.clone();
246    }
247}
248