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    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
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 */
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