summaryrefslogtreecommitdiffstats
path: root/net/android
diff options
context:
space:
mode:
authordavidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-14 00:51:09 +0000
committerdavidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-14 00:51:09 +0000
commit981f6c4e7458664fdb9200fa4e1916b0389d70fd (patch)
treea447afbaba312f08d9ad7874720df2100e4dad6a /net/android
parentb2a7a8052690922498512a6a45764e09b3b15da7 (diff)
downloadchromium_src-981f6c4e7458664fdb9200fa4e1916b0389d70fd.zip
chromium_src-981f6c4e7458664fdb9200fa4e1916b0389d70fd.tar.gz
chromium_src-981f6c4e7458664fdb9200fa4e1916b0389d70fd.tar.bz2
Detect user-installed roots more efficiently on Android.
This makes assumptions about how Android stores system certificates on disk; KeyStore's API is not sufficient to look up a trust anchor by subject and key. BUG=361166 Review URL: https://codereview.chromium.org/228293012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270275 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/android')
-rw-r--r--net/android/java/src/org/chromium/net/X509Util.java138
1 files changed, 135 insertions, 3 deletions
diff --git a/net/android/java/src/org/chromium/net/X509Util.java b/net/android/java/src/org/chromium/net/X509Util.java
index 5c478a2..c583465 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);
}