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