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