X509Util.java revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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.File;
22import java.io.IOException;
23import java.security.KeyStore;
24import java.security.KeyStoreException;
25import java.security.MessageDigest;
26import java.security.NoSuchAlgorithmException;
27import java.security.PublicKey;
28import java.security.cert.Certificate;
29import java.security.cert.CertificateException;
30import java.security.cert.CertificateExpiredException;
31import java.security.cert.CertificateFactory;
32import java.security.cert.CertificateNotYetValidException;
33import java.security.cert.X509Certificate;
34import java.util.Arrays;
35import java.util.Collections;
36import java.util.HashSet;
37import java.util.List;
38import java.util.Set;
39
40import javax.net.ssl.TrustManager;
41import javax.net.ssl.TrustManagerFactory;
42import javax.net.ssl.X509TrustManager;
43import javax.security.auth.x500.X500Principal;
44
45/**
46 * Utility functions for verifying X.509 certificates.
47 */
48@JNINamespace("net")
49public class X509Util {
50
51    private static final String TAG = "X509Util";
52
53    private static final class TrustStorageListener extends BroadcastReceiver {
54        @Override public void onReceive(Context context, Intent intent) {
55            if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
56                try {
57                    reloadDefaultTrustManager();
58                }
59                catch (CertificateException e) {
60                    Log.e(TAG, "Unable to reload the default TrustManager", e);
61                }
62                catch (KeyStoreException e) {
63                    Log.e(TAG, "Unable to reload the default TrustManager", e);
64                }
65                catch (NoSuchAlgorithmException e) {
66                    Log.e(TAG, "Unable to reload the default TrustManager", e);
67                }
68            }
69        }
70    }
71
72    /**
73     * Interface that wraps one of X509TrustManager or
74     * X509TrustManagerExtensions to support platforms before the latter was
75     * added.
76     */
77    private static interface X509TrustManagerImplementation {
78        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
79                                                        String authType,
80                                                        String host) throws CertificateException;
81    }
82
83    private static final class X509TrustManagerIceCreamSandwich implements
84            X509TrustManagerImplementation {
85        private final X509TrustManager mTrustManager;
86
87        public X509TrustManagerIceCreamSandwich(X509TrustManager trustManager) {
88            mTrustManager = trustManager;
89        }
90
91        @Override
92        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
93                                                        String authType,
94                                                        String host) throws CertificateException {
95            mTrustManager.checkServerTrusted(chain, authType);
96            return Collections.<X509Certificate>emptyList();
97        }
98    }
99
100    private static final class X509TrustManagerJellyBean implements X509TrustManagerImplementation {
101        private final X509TrustManagerExtensions mTrustManagerExtensions;
102
103        @SuppressLint("NewApi")
104        public X509TrustManagerJellyBean(X509TrustManager trustManager) {
105            mTrustManagerExtensions = new X509TrustManagerExtensions(trustManager);
106        }
107
108        @Override
109        public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
110                                                        String authType,
111                                                        String host) throws CertificateException {
112            return mTrustManagerExtensions.checkServerTrusted(chain, authType, host);
113        }
114    }
115
116    private static CertificateFactory sCertificateFactory;
117
118    private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
119    private static final String OID_ANY_EKU = "2.5.29.37.0";
120    // Server-Gated Cryptography (necessary to support a few legacy issuers):
121    //    Netscape:
122    private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
123    //    Microsoft:
124    private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
125
126    /**
127     * Trust manager backed up by the read-only system certificate store.
128     */
129    private static X509TrustManagerImplementation sDefaultTrustManager;
130
131    /**
132     * BroadcastReceiver that listens to change in the system keystore to invalidate certificate
133     * caches.
134     */
135    private static TrustStorageListener sTrustStorageListener;
136
137    /**
138     * Trust manager backed up by a custom certificate store. We need such manager to plant test
139     * root CA to the trust store in testing.
140     */
141    private static X509TrustManagerImplementation sTestTrustManager;
142    private static KeyStore sTestKeyStore;
143
144    /**
145     * The system key store. This is used to determine whether a trust anchor is a system trust
146     * anchor or user-installed.
147     */
148    private static KeyStore sSystemKeyStore;
149
150    /**
151     * The directory where system certificates are stored. This is used to determine whether a
152     * trust anchor is a system trust anchor or user-installed. The KeyStore API alone is not
153     * sufficient to efficiently query whether a given X500Principal, PublicKey pair is a trust
154     * anchor.
155     */
156    private static File sSystemCertificateDirectory;
157
158    /**
159     * An in-memory cache of which trust anchors are system trust roots. This avoids reading and
160     * decoding the root from disk on every verification. Mirrors a similar in-memory cache in
161     * Conscrypt's X509TrustManager implementation.
162     */
163    private static Set<Pair<X500Principal, PublicKey>> sSystemTrustAnchorCache;
164
165    /**
166     * True if the system key store has been loaded. If the "AndroidCAStore" KeyStore instance
167     * was not found, sSystemKeyStore may be null while sLoadedSystemKeyStore is true.
168     */
169    private static boolean sLoadedSystemKeyStore;
170
171    /**
172     * Lock object used to synchronize all calls that modify or depend on the trust managers.
173     */
174    private static final Object sLock = new Object();
175
176    /**
177     * Allow disabling registering the observer and recording histograms for the certificate
178     * changes. Net unit tests do not load native libraries which prevent this to succeed. Moreover,
179     * the system does not allow to interact with the certificate store without user interaction.
180     */
181    private static boolean sDisableNativeCodeForTest = false;
182
183    /**
184     * Ensures that the trust managers and certificate factory are initialized.
185     */
186    private static void ensureInitialized() throws CertificateException,
187            KeyStoreException, NoSuchAlgorithmException {
188        synchronized (sLock) {
189            if (sCertificateFactory == null) {
190                sCertificateFactory = CertificateFactory.getInstance("X.509");
191            }
192            if (sDefaultTrustManager == null) {
193                sDefaultTrustManager = X509Util.createTrustManager(null);
194            }
195            if (!sLoadedSystemKeyStore) {
196                try {
197                    sSystemKeyStore = KeyStore.getInstance("AndroidCAStore");
198                    try {
199                        sSystemKeyStore.load(null);
200                    } catch (IOException e) {
201                        // No IO operation is attempted.
202                    }
203                    sSystemCertificateDirectory =
204                            new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
205                } catch (KeyStoreException e) {
206                    // Could not load AndroidCAStore. Continue anyway; isKnownRoot will always
207                    // return false.
208                }
209                if (!sDisableNativeCodeForTest)
210                    nativeRecordCertVerifyCapabilitiesHistogram(sSystemKeyStore != null);
211                sLoadedSystemKeyStore = true;
212            }
213            if (sSystemTrustAnchorCache == null) {
214                sSystemTrustAnchorCache = new HashSet<Pair<X500Principal, PublicKey>>();
215            }
216            if (sTestKeyStore == null) {
217                sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
218                try {
219                    sTestKeyStore.load(null);
220                } catch (IOException e) {
221                    // No IO operation is attempted.
222                }
223            }
224            if (sTestTrustManager == null) {
225                sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
226            }
227            if (!sDisableNativeCodeForTest && sTrustStorageListener == null) {
228                sTrustStorageListener = new TrustStorageListener();
229                nativeGetApplicationContext().registerReceiver(sTrustStorageListener,
230                        new IntentFilter(KeyChain.ACTION_STORAGE_CHANGED));
231            }
232        }
233    }
234
235    /**
236     * Creates a X509TrustManagerImplementation backed up by the given key
237     * store. When null is passed as a key store, system default trust store is
238     * used.
239     * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
240     */
241    private static X509TrustManagerImplementation createTrustManager(KeyStore keyStore) throws
242            KeyStoreException, NoSuchAlgorithmException {
243        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
244        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
245        tmf.init(keyStore);
246
247        for (TrustManager tm : tmf.getTrustManagers()) {
248            if (tm instanceof X509TrustManager) {
249                try {
250                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
251                        return new X509TrustManagerJellyBean((X509TrustManager) tm);
252                    } else {
253                        return new X509TrustManagerIceCreamSandwich((X509TrustManager) tm);
254                    }
255                } catch (IllegalArgumentException e) {
256                    Log.e(TAG, "Error creating trust manager: " + e);
257                }
258            }
259        }
260        return null;
261    }
262
263    /**
264     * After each modification of test key store, trust manager has to be generated again.
265     */
266    private static void reloadTestTrustManager() throws KeyStoreException,
267            NoSuchAlgorithmException {
268        sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
269    }
270
271    /**
272     * After each modification by the system of the key store, trust manager has to be regenerated.
273     */
274    private static void reloadDefaultTrustManager() throws KeyStoreException,
275            NoSuchAlgorithmException, CertificateException {
276        sDefaultTrustManager = null;
277        sSystemTrustAnchorCache = null;
278        nativeNotifyKeyChainChanged();
279        ensureInitialized();
280    }
281
282    /**
283     * Convert a DER encoded certificate to an X509Certificate.
284     */
285    public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
286            CertificateException, KeyStoreException, NoSuchAlgorithmException {
287        ensureInitialized();
288        return (X509Certificate) sCertificateFactory.generateCertificate(
289                new ByteArrayInputStream(derBytes));
290    }
291
292    public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
293            KeyStoreException, NoSuchAlgorithmException {
294        ensureInitialized();
295        X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
296        synchronized (sLock) {
297            sTestKeyStore.setCertificateEntry(
298                    "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
299            reloadTestTrustManager();
300        }
301    }
302
303    public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
304            CertificateException, KeyStoreException {
305        ensureInitialized();
306        synchronized (sLock) {
307            try {
308                sTestKeyStore.load(null);
309                reloadTestTrustManager();
310            } catch (IOException e) {
311                // No IO operation is attempted.
312            }
313        }
314    }
315
316    private static final char[] HEX_DIGITS = {
317        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
318        'a', 'b', 'c', 'd', 'e', 'f',
319    };
320
321    private static String hashPrincipal(X500Principal principal) throws NoSuchAlgorithmException {
322        // Android hashes a principal as the first four bytes of its MD5 digest, encoded in
323        // lowercase hex and reversed. Verified in 4.2, 4.3, and 4.4.
324        byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
325        char[] hexChars = new char[8];
326        for (int i = 0; i < 4; i++) {
327            hexChars[2 * i] = HEX_DIGITS[(digest[3 - i] >> 4) & 0xf];
328            hexChars[2 * i + 1] = HEX_DIGITS[digest[3 - i] & 0xf];
329        }
330        return new String(hexChars);
331    }
332
333    private static boolean isKnownRoot(X509Certificate root)
334            throws NoSuchAlgorithmException, KeyStoreException {
335        // Could not find the system key store. Conservatively report false.
336        if (sSystemKeyStore == null)
337            return false;
338
339        // Check the in-memory cache first; avoid decoding the anchor from disk
340        // if it has been seen before.
341        Pair<X500Principal, PublicKey> key =
342            new Pair<X500Principal, PublicKey>(root.getSubjectX500Principal(), root.getPublicKey());
343        if (sSystemTrustAnchorCache.contains(key))
344            return true;
345
346        // Note: It is not sufficient to call sSystemKeyStore.getCertificiateAlias. If the server
347        // supplies a copy of a trust anchor, X509TrustManagerExtensions returns the server's
348        // version rather than the system one. getCertificiateAlias will then fail to find an anchor
349        // name. This is fixed upstream in https://android-review.googlesource.com/#/c/91605/
350        //
351        // TODO(davidben): When the change trickles into an Android release, query sSystemKeyStore
352        // directly.
353
354        // System trust anchors are stored under a hash of the principal. In case of collisions,
355        // a number is appended.
356        String hash = hashPrincipal(root.getSubjectX500Principal());
357        for (int i = 0; true; i++) {
358            String alias = hash + '.' + i;
359            if (!new File(sSystemCertificateDirectory, alias).exists())
360                break;
361
362            Certificate anchor = sSystemKeyStore.getCertificate("system:" + alias);
363            // It is possible for this to return null if the user deleted a trust anchor. In
364            // that case, the certificate remains in the system directory but is also added to
365            // another file. Continue iterating as there may be further collisions after the
366            // deleted anchor.
367            if (anchor == null)
368                continue;
369
370            if (!(anchor instanceof X509Certificate)) {
371                // This should never happen.
372                String className = anchor.getClass().getName();
373                Log.e(TAG, "Anchor " + alias + " not an X509Certificate: " + className);
374                continue;
375            }
376
377            // If the subject and public key match, this is a system root.
378            X509Certificate anchorX509 = (X509Certificate)anchor;
379            if (root.getSubjectX500Principal().equals(anchorX509.getSubjectX500Principal()) &&
380                root.getPublicKey().equals(anchorX509.getPublicKey())) {
381                sSystemTrustAnchorCache.add(key);
382                return true;
383            }
384        }
385
386        return false;
387    }
388
389    /**
390     * If an EKU extension is present in the end-entity certificate, it MUST contain either the
391     * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
392     *
393     * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
394     * OIDs for web server certificates.
395     *
396     * TODO(palmer): This can be removed after the equivalent change is made to the Android default
397     * TrustManager and that change is shipped to a large majority of Android users.
398     */
399    static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
400        List<String> ekuOids;
401        try {
402            ekuOids = certificate.getExtendedKeyUsage();
403        } catch (NullPointerException e) {
404            // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
405            // happens when the EKU extension data is malformed so return false here.
406            // See http://crbug.com/233610
407            return false;
408        }
409        if (ekuOids == null)
410            return true;
411
412        for (String ekuOid : ekuOids) {
413            if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
414                ekuOid.equals(OID_ANY_EKU) ||
415                ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
416                ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
417                return true;
418            }
419        }
420
421        return false;
422    }
423
424    public static AndroidCertVerifyResult verifyServerCertificates(byte[][] certChain,
425                                                                   String authType,
426                                                                   String host)
427            throws KeyStoreException, NoSuchAlgorithmException {
428        if (certChain == null || certChain.length == 0 || certChain[0] == null) {
429            throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
430                    "chain passed as |certChain|. |certChain|=" + Arrays.deepToString(certChain));
431        }
432
433
434        try {
435            ensureInitialized();
436        } catch (CertificateException e) {
437            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
438        }
439
440        X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
441        try {
442            for (int i = 0; i < certChain.length; ++i) {
443                serverCertificates[i] = createCertificateFromBytes(certChain[i]);
444            }
445        } catch (CertificateException e) {
446            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_UNABLE_TO_PARSE);
447        }
448
449        // Expired and not yet valid certificates would be rejected by the trust managers, but the
450        // trust managers report all certificate errors using the general CertificateException. In
451        // order to get more granular error information, cert validity time range is being checked
452        // separately.
453        try {
454            serverCertificates[0].checkValidity();
455            if (!verifyKeyUsage(serverCertificates[0])) {
456                return new AndroidCertVerifyResult(
457                        CertVerifyStatusAndroid.VERIFY_INCORRECT_KEY_USAGE);
458            }
459        } catch (CertificateExpiredException e) {
460            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_EXPIRED);
461        } catch (CertificateNotYetValidException e) {
462            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_NOT_YET_VALID);
463        } catch (CertificateException e) {
464            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
465        }
466
467        synchronized (sLock) {
468            List<X509Certificate> verifiedChain;
469            try {
470                verifiedChain = sDefaultTrustManager.checkServerTrusted(serverCertificates,
471                                                                        authType, host);
472            } catch (CertificateException eDefaultManager) {
473                try {
474                    verifiedChain = sTestTrustManager.checkServerTrusted(serverCertificates,
475                                                                         authType, host);
476                } catch (CertificateException eTestManager) {
477                    // Neither of the trust managers confirms the validity of the certificate chain,
478                    // log the error message returned by the system trust manager.
479                    Log.i(TAG, "Failed to validate the certificate chain, error: " +
480                              eDefaultManager.getMessage());
481                    return new AndroidCertVerifyResult(
482                            CertVerifyStatusAndroid.VERIFY_NO_TRUSTED_ROOT);
483                }
484            }
485
486            boolean isIssuedByKnownRoot = false;
487            if (verifiedChain.size() > 0) {
488                X509Certificate root = verifiedChain.get(verifiedChain.size() - 1);
489                isIssuedByKnownRoot = isKnownRoot(root);
490            }
491
492            return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_OK,
493                                               isIssuedByKnownRoot, verifiedChain);
494        }
495    }
496
497    public static void setDisableNativeCodeForTest(boolean disabled) {
498        sDisableNativeCodeForTest = disabled;
499    }
500    /**
501     * Notify the native net::CertDatabase instance that the system database has been updated.
502     */
503    private static native void nativeNotifyKeyChainChanged();
504
505    /**
506     * Record histograms on the platform's certificate verification capabilities.
507     */
508    private static native void nativeRecordCertVerifyCapabilitiesHistogram(
509        boolean foundSystemTrustRoots);
510
511    /**
512     * Returns the application context.
513     */
514    private static native Context nativeGetApplicationContext();
515
516}
517