| Index: net/android/java/src/org/chromium/net/X509Util.java
|
| diff --git a/net/android/java/src/org/chromium/net/X509Util.java b/net/android/java/src/org/chromium/net/X509Util.java
|
| index 5c478a27f4a430445d5564c6fd5f02ac3dcaf985..c58346583f7e9eb4b608f2b5b4e39f39f80720f5 100644
|
| --- a/net/android/java/src/org/chromium/net/X509Util.java
|
| +++ b/net/android/java/src/org/chromium/net/X509Util.java
|
| @@ -13,14 +13,19 @@ import android.net.http.X509TrustManagerExtensions;
|
| import android.os.Build;
|
| import android.security.KeyChain;
|
| import android.util.Log;
|
| +import android.util.Pair;
|
|
|
| import org.chromium.base.JNINamespace;
|
|
|
| import java.io.ByteArrayInputStream;
|
| +import java.io.File;
|
| import java.io.IOException;
|
| import java.security.KeyStore;
|
| import java.security.KeyStoreException;
|
| +import java.security.MessageDigest;
|
| import java.security.NoSuchAlgorithmException;
|
| +import java.security.PublicKey;
|
| +import java.security.cert.Certificate;
|
| import java.security.cert.CertificateException;
|
| import java.security.cert.CertificateExpiredException;
|
| import java.security.cert.CertificateFactory;
|
| @@ -28,11 +33,14 @@ import java.security.cert.CertificateNotYetValidException;
|
| import java.security.cert.X509Certificate;
|
| import java.util.Arrays;
|
| import java.util.Collections;
|
| +import java.util.HashSet;
|
| import java.util.List;
|
| +import java.util.Set;
|
|
|
| import javax.net.ssl.TrustManager;
|
| import javax.net.ssl.TrustManagerFactory;
|
| import javax.net.ssl.X509TrustManager;
|
| +import javax.security.auth.x500.X500Principal;
|
|
|
| /**
|
| * Utility functions for verifying X.509 certificates.
|
| @@ -134,6 +142,33 @@ public class X509Util {
|
| private static KeyStore sTestKeyStore;
|
|
|
| /**
|
| + * The system key store. This is used to determine whether a trust anchor is a system trust
|
| + * anchor or user-installed.
|
| + */
|
| + private static KeyStore sSystemKeyStore;
|
| +
|
| + /**
|
| + * The directory where system certificates are stored. This is used to determine whether a
|
| + * trust anchor is a system trust anchor or user-installed. The KeyStore API alone is not
|
| + * sufficient to efficiently query whether a given X500Principal, PublicKey pair is a trust
|
| + * anchor.
|
| + */
|
| + private static File sSystemCertificateDirectory;
|
| +
|
| + /**
|
| + * An in-memory cache of which trust anchors are system trust roots. This avoids reading and
|
| + * decoding the root from disk on every verification. Mirrors a similar in-memory cache in
|
| + * Conscrypt's X509TrustManager implementation.
|
| + */
|
| + private static Set<Pair<X500Principal, PublicKey>> sSystemTrustAnchorCache;
|
| +
|
| + /**
|
| + * True if the system key store has been loaded. If the "AndroidCAStore" KeyStore instance
|
| + * was not found, sSystemKeyStore may be null while sLoadedSystemKeyStore is true.
|
| + */
|
| + private static boolean sLoadedSystemKeyStore;
|
| +
|
| + /**
|
| * Lock object used to synchronize all calls that modify or depend on the trust managers.
|
| */
|
| private static final Object sLock = new Object();
|
| @@ -157,6 +192,27 @@ public class X509Util {
|
| if (sDefaultTrustManager == null) {
|
| sDefaultTrustManager = X509Util.createTrustManager(null);
|
| }
|
| + if (!sLoadedSystemKeyStore) {
|
| + try {
|
| + sSystemKeyStore = KeyStore.getInstance("AndroidCAStore");
|
| + try {
|
| + sSystemKeyStore.load(null);
|
| + } catch (IOException e) {
|
| + // No IO operation is attempted.
|
| + }
|
| + sSystemCertificateDirectory =
|
| + new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
|
| + } catch (KeyStoreException e) {
|
| + // Could not load AndroidCAStore. Continue anyway; isKnownRoot will always
|
| + // return false.
|
| + }
|
| + if (!sDisableNativeCodeForTest)
|
| + nativeRecordCertVerifyCapabilitiesHistogram(sSystemKeyStore != null);
|
| + sLoadedSystemKeyStore = true;
|
| + }
|
| + if (sSystemTrustAnchorCache == null) {
|
| + sSystemTrustAnchorCache = new HashSet<Pair<X500Principal, PublicKey>>();
|
| + }
|
| if (sTestKeyStore == null) {
|
| sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
| try {
|
| @@ -218,6 +274,7 @@ public class X509Util {
|
| private static void reloadDefaultTrustManager() throws KeyStoreException,
|
| NoSuchAlgorithmException, CertificateException {
|
| sDefaultTrustManager = null;
|
| + sSystemTrustAnchorCache = null;
|
| nativeNotifyKeyChainChanged();
|
| ensureInitialized();
|
| }
|
| @@ -256,6 +313,79 @@ public class X509Util {
|
| }
|
| }
|
|
|
| + private static final char[] HEX_DIGITS = {
|
| + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
| + 'a', 'b', 'c', 'd', 'e', 'f',
|
| + };
|
| +
|
| + private static String hashPrincipal(X500Principal principal) throws NoSuchAlgorithmException {
|
| + // Android hashes a principal as the first four bytes of its MD5 digest, encoded in
|
| + // lowercase hex and reversed. Verified in 4.2, 4.3, and 4.4.
|
| + byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
|
| + char[] hexChars = new char[8];
|
| + for (int i = 0; i < 4; i++) {
|
| + hexChars[2 * i] = HEX_DIGITS[(digest[3 - i] >> 4) & 0xf];
|
| + hexChars[2 * i + 1] = HEX_DIGITS[digest[3 - i] & 0xf];
|
| + }
|
| + return new String(hexChars);
|
| + }
|
| +
|
| + private static boolean isKnownRoot(X509Certificate root)
|
| + throws NoSuchAlgorithmException, KeyStoreException {
|
| + // Could not find the system key store. Conservatively report false.
|
| + if (sSystemKeyStore == null)
|
| + return false;
|
| +
|
| + // Check the in-memory cache first; avoid decoding the anchor from disk
|
| + // if it has been seen before.
|
| + Pair<X500Principal, PublicKey> key =
|
| + new Pair<X500Principal, PublicKey>(root.getSubjectX500Principal(), root.getPublicKey());
|
| + if (sSystemTrustAnchorCache.contains(key))
|
| + return true;
|
| +
|
| + // Note: It is not sufficient to call sSystemKeyStore.getCertificiateAlias. If the server
|
| + // supplies a copy of a trust anchor, X509TrustManagerExtensions returns the server's
|
| + // version rather than the system one. getCertificiateAlias will then fail to find an anchor
|
| + // name. This is fixed upstream in https://android-review.googlesource.com/#/c/91605/
|
| + //
|
| + // TODO(davidben): When the change trickles into an Android release, query sSystemKeyStore
|
| + // directly.
|
| +
|
| + // System trust anchors are stored under a hash of the principal. In case of collisions,
|
| + // a number is appended.
|
| + String hash = hashPrincipal(root.getSubjectX500Principal());
|
| + for (int i = 0; true; i++) {
|
| + String alias = hash + '.' + i;
|
| + if (!new File(sSystemCertificateDirectory, alias).exists())
|
| + break;
|
| +
|
| + Certificate anchor = sSystemKeyStore.getCertificate("system:" + alias);
|
| + // It is possible for this to return null if the user deleted a trust anchor. In
|
| + // that case, the certificate remains in the system directory but is also added to
|
| + // another file. Continue iterating as there may be further collisions after the
|
| + // deleted anchor.
|
| + if (anchor == null)
|
| + continue;
|
| +
|
| + if (!(anchor instanceof X509Certificate)) {
|
| + // This should never happen.
|
| + String className = anchor.getClass().getName();
|
| + Log.e(TAG, "Anchor " + alias + " not an X509Certificate: " + className);
|
| + continue;
|
| + }
|
| +
|
| + // If the subject and public key match, this is a system root.
|
| + X509Certificate anchorX509 = (X509Certificate)anchor;
|
| + if (root.getSubjectX500Principal().equals(anchorX509.getSubjectX500Principal()) &&
|
| + root.getPublicKey().equals(anchorX509.getPublicKey())) {
|
| + sSystemTrustAnchorCache.add(key);
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| + }
|
| +
|
| /**
|
| * If an EKU extension is present in the end-entity certificate, it MUST contain either the
|
| * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
|
| @@ -353,10 +483,12 @@ public class X509Util {
|
| }
|
| }
|
|
|
| - // TODO(davidben): This code was removed for
|
| - // http://crbug.com/361166. Fix the performance regression and
|
| - // export it again.
|
| boolean isIssuedByKnownRoot = false;
|
| + if (verifiedChain.size() > 0) {
|
| + X509Certificate root = verifiedChain.get(verifiedChain.size() - 1);
|
| + isIssuedByKnownRoot = isKnownRoot(root);
|
| + }
|
| +
|
| return new AndroidCertVerifyResult(CertVerifyStatusAndroid.VERIFY_OK,
|
| isIssuedByKnownRoot, verifiedChain);
|
| }
|
|
|