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