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