• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package org.chromium.net;
6 
7 import android.annotation.SuppressLint;
8 import android.content.BroadcastReceiver;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.content.IntentFilter;
12 import android.net.http.X509TrustManagerExtensions;
13 import android.os.Build;
14 import android.security.KeyChain;
15 import android.util.Log;
16 import android.util.Pair;
17 
18 import org.chromium.base.JNINamespace;
19 
20 import java.io.ByteArrayInputStream;
21 import java.io.File;
22 import java.io.IOException;
23 import java.security.KeyStore;
24 import java.security.KeyStoreException;
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.PublicKey;
28 import java.security.cert.Certificate;
29 import java.security.cert.CertificateException;
30 import java.security.cert.CertificateExpiredException;
31 import java.security.cert.CertificateFactory;
32 import java.security.cert.CertificateNotYetValidException;
33 import java.security.cert.X509Certificate;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39 
40 import javax.net.ssl.TrustManager;
41 import javax.net.ssl.TrustManagerFactory;
42 import javax.net.ssl.X509TrustManager;
43 import javax.security.auth.x500.X500Principal;
44 
45 /**
46  * Utility functions for verifying X.509 certificates.
47  */
48 @JNINamespace("net")
49 public class X509Util {
50 
51     private static final String TAG = "X509Util";
52 
53     private static final class TrustStorageListener extends BroadcastReceiver {
onReceive(Context context, Intent intent)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 {
checkServerTrusted(X509Certificate[] chain, String authType, String host)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 
X509TrustManagerIceCreamSandwich(X509TrustManager trustManager)87         public X509TrustManagerIceCreamSandwich(X509TrustManager trustManager) {
88             mTrustManager = trustManager;
89         }
90 
91         @Override
checkServerTrusted(X509Certificate[] chain, String authType, String host)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")
X509TrustManagerJellyBean(X509TrustManager trustManager)104         public X509TrustManagerJellyBean(X509TrustManager trustManager) {
105             mTrustManagerExtensions = new X509TrustManagerExtensions(trustManager);
106         }
107 
108         @Override
checkServerTrusted(X509Certificate[] chain, String authType, String host)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      */
ensureInitialized()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. Returns null if no created TrustManager was suitable.
239      * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
240      */
createTrustManager(KeyStore keyStore)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                     String className = tm.getClass().getName();
257                     Log.e(TAG, "Error creating trust manager (" + className + "): " + e);
258                 }
259             }
260         }
261         Log.e(TAG, "Could not find suitable trust manager");
262         return null;
263     }
264 
265     /**
266      * After each modification of test key store, trust manager has to be generated again.
267      */
reloadTestTrustManager()268     private static void reloadTestTrustManager() throws KeyStoreException,
269             NoSuchAlgorithmException {
270         sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
271     }
272 
273     /**
274      * After each modification by the system of the key store, trust manager has to be regenerated.
275      */
reloadDefaultTrustManager()276     private static void reloadDefaultTrustManager() throws KeyStoreException,
277             NoSuchAlgorithmException, CertificateException {
278         sDefaultTrustManager = null;
279         sSystemTrustAnchorCache = null;
280         nativeNotifyKeyChainChanged();
281         ensureInitialized();
282     }
283 
284     /**
285      * Convert a DER encoded certificate to an X509Certificate.
286      */
createCertificateFromBytes(byte[] derBytes)287     public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
288             CertificateException, KeyStoreException, NoSuchAlgorithmException {
289         ensureInitialized();
290         return (X509Certificate) sCertificateFactory.generateCertificate(
291                 new ByteArrayInputStream(derBytes));
292     }
293 
addTestRootCertificate(byte[] rootCertBytes)294     public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
295             KeyStoreException, NoSuchAlgorithmException {
296         ensureInitialized();
297         X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
298         synchronized (sLock) {
299             sTestKeyStore.setCertificateEntry(
300                     "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
301             reloadTestTrustManager();
302         }
303     }
304 
clearTestRootCertificates()305     public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
306             CertificateException, KeyStoreException {
307         ensureInitialized();
308         synchronized (sLock) {
309             try {
310                 sTestKeyStore.load(null);
311                 reloadTestTrustManager();
312             } catch (IOException e) {
313                 // No IO operation is attempted.
314             }
315         }
316     }
317 
318     private static final char[] HEX_DIGITS = {
319         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
320         'a', 'b', 'c', 'd', 'e', 'f',
321     };
322 
hashPrincipal(X500Principal principal)323     private static String hashPrincipal(X500Principal principal) throws NoSuchAlgorithmException {
324         // Android hashes a principal as the first four bytes of its MD5 digest, encoded in
325         // lowercase hex and reversed. Verified in 4.2, 4.3, and 4.4.
326         byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
327         char[] hexChars = new char[8];
328         for (int i = 0; i < 4; i++) {
329             hexChars[2 * i] = HEX_DIGITS[(digest[3 - i] >> 4) & 0xf];
330             hexChars[2 * i + 1] = HEX_DIGITS[digest[3 - i] & 0xf];
331         }
332         return new String(hexChars);
333     }
334 
isKnownRoot(X509Certificate root)335     private static boolean isKnownRoot(X509Certificate root)
336             throws NoSuchAlgorithmException, KeyStoreException {
337         // Could not find the system key store. Conservatively report false.
338         if (sSystemKeyStore == null)
339             return false;
340 
341         // Check the in-memory cache first; avoid decoding the anchor from disk
342         // if it has been seen before.
343         Pair<X500Principal, PublicKey> key =
344             new Pair<X500Principal, PublicKey>(root.getSubjectX500Principal(), root.getPublicKey());
345         if (sSystemTrustAnchorCache.contains(key))
346             return true;
347 
348         // Note: It is not sufficient to call sSystemKeyStore.getCertificiateAlias. If the server
349         // supplies a copy of a trust anchor, X509TrustManagerExtensions returns the server's
350         // version rather than the system one. getCertificiateAlias will then fail to find an anchor
351         // name. This is fixed upstream in https://android-review.googlesource.com/#/c/91605/
352         //
353         // TODO(davidben): When the change trickles into an Android release, query sSystemKeyStore
354         // directly.
355 
356         // System trust anchors are stored under a hash of the principal. In case of collisions,
357         // a number is appended.
358         String hash = hashPrincipal(root.getSubjectX500Principal());
359         for (int i = 0; true; i++) {
360             String alias = hash + '.' + i;
361             if (!new File(sSystemCertificateDirectory, alias).exists())
362                 break;
363 
364             Certificate anchor = sSystemKeyStore.getCertificate("system:" + alias);
365             // It is possible for this to return null if the user deleted a trust anchor. In
366             // that case, the certificate remains in the system directory but is also added to
367             // another file. Continue iterating as there may be further collisions after the
368             // deleted anchor.
369             if (anchor == null)
370                 continue;
371 
372             if (!(anchor instanceof X509Certificate)) {
373                 // This should never happen.
374                 String className = anchor.getClass().getName();
375                 Log.e(TAG, "Anchor " + alias + " not an X509Certificate: " + className);
376                 continue;
377             }
378 
379             // If the subject and public key match, this is a system root.
380             X509Certificate anchorX509 = (X509Certificate)anchor;
381             if (root.getSubjectX500Principal().equals(anchorX509.getSubjectX500Principal()) &&
382                 root.getPublicKey().equals(anchorX509.getPublicKey())) {
383                 sSystemTrustAnchorCache.add(key);
384                 return true;
385             }
386         }
387 
388         return false;
389     }
390 
391     /**
392      * If an EKU extension is present in the end-entity certificate, it MUST contain either the
393      * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
394      *
395      * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
396      * OIDs for web server certificates.
397      *
398      * TODO(palmer): This can be removed after the equivalent change is made to the Android default
399      * TrustManager and that change is shipped to a large majority of Android users.
400      */
verifyKeyUsage(X509Certificate certificate)401     static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
402         List<String> ekuOids;
403         try {
404             ekuOids = certificate.getExtendedKeyUsage();
405         } catch (NullPointerException e) {
406             // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
407             // happens when the EKU extension data is malformed so return false here.
408             // See http://crbug.com/233610
409             return false;
410         }
411         if (ekuOids == null)
412             return true;
413 
414         for (String ekuOid : ekuOids) {
415             if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
416                 ekuOid.equals(OID_ANY_EKU) ||
417                 ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
418                 ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
419                 return true;
420             }
421         }
422 
423         return false;
424     }
425 
verifyServerCertificates(byte[][] certChain, String authType, String host)426     public static AndroidCertVerifyResult verifyServerCertificates(byte[][] certChain,
427                                                                    String authType,
428                                                                    String host)
429             throws KeyStoreException, NoSuchAlgorithmException {
430         if (certChain == null || certChain.length == 0 || certChain[0] == null) {
431             throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
432                     "chain passed as |certChain|. |certChain|=" + Arrays.deepToString(certChain));
433         }
434 
435 
436         try {
437             ensureInitialized();
438         } catch (CertificateException e) {
439             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
440         }
441 
442         X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
443         try {
444             for (int i = 0; i < certChain.length; ++i) {
445                 serverCertificates[i] = createCertificateFromBytes(certChain[i]);
446             }
447         } catch (CertificateException e) {
448             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_UNABLE_TO_PARSE);
449         }
450 
451         // Expired and not yet valid certificates would be rejected by the trust managers, but the
452         // trust managers report all certificate errors using the general CertificateException. In
453         // order to get more granular error information, cert validity time range is being checked
454         // separately.
455         try {
456             serverCertificates[0].checkValidity();
457             if (!verifyKeyUsage(serverCertificates[0])) {
458                 return new AndroidCertVerifyResult(
459                         CertVerifyStatusAndroid.VERIFY_INCORRECT_KEY_USAGE);
460             }
461         } catch (CertificateExpiredException e) {
462             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_EXPIRED);
463         } catch (CertificateNotYetValidException e) {
464             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_NOT_YET_VALID);
465         } catch (CertificateException e) {
466             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
467         }
468 
469         synchronized (sLock) {
470             // If no trust manager was found, fail without crashing on the null pointer.
471             if (sDefaultTrustManager == null)
472                 return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_FAILED);
473 
474             List<X509Certificate> verifiedChain;
475             try {
476                 verifiedChain = sDefaultTrustManager.checkServerTrusted(serverCertificates,
477                                                                         authType, host);
478             } catch (CertificateException eDefaultManager) {
479                 try {
480                     verifiedChain = sTestTrustManager.checkServerTrusted(serverCertificates,
481                                                                          authType, host);
482                 } catch (CertificateException eTestManager) {
483                     // Neither of the trust managers confirms the validity of the certificate chain,
484                     // log the error message returned by the system trust manager.
485                     Log.i(TAG, "Failed to validate the certificate chain, error: " +
486                               eDefaultManager.getMessage());
487                     return new AndroidCertVerifyResult(
488                             CertVerifyStatusAndroid.VERIFY_NO_TRUSTED_ROOT);
489                 }
490             }
491 
492             boolean isIssuedByKnownRoot = false;
493             if (verifiedChain.size() > 0) {
494                 X509Certificate root = verifiedChain.get(verifiedChain.size() - 1);
495                 isIssuedByKnownRoot = isKnownRoot(root);
496             }
497 
498             return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_OK,
499                                                isIssuedByKnownRoot, verifiedChain);
500         }
501     }
502 
setDisableNativeCodeForTest(boolean disabled)503     public static void setDisableNativeCodeForTest(boolean disabled) {
504         sDisableNativeCodeForTest = disabled;
505     }
506     /**
507      * Notify the native net::CertDatabase instance that the system database has been updated.
508      */
nativeNotifyKeyChainChanged()509     private static native void nativeNotifyKeyChainChanged();
510 
511     /**
512      * Record histograms on the platform's certificate verification capabilities.
513      */
nativeRecordCertVerifyCapabilitiesHistogram( boolean foundSystemTrustRoots)514     private static native void nativeRecordCertVerifyCapabilitiesHistogram(
515         boolean foundSystemTrustRoots);
516 
517     /**
518      * Returns the application context.
519      */
nativeGetApplicationContext()520     private static native Context nativeGetApplicationContext();
521 
522 }
523