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