1bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra/*
2bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * Copyright (C) 2012 The Android Open Source Project
3bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra *
4bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * Licensed under the Apache License, Version 2.0 (the "License");
5bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * you may not use this file except in compliance with the License.
6bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * You may obtain a copy of the License at
7bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra *
8bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra *      http://www.apache.org/licenses/LICENSE-2.0
9bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra *
10bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * Unless required by applicable law or agreed to in writing, software
11bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * distributed under the License is distributed on an "AS IS" BASIS,
12bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * See the License for the specific language governing permissions and
14bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * limitations under the License.
15bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra */
16bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
17860d2707ce126ef8f66e3eac7ceeab6d24218cd8Kenny Rootpackage org.conscrypt;
18bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
19bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.math.BigInteger;
20bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.security.MessageDigest;
21bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.security.NoSuchAlgorithmException;
22bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.security.cert.X509Certificate;
23bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.util.Arrays;
24bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.util.Collections;
25bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.util.HashSet;
26bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.util.List;
27bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condraimport java.util.Set;
28bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
29bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra/**
30bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra * This class represents a single entry in the pin file.
31bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra */
32b3baf9fdcf2b4f44949ca12faa22fac11a680c6dBrian Carlstrom// public for testing by CertPinManagerTest
33b3baf9fdcf2b4f44949ca12faa22fac11a680c6dBrian Carlstrompublic class PinListEntry {
34bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
35bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    /** The Common Name (CN) as used on the SSL certificate */
36bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private final String cn;
37bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
38bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    /**
39bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     * Determines whether a failed match here will prevent the chain from being accepted. If true,
40bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     *  an unpinned chain will log and cause a match failure. If false, it will merely log.
41bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     */
42bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private final boolean enforcing;
43bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
44bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private final Set<String> pinnedFingerprints = new HashSet<String>();
45bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
46d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra    private final TrustedCertificateStore certStore;
47d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra
48bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    public String getCommonName() {
49bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        return cn;
50bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
51bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
52bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    public boolean getEnforcing() {
53bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        return enforcing;
54bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
55bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
56d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra    public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException {
57bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        if (entry == null) {
58bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new NullPointerException("entry == null");
59bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
60d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra        certStore = store;
61bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // Examples:
62bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d
63bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // *.android.com=true|ca05f...8a0d34c
64bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f
65bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        String[] values = entry.split("[=,|]");
66bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // entry must have a CN, an enforcement value, and at least one pin
67bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        if (values.length < 3) {
68bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new PinEntryException("Received malformed pin entry");
69bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
70bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // get the cn
71bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        cn = values[0];        // is there more validation we can do here?
72bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        enforcing = enforcementValueFromString(values[1]);
73bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // the remainder should be pins
74bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        addPins(Arrays.copyOfRange(values, 2, values.length));
75bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
76bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
77bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private static boolean enforcementValueFromString(String val) throws PinEntryException {
78bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        if (val.equals("true")) {
79bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            return true;
80bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        } else if (val.equals("false")) {
81bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            return false;
82bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        } else {
83bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new PinEntryException("Enforcement status is not a valid value");
84bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
85bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
86bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
87bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    /**
88bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     * Checks the given chain against the pin list corresponding to this entry.
89bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     *
90071523134e16d74430eb184d1e0b85e821306214Kenny Root     * <p>If enforcing is on and the given {@code chain} does not include the
91071523134e16d74430eb184d1e0b85e821306214Kenny Root     * expected pinned certificate, this will return {@code false} indicating
929be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh     * the chain is not valid unless the {@code chain} chains up to an user-installed
939be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh     * CA cert. Otherwise this will return {@code true} indicating the {@code chain}
949be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh     * is valid.
95bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra     */
96071523134e16d74430eb184d1e0b85e821306214Kenny Root    public boolean isChainValid(List<X509Certificate> chain) {
979be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh        boolean containsUserCert = chainContainsUserCert(chain);
989be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh        if (!containsUserCert) {
999be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh            for (X509Certificate cert : chain) {
1009be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh                String fingerprint = getFingerprint(cert);
1019be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh                if (pinnedFingerprints.contains(fingerprint)) {
1029be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh                    return true;
1039be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh                }
104bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            }
105bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
1069be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh        logPinFailure(chain, containsUserCert);
1079be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh        return !enforcing || containsUserCert;
108bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
109bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
110bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private static String getFingerprint(X509Certificate cert) {
111bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        try {
112bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            MessageDigest dgst = MessageDigest.getInstance("SHA512");
113bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            byte[] encoded = cert.getPublicKey().getEncoded();
114bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            byte[] fingerprint = dgst.digest(encoded);
115bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            return IntegralToString.bytesToHexString(fingerprint, false);
116bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        } catch (NoSuchAlgorithmException e) {
117bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new AssertionError(e);
118bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
119bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
120bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
121bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private void addPins(String[] pins) {
122bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        for (String pin : pins) {
123bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            validatePin(pin);
124bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
125bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        Collections.addAll(pinnedFingerprints, pins);
126bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
127bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
128bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    private static void validatePin(String pin) {
129bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // check to make sure the length is correct
130bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        if (pin.length() != 128) {
131bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new IllegalArgumentException("Pin is not a valid length");
132bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
133bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        // check to make sure that it's a valid hex string
134bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        try {
135bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            new BigInteger(pin, 16);
136bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        } catch (NumberFormatException e) {
137bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra            throw new IllegalArgumentException("Pin is not a valid hex string", e);
138bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
139bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
140bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
141d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra    private boolean chainContainsUserCert(List<X509Certificate> chain) {
14257d58181ea27386828357990b12efa9ae336e072Geremy Condra        if (certStore == null) {
14357d58181ea27386828357990b12efa9ae336e072Geremy Condra            return false;
14457d58181ea27386828357990b12efa9ae336e072Geremy Condra        }
145d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra        for (X509Certificate cert : chain) {
146d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra            if (certStore.isUserAddedCertificate(cert)) {
147d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra                return true;
148d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra            }
149bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra        }
150d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra        return false;
151d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra    }
152d928361f8b85e08b10a58db2877616fce7989d52Geremy Condra
1539be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh    private void logPinFailure(List<X509Certificate> chain, boolean containsUserCert) {
1549be0a37b7d5075e879159f25dc1d5c007e9cbc18William Luh        PinFailureLogger.log(cn, containsUserCert, enforcing, chain);
155bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra    }
156bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra}
157bbe7b9241a63a18bebebf38206d0aebb015f1518Geremy Condra
158