CertBlacklist.java revision c4c8087cad0aa18dc808d4f7058855f26891d935
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.bouncycastle.jce.provider;
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.PublicKey;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.HashSet;
29import java.util.Set;
30import java.util.logging.Level;
31import java.util.logging.Logger;
32import org.bouncycastle.crypto.Digest;
33import org.bouncycastle.crypto.digests.AndroidDigestFactory;
34import org.bouncycastle.util.encoders.Hex;
35
36public class CertBlacklist {
37
38    private static final String ANDROID_DATA = System.getenv("ANDROID_DATA");
39    private static final String BLACKLIST_ROOT = ANDROID_DATA + "/misc/keychain/";
40    public static final String DEFAULT_PUBKEY_BLACKLIST_PATH = BLACKLIST_ROOT + "pubkey_blacklist.txt";
41    public static final String DEFAULT_SERIAL_BLACKLIST_PATH = BLACKLIST_ROOT + "serial_blacklist.txt";
42
43    private static final Logger logger = Logger.getLogger(CertBlacklist.class.getName());
44
45    // public for testing
46    public final Set<BigInteger> serialBlacklist;
47    public final Set<byte[]> pubkeyBlacklist;
48
49    public CertBlacklist() {
50        this(DEFAULT_PUBKEY_BLACKLIST_PATH, DEFAULT_SERIAL_BLACKLIST_PATH);
51    }
52
53    /** Test only interface, not for public use */
54    public CertBlacklist(String pubkeyBlacklistPath, String serialBlacklistPath) {
55        serialBlacklist = readSerialBlackList(serialBlacklistPath);
56        pubkeyBlacklist = readPublicKeyBlackList(pubkeyBlacklistPath);
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        ));
142
143        // attempt to augment it with values taken from gservices
144        String serialBlacklist = readBlacklist(path);
145        if (!serialBlacklist.equals("")) {
146            for(String value : serialBlacklist.split(",")) {
147                try {
148                    bl.add(new BigInteger(value, 16));
149                } catch (NumberFormatException e) {
150                    logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e);
151                }
152            }
153        }
154
155        // whether that succeeds or fails, send it on its merry way
156        return Collections.unmodifiableSet(bl);
157    }
158
159    private static final Set<byte[]> readPublicKeyBlackList(String path) {
160
161        // start out with a base set of known bad values
162        Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList(
163            // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
164            // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
165            "410f36363258f30b347d12ce4863e433437806a8".getBytes(),
166            // Subject: CN=DigiNotar Cyber CA
167            // Issuer: CN=GTE CyberTrust Global Root
168            "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(),
169            // Subject: CN=DigiNotar Services 1024 CA
170            // Issuer: CN=Entrust.net
171            "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(),
172            // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
173            // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
174            "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(),
175            // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
176            // Issuer: CN=Staat der Nederlanden Overheid CA
177            "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(),
178            // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
179            // Subject: O=Digicert Sdn. Bhd.
180            // Issuer: CN=GTE CyberTrust Global Root
181            "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(),
182            // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti@kktcmerkezbankasi.org
183            // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
184            "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(),
185            // Subject: CN=*.EGO.GOV.TR 93
186            // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
187            "783333c9687df63377efceddd82efa9101913e8e".getBytes()
188        ));
189
190        // attempt to augment it with values taken from gservices
191        String pubkeyBlacklist = readBlacklist(path);
192        if (!pubkeyBlacklist.equals("")) {
193            for (String value : pubkeyBlacklist.split(",")) {
194                value = value.trim();
195                if (isPubkeyHash(value)) {
196                    bl.add(value.getBytes());
197                } else {
198                    logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
199                }
200            }
201        }
202
203        return bl;
204    }
205
206    public boolean isPublicKeyBlackListed(PublicKey publicKey) {
207        byte[] encoded = publicKey.getEncoded();
208        Digest digest = AndroidDigestFactory.getSHA1();
209        digest.update(encoded, 0, encoded.length);
210        byte[] out = new byte[digest.getDigestSize()];
211        digest.doFinal(out, 0);
212        for (byte[] blacklisted : pubkeyBlacklist) {
213            if (Arrays.equals(blacklisted, Hex.encode(out))) {
214                return true;
215            }
216        }
217        return false;
218    }
219
220    public boolean isSerialNumberBlackListed(BigInteger serial) {
221        return serialBlacklist.contains(serial);
222    }
223
224}
225