X509Util.java revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright 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.annotation.SuppressLint;
8import android.content.BroadcastReceiver;
9import android.content.Context;
10import android.content.Intent;
11import android.content.IntentFilter;
12import android.net.http.X509TrustManagerExtensions;
13import android.os.Build;
14import android.security.KeyChain;
15import android.util.Log;
16import android.util.Pair;
17
18import org.chromium.base.JNINamespace;
19
20import java.io.ByteArrayInputStream;
21import java.io.IOException;
22import java.security.KeyStore;
23import java.security.KeyStoreException;
24import java.security.NoSuchAlgorithmException;
25import java.security.PublicKey;
26import java.security.cert.Certificate;
27import java.security.cert.CertificateException;
28import java.security.cert.CertificateExpiredException;
29import java.security.cert.CertificateFactory;
30import java.security.cert.CertificateNotYetValidException;
31import java.security.cert.X509Certificate;
32import java.util.Arrays;
33import java.util.Collections;
34import java.util.Enumeration;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Set;
38
39import javax.net.ssl.TrustManager;
40import javax.net.ssl.TrustManagerFactory;
41import javax.net.ssl.X509TrustManager;
42import javax.security.auth.x500.X500Principal;
43
44/**
45 * Utility functions for verifying X.509 certificates.
46 */
47@JNINamespace("net")
48public class X509Util {
49
50    private static final String TAG = "X509Util";
51
52    private static final class TrustStorageListener extends BroadcastReceiver {
53        @Override public void onReceive(Context context, Intent intent) {
54            if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
55                try {
56                    reloadDefaultTrustManager();
57                }
58                catch (CertificateException e) {
59                    Log.e(TAG, "Unable to reload the default TrustManager", e);
60                }
61                catch (KeyStoreException e) {
62                    Log.e(TAG, "Unable to reload the default TrustManager", e);
63                }
64                catch (NoSuchAlgorithmException e) {
65                    Log.e(TAG, "Unable to reload the default TrustManager", e);
66                }
67            }
68        }
69    }
70
71    /**
72     * Interface that wraps one of X509TrustManager or
73     * X509TrustManagerExtensions to support platforms before the latter was
74     * added.
75     */
76    private static interface X509TrustManagerImplementation {
77        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
78                                                        String authType,
79                                                        String host) throws CertificateException;
80    }
81
82    private static final class X509TrustManagerIceCreamSandwich implements
83            X509TrustManagerImplementation {
84        private final X509TrustManager mTrustManager;
85
86        public X509TrustManagerIceCreamSandwich(X509TrustManager trustManager) {
87            mTrustManager = trustManager;
88        }
89
90        @Override
91        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
92                                                        String authType,
93                                                        String host) throws CertificateException {
94            mTrustManager.checkServerTrusted(chain, authType);
95            return Collections.<X509Certificate>emptyList();
96        }
97    }
98
99    private static final class X509TrustManagerJellyBean implements X509TrustManagerImplementation {
100        private final X509TrustManagerExtensions mTrustManagerExtensions;
101
102        @SuppressLint("NewApi")
103        public X509TrustManagerJellyBean(X509TrustManager trustManager) {
104            mTrustManagerExtensions = new X509TrustManagerExtensions(trustManager);
105        }
106
107        @Override
108        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
109                                                        String authType,
110                                                        String host) throws CertificateException {
111            return mTrustManagerExtensions.checkServerTrusted(chain, authType, host);
112        }
113    }
114
115    private static CertificateFactory sCertificateFactory;
116
117    private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
118    private static final String OID_ANY_EKU = "2.5.29.37.0";
119    // Server-Gated Cryptography (necessary to support a few legacy issuers):
120    //    Netscape:
121    private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
122    //    Microsoft:
123    private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
124
125    /**
126     * Trust manager backed up by the read-only system certificate store.
127     */
128    private static X509TrustManagerImplementation sDefaultTrustManager;
129
130    /**
131     * BroadcastReceiver that listens to change in the system keystore to invalidate certificate
132     * caches.
133     */
134    private static TrustStorageListener sTrustStorageListener;
135
136    /**
137     * Trust manager backed up by a custom certificate store. We need such manager to plant test
138     * root CA to the trust store in testing.
139     */
140    private static X509TrustManagerImplementation sTestTrustManager;
141    private static KeyStore sTestKeyStore;
142
143    /**
144     * Hash set of the subject and public key of system roots. This is used to
145     * determine whether a chain ends at a well-known root or not.
146     *
147     * Querying the system KeyStore for the root directly doesn't work as the
148     * root of the verified chain may be the server's version of a root rather
149     * than the system one. For instance, the server may send a certificate
150     * signed by another CA, while the system store contains a self-signed root
151     * with the same subject and SPKI.  The chain will terminate at that root
152     * but X509TrustManagerExtensions will return the server's version.
153     */
154    private static Set<Pair<X500Principal, PublicKey>> sSystemTrustRoots;
155
156    /**
157     * True if the system trust roots were initialized. (sSystemTrustRoots may
158     * still be null if system trust roots cannot be distinguished from
159     * user-installed ones.)
160     */
161    private static boolean sLoadedSystemTrustRoots;
162
163    /**
164     * Lock object used to synchronize all calls that modify or depend on the trust managers.
165     */
166    private static final Object sLock = new Object();
167
168    /**
169     * Allow disabling registering the observer and recording histograms for the certificate
170     * changes. Net unit tests do not load native libraries which prevent this to succeed. Moreover,
171     * the system does not allow to interact with the certificate store without user interaction.
172     */
173    private static boolean sDisableNativeCodeForTest = false;
174
175    /**
176     * Ensures that the trust managers and certificate factory are initialized.
177     */
178    private static void ensureInitialized() throws CertificateException,
179            KeyStoreException, NoSuchAlgorithmException {
180        synchronized (sLock) {
181            if (sCertificateFactory == null) {
182                sCertificateFactory = CertificateFactory.getInstance("X.509");
183            }
184            if (sDefaultTrustManager == null) {
185                sDefaultTrustManager = X509Util.createTrustManager(null);
186            }
187            if (!sLoadedSystemTrustRoots) {
188                try {
189                    sSystemTrustRoots = buildSystemTrustRootSet();
190                } catch (KeyStoreException e) {
191                    // If the device does not have an "AndroidCAStore" KeyStore, don't make the
192                    // failure fatal. Instead default conservatively to setting isIssuedByKnownRoot
193                    // to false everywhere.
194                    Log.w(TAG, "Could not load system trust root set", e);
195                }
196                if (!sDisableNativeCodeForTest)
197                    nativeRecordCertVerifyCapabilitiesHistogram(sSystemTrustRoots != null);
198                sLoadedSystemTrustRoots = true;
199            }
200            if (sTestKeyStore == null) {
201                sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
202                try {
203                    sTestKeyStore.load(null);
204                } catch (IOException e) {
205                    // No IO operation is attempted.
206                }
207            }
208            if (sTestTrustManager == null) {
209                sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
210            }
211            if (!sDisableNativeCodeForTest && sTrustStorageListener == null) {
212                sTrustStorageListener = new TrustStorageListener();
213                nativeGetApplicationContext().registerReceiver(sTrustStorageListener,
214                        new IntentFilter(KeyChain.ACTION_STORAGE_CHANGED));
215            }
216        }
217    }
218
219    private static Set<Pair<X500Principal, PublicKey>> buildSystemTrustRootSet() throws
220            CertificateException, KeyStoreException, NoSuchAlgorithmException {
221        // Load the Android CA store.
222        KeyStore systemKeyStore = KeyStore.getInstance("AndroidCAStore");
223        try {
224            systemKeyStore.load(null);
225        } catch (IOException e) {
226            // No IO operation is attempted.
227        }
228
229        // System trust roots have prefix of "system:".
230        Set<Pair<X500Principal, PublicKey>> roots = new HashSet<Pair<X500Principal, PublicKey>>();
231        Enumeration<String> aliases = systemKeyStore.aliases();
232        while (aliases.hasMoreElements()) {
233            String alias = aliases.nextElement();
234            if (!alias.startsWith("system:"))
235                continue;
236            Certificate cert = systemKeyStore.getCertificate(alias);
237            if (cert != null && cert instanceof X509Certificate) {
238                X509Certificate x509Cert = (X509Certificate)cert;
239                roots.add(new Pair<X500Principal, PublicKey>(x509Cert.getSubjectX500Principal(),
240                                                             x509Cert.getPublicKey()));
241            }
242        }
243        return roots;
244    }
245
246    /**
247     * Creates a X509TrustManagerImplementation backed up by the given key
248     * store. When null is passed as a key store, system default trust store is
249     * used.
250     * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
251     */
252    private static X509TrustManagerImplementation createTrustManager(KeyStore keyStore) throws
253            KeyStoreException, NoSuchAlgorithmException {
254        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
255        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
256        tmf.init(keyStore);
257
258        for (TrustManager tm : tmf.getTrustManagers()) {
259            if (tm instanceof X509TrustManager) {
260                try {
261                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
262                        return new X509TrustManagerJellyBean((X509TrustManager) tm);
263                    } else {
264                        return new X509TrustManagerIceCreamSandwich((X509TrustManager) tm);
265                    }
266                } catch (IllegalArgumentException e) {
267                    Log.e(TAG, "Error creating trust manager: " + e);
268                }
269            }
270        }
271        return null;
272    }
273
274    /**
275     * After each modification of test key store, trust manager has to be generated again.
276     */
277    private static void reloadTestTrustManager() throws KeyStoreException,
278            NoSuchAlgorithmException {
279        sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
280    }
281
282    /**
283     * After each modification by the system of the key store, trust manager has to be regenerated.
284     */
285    private static void reloadDefaultTrustManager() throws KeyStoreException,
286            NoSuchAlgorithmException, CertificateException {
287        sDefaultTrustManager = null;
288        sSystemTrustRoots = null;
289        sLoadedSystemTrustRoots = false;
290        nativeNotifyKeyChainChanged();
291        ensureInitialized();
292    }
293
294    /**
295     * Convert a DER encoded certificate to an X509Certificate.
296     */
297    public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
298            CertificateException, KeyStoreException, NoSuchAlgorithmException {
299        ensureInitialized();
300        return (X509Certificate) sCertificateFactory.generateCertificate(
301                new ByteArrayInputStream(derBytes));
302    }
303
304    public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
305            KeyStoreException, NoSuchAlgorithmException {
306        ensureInitialized();
307        X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
308        synchronized (sLock) {
309            sTestKeyStore.setCertificateEntry(
310                    "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
311            reloadTestTrustManager();
312        }
313    }
314
315    public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
316            CertificateException, KeyStoreException {
317        ensureInitialized();
318        synchronized (sLock) {
319            try {
320                sTestKeyStore.load(null);
321                reloadTestTrustManager();
322            } catch (IOException e) {
323                // No IO operation is attempted.
324            }
325        }
326    }
327
328    /**
329     * If an EKU extension is present in the end-entity certificate, it MUST contain either the
330     * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
331     *
332     * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
333     * OIDs for web server certificates.
334     *
335     * TODO(palmer): This can be removed after the equivalent change is made to the Android default
336     * TrustManager and that change is shipped to a large majority of Android users.
337     */
338    static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
339        List<String> ekuOids;
340        try {
341            ekuOids = certificate.getExtendedKeyUsage();
342        } catch (NullPointerException e) {
343            // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
344            // happens when the EKU extension data is malformed so return false here.
345            // See http://crbug.com/233610
346            return false;
347        }
348        if (ekuOids == null)
349            return true;
350
351        for (String ekuOid : ekuOids) {
352            if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
353                ekuOid.equals(OID_ANY_EKU) ||
354                ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
355                ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
356                return true;
357            }
358        }
359
360        return false;
361    }
362
363    public static AndroidCertVerifyResult verifyServerCertificates(byte[][] certChain,
364                                                                   String authType,
365                                                                   String host)
366            throws KeyStoreException, NoSuchAlgorithmException {
367        if (certChain == null || certChain.length == 0 || certChain[0] == null) {
368            throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
369                    "chain passed as |certChain|. |certChain|=" + Arrays.deepToString(certChain));
370        }
371
372
373        try {
374            ensureInitialized();
375        } catch (CertificateException e) {
376            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
377        }
378
379        X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
380        try {
381            for (int i = 0; i < certChain.length; ++i) {
382                serverCertificates[i] = createCertificateFromBytes(certChain[i]);
383            }
384        } catch (CertificateException e) {
385            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_UNABLE_TO_PARSE);
386        }
387
388        // Expired and not yet valid certificates would be rejected by the trust managers, but the
389        // trust managers report all certificate errors using the general CertificateException. In
390        // order to get more granular error information, cert validity time range is being checked
391        // separately.
392        try {
393            serverCertificates[0].checkValidity();
394            if (!verifyKeyUsage(serverCertificates[0])) {
395                return new AndroidCertVerifyResult(
396                        CertVerifyStatusAndroid.VERIFY_INCORRECT_KEY_USAGE);
397            }
398        } catch (CertificateExpiredException e) {
399            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_EXPIRED);
400        } catch (CertificateNotYetValidException e) {
401            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_NOT_YET_VALID);
402        } catch (CertificateException e) {
403            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
404        }
405
406        synchronized (sLock) {
407            List<X509Certificate> verifiedChain;
408            try {
409                verifiedChain = sDefaultTrustManager.checkServerTrusted(serverCertificates,
410                                                                        authType, host);
411            } catch (CertificateException eDefaultManager) {
412                try {
413                    verifiedChain = sTestTrustManager.checkServerTrusted(serverCertificates,
414                                                                         authType, host);
415                } catch (CertificateException eTestManager) {
416                    // Neither of the trust managers confirms the validity of the certificate chain,
417                    // log the error message returned by the system trust manager.
418                    Log.i(TAG, "Failed to validate the certificate chain, error: " +
419                              eDefaultManager.getMessage());
420                    return new AndroidCertVerifyResult(
421                            CertVerifyStatusAndroid.VERIFY_NO_TRUSTED_ROOT);
422                }
423            }
424
425            boolean isIssuedByKnownRoot = false;
426            if (sSystemTrustRoots != null && verifiedChain.size() > 0) {
427                X509Certificate root = verifiedChain.get(verifiedChain.size() - 1);
428                isIssuedByKnownRoot = sSystemTrustRoots.contains(
429                        new Pair<X500Principal, PublicKey>(root.getSubjectX500Principal(),
430                                                           root.getPublicKey()));
431            }
432
433            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_OK,
434                                               isIssuedByKnownRoot, verifiedChain);
435        }
436    }
437
438    public static void setDisableNativeCodeForTest(boolean disabled) {
439        sDisableNativeCodeForTest = disabled;
440    }
441    /**
442     * Notify the native net::CertDatabase instance that the system database has been updated.
443     */
444    private static native void nativeNotifyKeyChainChanged();
445
446    /**
447     * Record histograms on the platform's certificate verification capabilities.
448     */
449    private static native void nativeRecordCertVerifyCapabilitiesHistogram(
450        boolean foundSystemTrustRoots);
451
452    /**
453     * Returns the application context.
454     */
455    private static native Context nativeGetApplicationContext();
456
457}
458