X509Util.java revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.net;
6
7import android.util.Log;
8
9import org.chromium.net.CertVerifyResultAndroid;
10
11import java.io.ByteArrayInputStream;
12import java.io.IOException;
13import java.security.KeyStore;
14import java.security.KeyStoreException;
15import java.security.NoSuchAlgorithmException;
16import java.security.cert.CertificateException;
17import java.security.cert.CertificateExpiredException;
18import java.security.cert.CertificateNotYetValidException;
19import java.security.cert.CertificateFactory;
20import java.security.cert.CertificateParsingException;
21import java.security.cert.X509Certificate;
22import java.util.List;
23
24import javax.net.ssl.TrustManager;
25import javax.net.ssl.TrustManagerFactory;
26import javax.net.ssl.X509TrustManager;
27
28public class X509Util {
29
30    private static final String TAG = X509Util.class.getName();
31
32    private static CertificateFactory sCertificateFactory;
33
34    private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
35    private static final String OID_ANY_EKU = "2.5.29.37.0";
36    // Server-Gated Cryptography (necessary to support a few legacy issuers):
37    //    Netscape:
38    private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
39    //    Microsoft:
40    private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
41
42    /**
43     * Trust manager backed up by the read-only system certificate store.
44     */
45    private static X509TrustManager sDefaultTrustManager;
46
47    /**
48     * Trust manager backed up by a custom certificate store. We need such manager to plant test
49     * root CA to the trust store in testing.
50     */
51    private static X509TrustManager sTestTrustManager;
52    private static KeyStore sTestKeyStore;
53
54    /**
55     * Lock object used to synchronize all calls that modify or depend on the trust managers.
56     */
57    private static final Object sLock = new Object();
58
59    /**
60     * Ensures that the trust managers and certificate factory are initialized.
61     */
62    private static void ensureInitialized() throws CertificateException,
63            KeyStoreException, NoSuchAlgorithmException {
64        synchronized(sLock) {
65            if (sCertificateFactory == null) {
66                sCertificateFactory = CertificateFactory.getInstance("X.509");
67            }
68            if (sDefaultTrustManager == null) {
69                sDefaultTrustManager = X509Util.createTrustManager(null);
70            }
71            if (sTestKeyStore == null) {
72                sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
73                try {
74                    sTestKeyStore.load(null);
75                } catch(IOException e) {}  // No IO operation is attempted.
76            }
77            if (sTestTrustManager == null) {
78                sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
79            }
80        }
81    }
82
83    /**
84     * Creates a X509TrustManager backed up by the given key store. When null is passed as a key
85     * store, system default trust store is used.
86     * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
87     */
88    private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
89            NoSuchAlgorithmException {
90        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
91        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
92        tmf.init(keyStore);
93
94        for (TrustManager tm : tmf.getTrustManagers()) {
95            if (tm instanceof X509TrustManager) {
96                return (X509TrustManager) tm;
97            }
98        }
99        return null;
100    }
101
102    /**
103     * After each modification of test key store, trust manager has to be generated again.
104     */
105    private static void reloadTestTrustManager() throws KeyStoreException,
106            NoSuchAlgorithmException {
107        sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
108    }
109
110    /**
111     * Convert a DER encoded certificate to an X509Certificate.
112     */
113    public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
114            CertificateException, KeyStoreException, NoSuchAlgorithmException {
115        ensureInitialized();
116        return (X509Certificate) sCertificateFactory.generateCertificate(
117                new ByteArrayInputStream(derBytes));
118    }
119
120    public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
121            KeyStoreException, NoSuchAlgorithmException {
122        ensureInitialized();
123        X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
124        synchronized (sLock) {
125            sTestKeyStore.setCertificateEntry(
126                    "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
127            reloadTestTrustManager();
128        }
129    }
130
131    public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
132            CertificateException, KeyStoreException {
133        ensureInitialized();
134        synchronized (sLock) {
135            try {
136                sTestKeyStore.load(null);
137                reloadTestTrustManager();
138            } catch (IOException e) {}  // No IO operation is attempted.
139        }
140    }
141
142    /**
143     * If an EKU extension is present in the end-entity certificate, it MUST contain either the
144     * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
145     *
146     * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
147     * OIDs for web server certificates.
148     *
149     * TODO(palmer): This can be removed after the equivalent change is made to the Android default
150     * TrustManager and that change is shipped to a large majority of Android users.
151     */
152    static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
153        List<String> ekuOids;
154        try {
155            ekuOids = certificate.getExtendedKeyUsage();
156        } catch (NullPointerException e) {
157            // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
158            // happens when the EKU extension data is malformed so return false here.
159            // See http://crbug.com/233610
160            return false;
161        }
162        if (ekuOids == null)
163            return true;
164
165        for (String ekuOid : ekuOids) {
166            if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
167                ekuOid.equals(OID_ANY_EKU) ||
168                ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
169                ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
170                return true;
171            }
172        }
173
174        return false;
175    }
176
177    public static int verifyServerCertificates(byte[][] certChain, String authType)
178            throws KeyStoreException, NoSuchAlgorithmException {
179        if (certChain == null || certChain.length == 0 || certChain[0] == null) {
180            throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
181                    "chain passed as |certChain|. |certChain|=" + certChain);
182        }
183
184        try {
185            ensureInitialized();
186        } catch (CertificateException e) {
187            return CertVerifyResultAndroid.VERIFY_FAILED;
188        }
189
190        X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
191        try {
192            for (int i = 0; i < certChain.length; ++i) {
193                serverCertificates[i] = createCertificateFromBytes(certChain[i]);
194            }
195        } catch (CertificateException e) {
196            return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
197        }
198
199        // Expired and not yet valid certificates would be rejected by the trust managers, but the
200        // trust managers report all certificate errors using the general CertificateException. In
201        // order to get more granular error information, cert validity time range is being checked
202        // separately.
203        try {
204            serverCertificates[0].checkValidity();
205            if (!verifyKeyUsage(serverCertificates[0]))
206                return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE;
207        } catch (CertificateExpiredException e) {
208            return CertVerifyResultAndroid.VERIFY_EXPIRED;
209        } catch (CertificateNotYetValidException e) {
210            return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
211        } catch (CertificateException e) {
212            return CertVerifyResultAndroid.VERIFY_FAILED;
213        }
214
215        synchronized (sLock) {
216            try {
217                sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
218                return CertVerifyResultAndroid.VERIFY_OK;
219            } catch (CertificateException eDefaultManager) {
220                try {
221                    sTestTrustManager.checkServerTrusted(serverCertificates, authType);
222                    return CertVerifyResultAndroid.VERIFY_OK;
223                } catch (CertificateException eTestManager) {
224                    // Neither of the trust managers confirms the validity of the certificate chain,
225                    // log the error message returned by the system trust manager.
226                    Log.i(TAG, "Failed to validate the certificate chain, error: " +
227                              eDefaultManager.getMessage());
228                    return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
229                }
230            }
231        }
232    }
233}
234