aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChad Brubaker <cbrubaker@google.com>2016-06-15 15:54:40 -0700
committerChad Brubaker <cbrubaker@google.com>2016-06-15 16:22:03 -0700
commitce5bdd0391d93d9a4b1fe7005041271341eb69b2 (patch)
tree83c146037c4370ec6bbe93b94a86befe5bd466cf
parent80fd9357e7190211708ea9db6d0140fe8abe3b56 (diff)
downloadconscrypt-nougat-dev.tar.gz
Move CertBlacklist to conscryptnougat-dev
CertBlacklist was previously in bouncycastle, but with the enso switch we no longer use their CertPathValidator and so blacklist checking wasn't being done. CertBlacklist is mostly unchanged from bouncycastle except removing the bouncycastle Digest and Hex dependencies in isPublicKeyBlackListed. Bug: 29397721 Change-Id: Icccdcc0e108e8b0c60c47522114749518247a598
-rw-r--r--src/platform/java/org/conscrypt/CertBlacklist.java251
-rw-r--r--src/platform/java/org/conscrypt/TrustManagerImpl.java28
2 files changed, 279 insertions, 0 deletions
diff --git a/src/platform/java/org/conscrypt/CertBlacklist.java b/src/platform/java/org/conscrypt/CertBlacklist.java
new file mode 100644
index 00000000..56ddb659
--- /dev/null
+++ b/src/platform/java/org/conscrypt/CertBlacklist.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.io.Closeable;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class CertBlacklist {
+ private static final Logger logger = Logger.getLogger(CertBlacklist.class.getName());
+
+ // public for testing
+ public final Set<BigInteger> serialBlacklist;
+ public final Set<byte[]> pubkeyBlacklist;
+
+ public CertBlacklist() {
+ String androidData = System.getenv("ANDROID_DATA");
+ String blacklistRoot = androidData + "/misc/keychain/";
+ String defaultPubkeyBlacklistPath = blacklistRoot + "pubkey_blacklist.txt";
+ String defaultSerialBlacklistPath = blacklistRoot + "serial_blacklist.txt";
+
+ pubkeyBlacklist = readPublicKeyBlackList(defaultPubkeyBlacklistPath);
+ serialBlacklist = readSerialBlackList(defaultSerialBlacklistPath);
+ }
+
+ /** Test only interface, not for public use */
+ public CertBlacklist(String pubkeyBlacklistPath, String serialBlacklistPath) {
+ pubkeyBlacklist = readPublicKeyBlackList(pubkeyBlacklistPath);
+ serialBlacklist = readSerialBlackList(serialBlacklistPath);
+ }
+
+ private static boolean isHex(String value) {
+ try {
+ new BigInteger(value, 16);
+ return true;
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Could not parse hex value " + value, e);
+ return false;
+ }
+ }
+
+ private static boolean isPubkeyHash(String value) {
+ if (value.length() != 40) {
+ logger.log(Level.WARNING, "Invalid pubkey hash length: " + value.length());
+ return false;
+ }
+ return isHex(value);
+ }
+
+ private static String readBlacklist(String path) {
+ try {
+ return readFileAsString(path);
+ } catch (FileNotFoundException ignored) {
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Could not read blacklist", e);
+ }
+ return "";
+ }
+
+ // From IoUtils.readFileAsString
+ private static String readFileAsString(String path) throws IOException {
+ return readFileAsBytes(path).toString("UTF-8");
+ }
+
+ // Based on IoUtils.readFileAsBytes
+ private static ByteArrayOutputStream readFileAsBytes(String path) throws IOException {
+ RandomAccessFile f = null;
+ try {
+ f = new RandomAccessFile(path, "r");
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) f.length());
+ byte[] buffer = new byte[8192];
+ while (true) {
+ int byteCount = f.read(buffer);
+ if (byteCount == -1) {
+ return bytes;
+ }
+ bytes.write(buffer, 0, byteCount);
+ }
+ } finally {
+ closeQuietly(f);
+ }
+ }
+
+ // Base on IoUtils.closeQuietly
+ private static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private static final Set<BigInteger> readSerialBlackList(String path) {
+
+ /* Start out with a base set of known bad values.
+ *
+ * WARNING: Do not add short serials to this list!
+ *
+ * Since this currently doesn't compare the serial + issuer, you
+ * should only add serials that have enough entropy here. Short
+ * serials may inadvertently match a certificate that was issued
+ * not in compliance with the Baseline Requirements.
+ */
+ Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList(
+ // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup
+ // Not a real certificate. For testing only.
+ new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16),
+ new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16),
+ new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16),
+ new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16),
+ new BigInteger("9239d5348f40d1695a745470e1f23f43", 16),
+ new BigInteger("e9028b9578e415dc1a710a2b88154447", 16),
+ new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16),
+ new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16),
+ new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16),
+ new BigInteger("3e75ced46b693021218830ae86a82a71", 16)
+ ));
+
+ // attempt to augment it with values taken from gservices
+ String serialBlacklist = readBlacklist(path);
+ if (!serialBlacklist.equals("")) {
+ for(String value : serialBlacklist.split(",")) {
+ try {
+ bl.add(new BigInteger(value, 16));
+ } catch (NumberFormatException e) {
+ logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e);
+ }
+ }
+ }
+
+ // whether that succeeds or fails, send it on its merry way
+ return Collections.unmodifiableSet(bl);
+ }
+
+ private static final Set<byte[]> readPublicKeyBlackList(String path) {
+
+ // start out with a base set of known bad values
+ Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList(
+ // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
+ // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
+ "410f36363258f30b347d12ce4863e433437806a8".getBytes(),
+ // Subject: CN=DigiNotar Cyber CA
+ // Issuer: CN=GTE CyberTrust Global Root
+ "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(),
+ // Subject: CN=DigiNotar Services 1024 CA
+ // Issuer: CN=Entrust.net
+ "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(),
+ // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
+ // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
+ "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(),
+ // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
+ // Issuer: CN=Staat der Nederlanden Overheid CA
+ "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(),
+ // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
+ // Subject: O=Digicert Sdn. Bhd.
+ // Issuer: CN=GTE CyberTrust Global Root
+ "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(),
+ // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti@kktcmerkezbankasi.org
+ // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
+ "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(),
+ // Subject: CN=*.EGO.GOV.TR 93
+ // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
+ "783333c9687df63377efceddd82efa9101913e8e".getBytes(),
+ // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
+ // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
+ "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes()
+ ));
+
+ // attempt to augment it with values taken from gservices
+ String pubkeyBlacklist = readBlacklist(path);
+ if (!pubkeyBlacklist.equals("")) {
+ for (String value : pubkeyBlacklist.split(",")) {
+ value = value.trim();
+ if (isPubkeyHash(value)) {
+ bl.add(value.getBytes());
+ } else {
+ logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
+ }
+ }
+ }
+
+ return bl;
+ }
+
+ public boolean isPublicKeyBlackListed(PublicKey publicKey) {
+ byte[] encoded = publicKey.getEncoded();
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA1");
+ } catch (GeneralSecurityException e) {
+ logger.log(Level.SEVERE, "Unable to get SHA1 MessageDigest", e);
+ return false;
+ }
+ byte[] out = toHex(md.digest(encoded));
+ for (byte[] blacklisted : pubkeyBlacklist) {
+ if (Arrays.equals(blacklisted, out)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final byte[] HEX_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a',
+ (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'};
+
+ private static final byte[] toHex(byte[] in) {
+ byte[] out = new byte[in.length * 2];
+ int outIndex = 0;
+ for (int i = 0; i < in.length; i++) {
+ int value = in[i] & 0xff;
+ out[outIndex++] = HEX_TABLE[value >> 4];
+ out[outIndex++] = HEX_TABLE[value & 0xf];
+ }
+ return out;
+ }
+
+ public boolean isSerialNumberBlackListed(BigInteger serial) {
+ return serialBlacklist.contains(serial);
+ }
+
+}
diff --git a/src/platform/java/org/conscrypt/TrustManagerImpl.java b/src/platform/java/org/conscrypt/TrustManagerImpl.java
index 8ee9b285..dc7882e7 100644
--- a/src/platform/java/org/conscrypt/TrustManagerImpl.java
+++ b/src/platform/java/org/conscrypt/TrustManagerImpl.java
@@ -107,6 +107,7 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
private final Exception err;
private final CertificateFactory factory;
+ private final CertBlacklist blacklist;
/**
* Creates X509TrustManager based on a keystore
@@ -129,6 +130,15 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
*/
public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
TrustedCertificateStore certStore) {
+ this(keyStore, manager, certStore, null);
+ }
+
+ /**
+ * For testing only.
+ */
+ public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
+ TrustedCertificateStore certStore,
+ CertBlacklist blacklist) {
CertPathValidator validatorLocal = null;
CertificateFactory factoryLocal = null;
KeyStore rootKeyStoreLocal = null;
@@ -169,6 +179,10 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
}
}
+ if (blacklist == null) {
+ blacklist = new CertBlacklist();
+ }
+
this.rootKeyStore = rootKeyStoreLocal;
this.trustedCertificateStore = trustedCertificateStoreLocal;
this.validator = validatorLocal;
@@ -177,6 +191,7 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
this.intermediateIndex = new TrustedCertificateIndex();
this.acceptedIssuers = acceptedIssuersLocal;
this.err = errLocal;
+ this.blacklist = blacklist;
}
private static X509Certificate[] acceptedIssuers(KeyStore ks) {
@@ -421,6 +436,9 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
current = trustAnchorChain.get(trustAnchorChain.size() - 1).getTrustedCert();
}
+ // Check that the certificate isn't blacklisted.
+ checkBlacklist(current);
+
// 1. If the current certificate in the chain is self-signed verify the chain as is.
if (current.getIssuerDN().equals(current.getSubjectDN())) {
return verifyChain(untrustedChain, trustAnchorChain, host, clientAuth);
@@ -564,6 +582,10 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
"Certificate path is not properly pinned.", null, certPath, -1));
}
}
+ // Check whole chain against the blacklist
+ for (X509Certificate cert : wholeChain) {
+ checkBlacklist(cert);
+ }
if (untrustedChain.isEmpty()) {
// The chain consists of only trust anchors, skip the validator
@@ -596,6 +618,12 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager {
return wholeChain;
}
+ private void checkBlacklist(X509Certificate cert) throws CertificateException {
+ if (blacklist.isPublicKeyBlackListed(cert.getPublicKey())) {
+ throw new CertificateException("Certificate blacklisted by public key: " + cert);
+ }
+ }
+
/**
* Sort potential anchors so that the most preferred for use come first.
*