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     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          *
    127          * WARNING: Do not add short serials to this list!
    128          *
    129          * Since this currently doesn't compare the serial + issuer, you
    130          * should only add serials that have enough entropy here. Short
    131          * serials may inadvertently match a certificate that was issued
    132          * not in compliance with the Baseline Requirements.
    133          */
    134         Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList(
    135             // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup
    136             // Not a real certificate. For testing only.
    137             new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16),
    138             new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16),
    139             new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16),
    140             new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16),
    141             new BigInteger("9239d5348f40d1695a745470e1f23f43", 16),
    142             new BigInteger("e9028b9578e415dc1a710a2b88154447", 16),
    143             new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16),
    144             new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16),
    145             new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16),
    146             new BigInteger("3e75ced46b693021218830ae86a82a71", 16)
    147         ));
    148 
    149         // attempt to augment it with values taken from gservices
    150         String serialBlacklist = readBlacklist(path);
    151         if (!serialBlacklist.equals("")) {
    152             for(String value : serialBlacklist.split(",")) {
    153                 try {
    154                     bl.add(new BigInteger(value, 16));
    155                 } catch (NumberFormatException e) {
    156                     logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e);
    157                 }
    158             }
    159         }
    160 
    161         // whether that succeeds or fails, send it on its merry way
    162         return Collections.unmodifiableSet(bl);
    163     }
    164 
    165     private static final Set<byte[]> readPublicKeyBlackList(String path) {
    166 
    167         // start out with a base set of known bad values
    168         Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList(
    169             // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
    170             // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info (at) diginotar.nl
    171             "410f36363258f30b347d12ce4863e433437806a8".getBytes(),
    172             // Subject: CN=DigiNotar Cyber CA
    173             // Issuer: CN=GTE CyberTrust Global Root
    174             "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(),
    175             // Subject: CN=DigiNotar Services 1024 CA
    176             // Issuer: CN=Entrust.net
    177             "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(),
    178             // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
    179             // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
    180             "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(),
    181             // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
    182             // Issuer: CN=Staat der Nederlanden Overheid CA
    183             "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(),
    184             // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
    185             // Subject: O=Digicert Sdn. Bhd.
    186             // Issuer: CN=GTE CyberTrust Global Root
    187             "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(),
    188             // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti (at) kktcmerkezbankasi.org
    189             // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
    190             "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(),
    191             // Subject: CN=*.EGO.GOV.TR 93
    192             // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
    193             "783333c9687df63377efceddd82efa9101913e8e".getBytes(),
    194             // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
    195             // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
    196             "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes()
    197         ));
    198 
    199         // attempt to augment it with values taken from gservices
    200         String pubkeyBlacklist = readBlacklist(path);
    201         if (!pubkeyBlacklist.equals("")) {
    202             for (String value : pubkeyBlacklist.split(",")) {
    203                 value = value.trim();
    204                 if (isPubkeyHash(value)) {
    205                     bl.add(value.getBytes());
    206                 } else {
    207                     logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
    208                 }
    209             }
    210         }
    211 
    212         return bl;
    213     }
    214 
    215     public boolean isPublicKeyBlackListed(PublicKey publicKey) {
    216         byte[] encoded = publicKey.getEncoded();
    217         Digest digest = AndroidDigestFactory.getSHA1();
    218         digest.update(encoded, 0, encoded.length);
    219         byte[] out = new byte[digest.getDigestSize()];
    220         digest.doFinal(out, 0);
    221         for (byte[] blacklisted : pubkeyBlacklist) {
    222             if (Arrays.equals(blacklisted, Hex.encode(out))) {
    223                 return true;
    224             }
    225         }
    226         return false;
    227     }
    228 
    229     public boolean isSerialNumberBlackListed(BigInteger serial) {
    230         return serialBlacklist.contains(serial);
    231     }
    232 
    233 }
    234