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