| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.conscrypt; |
| |
| import java.math.BigInteger; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.X509Certificate; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * This class represents a single entry in the pin file. |
| */ |
| // public for testing by CertPinManagerTest |
| public class PinListEntry { |
| |
| /** The Common Name (CN) as used on the SSL certificate */ |
| private final String cn; |
| |
| /** |
| * Determines whether a failed match here will prevent the chain from being accepted. If true, |
| * an unpinned chain will log and cause a match failure. If false, it will merely log. |
| */ |
| private final boolean enforcing; |
| |
| private final Set<String> pinnedFingerprints = new HashSet<String>(); |
| |
| private final TrustedCertificateStore certStore; |
| |
| public String getCommonName() { |
| return cn; |
| } |
| |
| public boolean getEnforcing() { |
| return enforcing; |
| } |
| |
| public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException { |
| if (entry == null) { |
| throw new NullPointerException("entry == null"); |
| } |
| certStore = store; |
| // Examples: |
| // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d |
| // *.android.com=true|ca05f...8a0d34c |
| // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f |
| String[] values = entry.split("[=,|]"); |
| // entry must have a CN, an enforcement value, and at least one pin |
| if (values.length < 3) { |
| throw new PinEntryException("Received malformed pin entry"); |
| } |
| // get the cn |
| cn = values[0]; // is there more validation we can do here? |
| enforcing = enforcementValueFromString(values[1]); |
| // the remainder should be pins |
| addPins(Arrays.copyOfRange(values, 2, values.length)); |
| } |
| |
| private static boolean enforcementValueFromString(String val) throws PinEntryException { |
| if (val.equals("true")) { |
| return true; |
| } else if (val.equals("false")) { |
| return false; |
| } else { |
| throw new PinEntryException("Enforcement status is not a valid value"); |
| } |
| } |
| |
| /** |
| * Checks the given chain against the pin list corresponding to this entry. |
| * |
| * <p>If enforcing is on and the given {@code chain} does not include the |
| * expected pinned certificate, this will return {@code false} indicating |
| * the chain is not valid unless the {@code chain} chains up to an user-installed |
| * CA cert. Otherwise this will return {@code true} indicating the {@code chain} |
| * is valid. |
| */ |
| public boolean isChainValid(List<X509Certificate> chain) { |
| boolean containsUserCert = chainContainsUserCert(chain); |
| if (!containsUserCert) { |
| for (X509Certificate cert : chain) { |
| String fingerprint = getFingerprint(cert); |
| if (pinnedFingerprints.contains(fingerprint)) { |
| return true; |
| } |
| } |
| } |
| logPinFailure(chain, containsUserCert); |
| return !enforcing || containsUserCert; |
| } |
| |
| private static String getFingerprint(X509Certificate cert) { |
| try { |
| MessageDigest dgst = MessageDigest.getInstance("SHA512"); |
| byte[] encoded = cert.getPublicKey().getEncoded(); |
| byte[] fingerprint = dgst.digest(encoded); |
| return IntegralToString.bytesToHexString(fingerprint, false); |
| } catch (NoSuchAlgorithmException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| private void addPins(String[] pins) { |
| for (String pin : pins) { |
| validatePin(pin); |
| } |
| Collections.addAll(pinnedFingerprints, pins); |
| } |
| |
| private static void validatePin(String pin) { |
| // check to make sure the length is correct |
| if (pin.length() != 128) { |
| throw new IllegalArgumentException("Pin is not a valid length"); |
| } |
| // check to make sure that it's a valid hex string |
| try { |
| new BigInteger(pin, 16); |
| } catch (NumberFormatException e) { |
| throw new IllegalArgumentException("Pin is not a valid hex string", e); |
| } |
| } |
| |
| private boolean chainContainsUserCert(List<X509Certificate> chain) { |
| if (certStore == null) { |
| return false; |
| } |
| for (X509Certificate cert : chain) { |
| if (certStore.isUserAddedCertificate(cert)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void logPinFailure(List<X509Certificate> chain, boolean containsUserCert) { |
| PinFailureLogger.log(cn, containsUserCert, enforcing, chain); |
| } |
| } |
| |