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