CertBlacklist.java revision 2a64eecc02ffb5b991fb5c367eab777b1325eef8
1effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch/*
2effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Copyright (C) 2012 The Android Open Source Project
3effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch *
4effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Licensed under the Apache License, Version 2.0 (the "License");
5effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * you may not use this file except in compliance with the License.
6effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * You may obtain a copy of the License at
7effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch *
80529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch *      http://www.apache.org/licenses/LICENSE-2.0
9effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch *
10effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Unless required by applicable law or agreed to in writing, software
11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * distributed under the License is distributed on an "AS IS" BASIS,
12effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * See the License for the specific language governing permissions and
14effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * limitations under the License.
15effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch */
16effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
17effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochpackage org.bouncycastle.jce.provider;
18effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
19effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.io.Closeable;
20effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.io.ByteArrayOutputStream;
21effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.io.FileNotFoundException;
22effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.io.IOException;
23effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.io.RandomAccessFile;
24effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.math.BigInteger;
25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.security.PublicKey;
26effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.Arrays;
27effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.Collections;
28effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.HashSet;
29effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.Set;
30effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.logging.Level;
31effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport java.util.logging.Logger;
32effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport org.bouncycastle.crypto.Digest;
33effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport org.bouncycastle.crypto.digests.AndroidDigestFactory;
34effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport org.bouncycastle.util.encoders.Hex;
35effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochpublic class CertBlacklist {
37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    private static final Logger logger = Logger.getLogger(CertBlacklist.class.getName());
38effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
39effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    // public for testing
40effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    public final Set<BigInteger> serialBlacklist;
41effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    public final Set<byte[]> pubkeyBlacklist;
42effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
43effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    public CertBlacklist() {
44effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        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 */
54    public CertBlacklist(String pubkeyBlacklistPath, String serialBlacklistPath) {
55        pubkeyBlacklist = readPublicKeyBlackList(pubkeyBlacklistPath);
56        serialBlacklist = readSerialBlackList(serialBlacklistPath);
57    }
58
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
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
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
88    private static String readFileAsString(String path) throws IOException {
89        return readFileAsBytes(path).toString("UTF-8");
90    }
91
92    // Based on IoUtils.readFileAsBytes
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
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
123    private static final Set<BigInteger> readSerialBlackList(String path) {
124
125        // start out with a base set of known bad values
126        Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList(
127            // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup
128            // Not a real certificate. For testing only.
129            new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16),
130            new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16),
131            new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16),
132            new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16),
133            new BigInteger("9239d5348f40d1695a745470e1f23f43", 16),
134            new BigInteger("e9028b9578e415dc1a710a2b88154447", 16),
135            new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16),
136            new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16),
137            new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16),
138            new BigInteger("3e75ced46b693021218830ae86a82a71", 16),
139            new BigInteger("864", 16),
140            new BigInteger("827", 16),
141            new BigInteger("31da7", 16)
142        ));
143
144        // attempt to augment it with values taken from gservices
145        String serialBlacklist = readBlacklist(path);
146        if (!serialBlacklist.equals("")) {
147            for(String value : serialBlacklist.split(",")) {
148                try {
149                    bl.add(new BigInteger(value, 16));
150                } catch (NumberFormatException e) {
151                    logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e);
152                }
153            }
154        }
155
156        // whether that succeeds or fails, send it on its merry way
157        return Collections.unmodifiableSet(bl);
158    }
159
160    private static final Set<byte[]> readPublicKeyBlackList(String path) {
161
162        // start out with a base set of known bad values
163        Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList(
164            // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
165            // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
166            "410f36363258f30b347d12ce4863e433437806a8".getBytes(),
167            // Subject: CN=DigiNotar Cyber CA
168            // Issuer: CN=GTE CyberTrust Global Root
169            "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(),
170            // Subject: CN=DigiNotar Services 1024 CA
171            // Issuer: CN=Entrust.net
172            "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(),
173            // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
174            // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
175            "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(),
176            // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
177            // Issuer: CN=Staat der Nederlanden Overheid CA
178            "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(),
179            // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
180            // Subject: O=Digicert Sdn. Bhd.
181            // Issuer: CN=GTE CyberTrust Global Root
182            "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(),
183            // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti@kktcmerkezbankasi.org
184            // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
185            "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(),
186            // Subject: CN=*.EGO.GOV.TR 93
187            // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
188            "783333c9687df63377efceddd82efa9101913e8e".getBytes(),
189            // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
190            // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
191            "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes()
192        ));
193
194        // attempt to augment it with values taken from gservices
195        String pubkeyBlacklist = readBlacklist(path);
196        if (!pubkeyBlacklist.equals("")) {
197            for (String value : pubkeyBlacklist.split(",")) {
198                value = value.trim();
199                if (isPubkeyHash(value)) {
200                    bl.add(value.getBytes());
201                } else {
202                    logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
203                }
204            }
205        }
206
207        return bl;
208    }
209
210    public boolean isPublicKeyBlackListed(PublicKey publicKey) {
211        byte[] encoded = publicKey.getEncoded();
212        Digest digest = AndroidDigestFactory.getSHA1();
213        digest.update(encoded, 0, encoded.length);
214        byte[] out = new byte[digest.getDigestSize()];
215        digest.doFinal(out, 0);
216        for (byte[] blacklisted : pubkeyBlacklist) {
217            if (Arrays.equals(blacklisted, Hex.encode(out))) {
218                return true;
219            }
220        }
221        return false;
222    }
223
224    public boolean isSerialNumberBlackListed(BigInteger serial) {
225        return serialBlacklist.contains(serial);
226    }
227
228}
229