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 17package org.conscrypt; 18 19import java.math.BigInteger; 20import java.security.MessageDigest; 21import java.security.NoSuchAlgorithmException; 22import java.security.cert.X509Certificate; 23import java.util.Arrays; 24import java.util.Collections; 25import java.util.HashSet; 26import java.util.List; 27import java.util.Set; 28 29/** 30 * This class represents a single entry in the pin file. 31 */ 32// public for testing by CertPinManagerTest 33public class PinListEntry { 34 35 /** The Common Name (CN) as used on the SSL certificate */ 36 private final String cn; 37 38 /** 39 * Determines whether a failed match here will prevent the chain from being accepted. If true, 40 * an unpinned chain will log and cause a match failure. If false, it will merely log. 41 */ 42 private final boolean enforcing; 43 44 private final Set<String> pinnedFingerprints = new HashSet<String>(); 45 46 private final TrustedCertificateStore certStore; 47 48 public String getCommonName() { 49 return cn; 50 } 51 52 public boolean getEnforcing() { 53 return enforcing; 54 } 55 56 public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException { 57 if (entry == null) { 58 throw new NullPointerException("entry == null"); 59 } 60 certStore = store; 61 // Examples: 62 // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d 63 // *.android.com=true|ca05f...8a0d34c 64 // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f 65 String[] values = entry.split("[=,|]"); 66 // entry must have a CN, an enforcement value, and at least one pin 67 if (values.length < 3) { 68 throw new PinEntryException("Received malformed pin entry"); 69 } 70 // get the cn 71 cn = values[0]; // is there more validation we can do here? 72 enforcing = enforcementValueFromString(values[1]); 73 // the remainder should be pins 74 addPins(Arrays.copyOfRange(values, 2, values.length)); 75 } 76 77 private static boolean enforcementValueFromString(String val) throws PinEntryException { 78 if (val.equals("true")) { 79 return true; 80 } else if (val.equals("false")) { 81 return false; 82 } else { 83 throw new PinEntryException("Enforcement status is not a valid value"); 84 } 85 } 86 87 /** 88 * Checks the given chain against the pin list corresponding to this entry. 89 * 90 * <p>If enforcing is on and the given {@code chain} does not include the 91 * expected pinned certificate, this will return {@code false} indicating 92 * the chain is not valid unless the {@code chain} chains up to an user-installed 93 * CA cert. Otherwise this will return {@code true} indicating the {@code chain} 94 * is valid. 95 */ 96 public boolean isChainValid(List<X509Certificate> chain) { 97 boolean containsUserCert = chainContainsUserCert(chain); 98 if (!containsUserCert) { 99 for (X509Certificate cert : chain) { 100 String fingerprint = getFingerprint(cert); 101 if (pinnedFingerprints.contains(fingerprint)) { 102 return true; 103 } 104 } 105 } 106 logPinFailure(chain, containsUserCert); 107 return !enforcing || containsUserCert; 108 } 109 110 private static String getFingerprint(X509Certificate cert) { 111 try { 112 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 113 byte[] encoded = cert.getPublicKey().getEncoded(); 114 byte[] fingerprint = dgst.digest(encoded); 115 return IntegralToString.bytesToHexString(fingerprint, false); 116 } catch (NoSuchAlgorithmException e) { 117 throw new AssertionError(e); 118 } 119 } 120 121 private void addPins(String[] pins) { 122 for (String pin : pins) { 123 validatePin(pin); 124 } 125 Collections.addAll(pinnedFingerprints, pins); 126 } 127 128 private static void validatePin(String pin) { 129 // check to make sure the length is correct 130 if (pin.length() != 128) { 131 throw new IllegalArgumentException("Pin is not a valid length"); 132 } 133 // check to make sure that it's a valid hex string 134 try { 135 new BigInteger(pin, 16); 136 } catch (NumberFormatException e) { 137 throw new IllegalArgumentException("Pin is not a valid hex string", e); 138 } 139 } 140 141 private boolean chainContainsUserCert(List<X509Certificate> chain) { 142 if (certStore == null) { 143 return false; 144 } 145 for (X509Certificate cert : chain) { 146 if (certStore.isUserAddedCertificate(cert)) { 147 return true; 148 } 149 } 150 return false; 151 } 152 153 private void logPinFailure(List<X509Certificate> chain, boolean containsUserCert) { 154 PinFailureLogger.log(cn, containsUserCert, enforcing, chain); 155 } 156} 157 158