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