Home | History | Annotate | Download | only in provider
      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.bouncycastle.jce.provider;
     18 
     19 import java.io.Closeable;
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.FileNotFoundException;
     22 import java.io.IOException;
     23 import java.io.RandomAccessFile;
     24 import java.math.BigInteger;
     25 import java.security.PublicKey;
     26 import java.util.Arrays;
     27 import java.util.Collections;
     28 import java.util.HashSet;
     29 import java.util.Set;
     30 import java.util.logging.Level;
     31 import java.util.logging.Logger;
     32 import org.bouncycastle.crypto.Digest;
     33 import org.bouncycastle.crypto.digests.AndroidDigestFactory;
     34 import org.bouncycastle.util.encoders.Hex;
     35 
     36 public 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             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 (at) 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 (at) 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