X509Util.java revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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.content.BroadcastReceiver;
8import android.content.Context;
9import android.content.Intent;
10import android.content.IntentFilter;
11import android.security.KeyChain;
12import android.util.Log;
13
14import org.chromium.base.JNINamespace;
15import org.chromium.net.CertVerifyResultAndroid;
16
17import java.io.ByteArrayInputStream;
18import java.io.IOException;
19import java.security.KeyStore;
20import java.security.KeyStoreException;
21import java.security.NoSuchAlgorithmException;
22import java.security.cert.CertificateException;
23import java.security.cert.CertificateExpiredException;
24import java.security.cert.CertificateNotYetValidException;
25import java.security.cert.CertificateFactory;
26import java.security.cert.CertificateParsingException;
27import java.security.cert.X509Certificate;
28import java.util.List;
29
30import javax.net.ssl.TrustManager;
31import javax.net.ssl.TrustManagerFactory;
32import javax.net.ssl.X509TrustManager;
33
34@JNINamespace("net")
35public class X509Util {
36
37    private static final String TAG = "X509Util";
38
39    public static final class TrustStorageListener extends BroadcastReceiver {
40        @Override public void onReceive(Context context, Intent intent) {
41            if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
42                try {
43                    reloadDefaultTrustManager();
44                }
45                catch (CertificateException e) {
46                    Log.e(TAG, "Unable to reload the default TrustManager", e);
47                }
48                catch (KeyStoreException e) {
49                    Log.e(TAG, "Unable to reload the default TrustManager", e);
50                }
51                catch (NoSuchAlgorithmException e) {
52                    Log.e(TAG, "Unable to reload the default TrustManager", e);
53                }
54            }
55        }
56    }
57
58    private static CertificateFactory sCertificateFactory;
59
60    private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
61    private static final String OID_ANY_EKU = "2.5.29.37.0";
62    // Server-Gated Cryptography (necessary to support a few legacy issuers):
63    //    Netscape:
64    private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
65    //    Microsoft:
66    private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
67
68    /**
69     * Trust manager backed up by the read-only system certificate store.
70     */
71    private static X509TrustManager sDefaultTrustManager;
72
73    /**
74     * BroadcastReceiver that listens to change in the system keystore to invalidate certificate
75     * caches.
76     */
77    private static TrustStorageListener sTrustStorageListener;
78
79    /**
80     * Trust manager backed up by a custom certificate store. We need such manager to plant test
81     * root CA to the trust store in testing.
82     */
83    private static X509TrustManager sTestTrustManager;
84    private static KeyStore sTestKeyStore;
85
86    /**
87     * Lock object used to synchronize all calls that modify or depend on the trust managers.
88     */
89    private static final Object sLock = new Object();
90
91    /*
92     * Allow disabling registering the observer for the certificat changes. Net unit tests do not
93     * load native libraries which prevent this to succeed. Moreover, the system does not allow to
94     * interact with the certificate store without user interaction.
95     */
96    private static boolean sDisableCertificateObservationForTest = false;
97
98    /**
99     * Ensures that the trust managers and certificate factory are initialized.
100     */
101    private static void ensureInitialized() throws CertificateException,
102            KeyStoreException, NoSuchAlgorithmException {
103        synchronized(sLock) {
104            if (sCertificateFactory == null) {
105                sCertificateFactory = CertificateFactory.getInstance("X.509");
106            }
107            if (sDefaultTrustManager == null) {
108                sDefaultTrustManager = X509Util.createTrustManager(null);
109            }
110            if (sTestKeyStore == null) {
111                sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
112                try {
113                    sTestKeyStore.load(null);
114                } catch(IOException e) {}  // No IO operation is attempted.
115            }
116            if (sTestTrustManager == null) {
117                sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
118            }
119            if (!sDisableCertificateObservationForTest &&
120                    sTrustStorageListener == null) {
121                sTrustStorageListener = new TrustStorageListener();
122                nativeGetApplicationContext().registerReceiver(sTrustStorageListener,
123                        new IntentFilter(KeyChain.ACTION_STORAGE_CHANGED));
124            }
125        }
126    }
127
128    /**
129     * Creates a X509TrustManager backed up by the given key store. When null is passed as a key
130     * store, system default trust store is used.
131     * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
132     */
133    private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
134            NoSuchAlgorithmException {
135        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
136        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
137        tmf.init(keyStore);
138
139        for (TrustManager tm : tmf.getTrustManagers()) {
140            if (tm instanceof X509TrustManager) {
141                return (X509TrustManager) tm;
142            }
143        }
144        return null;
145    }
146
147    /**
148     * After each modification of test key store, trust manager has to be generated again.
149     */
150    private static void reloadTestTrustManager() throws KeyStoreException,
151            NoSuchAlgorithmException {
152        sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
153    }
154
155    /**
156     * After each modification by the system of the key store, trust manager has to be regenerated.
157     */
158    private static void reloadDefaultTrustManager() throws KeyStoreException,
159            NoSuchAlgorithmException, CertificateException {
160        sDefaultTrustManager = null;
161        nativeNotifyKeyChainChanged();
162        ensureInitialized();
163    }
164
165    /**
166     * Convert a DER encoded certificate to an X509Certificate.
167     */
168    public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
169            CertificateException, KeyStoreException, NoSuchAlgorithmException {
170        ensureInitialized();
171        return (X509Certificate) sCertificateFactory.generateCertificate(
172                new ByteArrayInputStream(derBytes));
173    }
174
175    public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
176            KeyStoreException, NoSuchAlgorithmException {
177        ensureInitialized();
178        X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
179        synchronized (sLock) {
180            sTestKeyStore.setCertificateEntry(
181                    "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
182            reloadTestTrustManager();
183        }
184    }
185
186    public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
187            CertificateException, KeyStoreException {
188        ensureInitialized();
189        synchronized (sLock) {
190            try {
191                sTestKeyStore.load(null);
192                reloadTestTrustManager();
193            } catch (IOException e) {}  // No IO operation is attempted.
194        }
195    }
196
197    /**
198     * If an EKU extension is present in the end-entity certificate, it MUST contain either the
199     * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
200     *
201     * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
202     * OIDs for web server certificates.
203     *
204     * TODO(palmer): This can be removed after the equivalent change is made to the Android default
205     * TrustManager and that change is shipped to a large majority of Android users.
206     */
207    static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
208        List<String> ekuOids;
209        try {
210            ekuOids = certificate.getExtendedKeyUsage();
211        } catch (NullPointerException e) {
212            // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
213            // happens when the EKU extension data is malformed so return false here.
214            // See http://crbug.com/233610
215            return false;
216        }
217        if (ekuOids == null)
218            return true;
219
220        for (String ekuOid : ekuOids) {
221            if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
222                ekuOid.equals(OID_ANY_EKU) ||
223                ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
224                ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
225                return true;
226            }
227        }
228
229        return false;
230    }
231
232    public static int verifyServerCertificates(byte[][] certChain, String authType)
233            throws KeyStoreException, NoSuchAlgorithmException {
234        if (certChain == null || certChain.length == 0 || certChain[0] == null) {
235            throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
236                    "chain passed as |certChain|. |certChain|=" + certChain);
237        }
238
239        try {
240            ensureInitialized();
241        } catch (CertificateException e) {
242            return CertVerifyResultAndroid.VERIFY_FAILED;
243        }
244
245        X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
246        try {
247            for (int i = 0; i < certChain.length; ++i) {
248                serverCertificates[i] = createCertificateFromBytes(certChain[i]);
249            }
250        } catch (CertificateException e) {
251            return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
252        }
253
254        // Expired and not yet valid certificates would be rejected by the trust managers, but the
255        // trust managers report all certificate errors using the general CertificateException. In
256        // order to get more granular error information, cert validity time range is being checked
257        // separately.
258        try {
259            serverCertificates[0].checkValidity();
260            if (!verifyKeyUsage(serverCertificates[0]))
261                return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE;
262        } catch (CertificateExpiredException e) {
263            return CertVerifyResultAndroid.VERIFY_EXPIRED;
264        } catch (CertificateNotYetValidException e) {
265            return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
266        } catch (CertificateException e) {
267            return CertVerifyResultAndroid.VERIFY_FAILED;
268        }
269
270        synchronized (sLock) {
271            try {
272                sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
273                return CertVerifyResultAndroid.VERIFY_OK;
274            } catch (CertificateException eDefaultManager) {
275                try {
276                    sTestTrustManager.checkServerTrusted(serverCertificates, authType);
277                    return CertVerifyResultAndroid.VERIFY_OK;
278                } catch (CertificateException eTestManager) {
279                    // Neither of the trust managers confirms the validity of the certificate chain,
280                    // log the error message returned by the system trust manager.
281                    Log.i(TAG, "Failed to validate the certificate chain, error: " +
282                              eDefaultManager.getMessage());
283                    return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
284                }
285            }
286        }
287    }
288
289    public static void setDisableCertificateObservationForTest(boolean disabled) {
290        sDisableCertificateObservationForTest = disabled;
291    }
292    /**
293     * Notify the native net::CertDatabase instance that the system database has been updated.
294     */
295    private static native void nativeNotifyKeyChainChanged();
296
297    /**
298     * Returns the application context.
299     */
300    private static native Context nativeGetApplicationContext();
301
302}
303