1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import java.io.ByteArrayOutputStream; 22 import java.io.Closeable; 23 import java.io.FileNotFoundException; 24 import java.io.IOException; 25 import java.io.RandomAccessFile; 26 import java.math.BigInteger; 27 import java.security.GeneralSecurityException; 28 import java.security.MessageDigest; 29 import java.security.PublicKey; 30 import java.util.Arrays; 31 import java.util.Collections; 32 import java.util.HashSet; 33 import java.util.Set; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 37 /** 38 * @hide 39 */ 40 @Internal 41 public final class CertBlacklist { 42 private static final Logger logger = Logger.getLogger(CertBlacklist.class.getName()); 43 44 private final Set<BigInteger> serialBlacklist; 45 private final Set<byte[]> pubkeyBlacklist; 46 47 /** 48 * public for testing only. 49 */ 50 public CertBlacklist(Set<BigInteger> serialBlacklist, Set<byte[]> pubkeyBlacklist) { 51 this.serialBlacklist = serialBlacklist; 52 this.pubkeyBlacklist = pubkeyBlacklist; 53 } 54 55 public static CertBlacklist getDefault() { 56 String androidData = System.getenv("ANDROID_DATA"); 57 String blacklistRoot = androidData + "/misc/keychain/"; 58 String defaultPubkeyBlacklistPath = blacklistRoot + "pubkey_blacklist.txt"; 59 String defaultSerialBlacklistPath = blacklistRoot + "serial_blacklist.txt"; 60 61 Set<byte[]> pubkeyBlacklist = readPublicKeyBlackList(defaultPubkeyBlacklistPath); 62 Set<BigInteger> serialBlacklist = readSerialBlackList(defaultSerialBlacklistPath); 63 return new CertBlacklist(serialBlacklist, pubkeyBlacklist); 64 } 65 66 private static boolean isHex(String value) { 67 try { 68 new BigInteger(value, 16); 69 return true; 70 } catch (NumberFormatException e) { 71 logger.log(Level.WARNING, "Could not parse hex value " + value, e); 72 return false; 73 } 74 } 75 76 private static boolean isPubkeyHash(String value) { 77 if (value.length() != 40) { 78 logger.log(Level.WARNING, "Invalid pubkey hash length: " + value.length()); 79 return false; 80 } 81 return isHex(value); 82 } 83 84 private static String readBlacklist(String path) { 85 try { 86 return readFileAsString(path); 87 } catch (FileNotFoundException ignored) { 88 } catch (IOException e) { 89 logger.log(Level.WARNING, "Could not read blacklist", e); 90 } 91 return ""; 92 } 93 94 // From IoUtils.readFileAsString 95 private static String readFileAsString(String path) throws IOException { 96 return readFileAsBytes(path).toString("UTF-8"); 97 } 98 99 // Based on IoUtils.readFileAsBytes 100 private static ByteArrayOutputStream readFileAsBytes(String path) throws IOException { 101 RandomAccessFile f = null; 102 try { 103 f = new RandomAccessFile(path, "r"); 104 ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) f.length()); 105 byte[] buffer = new byte[8192]; 106 while (true) { 107 int byteCount = f.read(buffer); 108 if (byteCount == -1) { 109 return bytes; 110 } 111 bytes.write(buffer, 0, byteCount); 112 } 113 } finally { 114 closeQuietly(f); 115 } 116 } 117 118 // Base on IoUtils.closeQuietly 119 private static void closeQuietly(Closeable closeable) { 120 if (closeable != null) { 121 try { 122 closeable.close(); 123 } catch (RuntimeException rethrown) { 124 throw rethrown; 125 } catch (Exception ignored) { 126 } 127 } 128 } 129 130 private static Set<BigInteger> readSerialBlackList(String path) { 131 132 /* Start out with a base set of known bad values. 133 * 134 * WARNING: Do not add short serials to this list! 135 * 136 * Since this currently doesn't compare the serial + issuer, you 137 * should only add serials that have enough entropy here. Short 138 * serials may inadvertently match a certificate that was issued 139 * not in compliance with the Baseline Requirements. 140 */ 141 Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList( 142 // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup 143 // Not a real certificate. For testing only. 144 new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16), 145 new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16), 146 new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16), 147 new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16), 148 new BigInteger("9239d5348f40d1695a745470e1f23f43", 16), 149 new BigInteger("e9028b9578e415dc1a710a2b88154447", 16), 150 new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16), 151 new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16), 152 new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16), 153 new BigInteger("3e75ced46b693021218830ae86a82a71", 16) 154 )); 155 156 // attempt to augment it with values taken from gservices 157 String serialBlacklist = readBlacklist(path); 158 if (!serialBlacklist.equals("")) { 159 for(String value : serialBlacklist.split(",")) { 160 try { 161 bl.add(new BigInteger(value, 16)); 162 } catch (NumberFormatException e) { 163 logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e); 164 } 165 } 166 } 167 168 // whether that succeeds or fails, send it on its merry way 169 return Collections.unmodifiableSet(bl); 170 } 171 172 private static Set<byte[]> readPublicKeyBlackList(String path) { 173 174 // start out with a base set of known bad values 175 Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList( 176 // Blacklist test cert for CTS. The cert and key can be found in 177 // src/test/resources/blacklist_test_ca.pem and 178 // src/test/resources/blacklist_test_ca_key.pem. 179 "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8), 180 // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750 181 // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info (at) diginotar.nl 182 "410f36363258f30b347d12ce4863e433437806a8".getBytes(UTF_8), 183 // Subject: CN=DigiNotar Cyber CA 184 // Issuer: CN=GTE CyberTrust Global Root 185 "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(UTF_8), 186 // Subject: CN=DigiNotar Services 1024 CA 187 // Issuer: CN=Entrust.net 188 "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(UTF_8), 189 // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2 190 // Issuer: CN=Staat der Nederlanden Organisatie CA - G2 191 "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(UTF_8), 192 // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven 193 // Issuer: CN=Staat der Nederlanden Overheid CA 194 "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(UTF_8), 195 // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479 196 // Subject: O=Digicert Sdn. Bhd. 197 // Issuer: CN=GTE CyberTrust Global Root 198 "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(UTF_8), 199 // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti (at) kktcmerkezbankasi.org 200 // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri 201 "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(UTF_8), 202 // Subject: CN=*.EGO.GOV.TR 93 203 // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri 204 "783333c9687df63377efceddd82efa9101913e8e".getBytes(UTF_8), 205 // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL 206 // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification 207 "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes(UTF_8) 208 )); 209 210 // attempt to augment it with values taken from gservices 211 String pubkeyBlacklist = readBlacklist(path); 212 if (!pubkeyBlacklist.equals("")) { 213 for (String value : pubkeyBlacklist.split(",")) { 214 value = value.trim(); 215 if (isPubkeyHash(value)) { 216 bl.add(value.getBytes(UTF_8)); 217 } else { 218 logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value); 219 } 220 } 221 } 222 223 return bl; 224 } 225 226 public boolean isPublicKeyBlackListed(PublicKey publicKey) { 227 byte[] encoded = publicKey.getEncoded(); 228 MessageDigest md; 229 try { 230 md = MessageDigest.getInstance("SHA1"); 231 } catch (GeneralSecurityException e) { 232 logger.log(Level.SEVERE, "Unable to get SHA1 MessageDigest", e); 233 return false; 234 } 235 byte[] out = toHex(md.digest(encoded)); 236 for (byte[] blacklisted : pubkeyBlacklist) { 237 if (Arrays.equals(blacklisted, out)) { 238 return true; 239 } 240 } 241 return false; 242 } 243 244 private static final byte[] HEX_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', 245 (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', 246 (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'}; 247 248 private static byte[] toHex(byte[] in) { 249 byte[] out = new byte[in.length * 2]; 250 int outIndex = 0; 251 for (int i = 0; i < in.length; i++) { 252 int value = in[i] & 0xff; 253 out[outIndex++] = HEX_TABLE[value >> 4]; 254 out[outIndex++] = HEX_TABLE[value & 0xf]; 255 } 256 return out; 257 } 258 259 public boolean isSerialNumberBlackListed(BigInteger serial) { 260 return serialBlacklist.contains(serial); 261 } 262 263 } 264