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