PinListEntry.java revision e88bbba97a2a68287b93fecba822d11f272325b7
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.apache.harmony.xnet.provider.jsse; 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; 28import libcore.io.EventLogger; 29 30/** 31 * This class represents a single entry in the pin file. 32 */ 33class 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 static final boolean DEBUG = false; 47 48 public String getCommonName() { 49 return cn; 50 } 51 52 public boolean getEnforcing() { 53 return enforcing; 54 } 55 56 public PinListEntry(String entry) throws PinEntryException { 57 if (entry == null) { 58 throw new NullPointerException("entry == null"); 59 } 60 // Examples: 61 // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d 62 // *.android.com=true|ca05f...8a0d34c 63 // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f 64 String[] values = entry.split("[=,|]"); 65 // entry must have a CN, an enforcement value, and at least one pin 66 if (values.length < 3) { 67 throw new PinEntryException("Received malformed pin entry"); 68 } 69 // get the cn 70 cn = values[0]; // is there more validation we can do here? 71 enforcing = enforcementValueFromString(values[1]); 72 // the remainder should be pins 73 addPins(Arrays.copyOfRange(values, 2, values.length)); 74 } 75 76 private static boolean enforcementValueFromString(String val) throws PinEntryException { 77 if (val.equals("true")) { 78 return true; 79 } else if (val.equals("false")) { 80 return false; 81 } else { 82 throw new PinEntryException("Enforcement status is not a valid value"); 83 } 84 } 85 86 /** 87 * Checks the given chain against the pin list corresponding to this entry. 88 * 89 * If the pin list does not contain the required certs and the enforcing field is true then 90 * this returns true, indicating a verification error. Otherwise, it returns false and 91 * verification should proceed. 92 */ 93 public boolean chainIsNotPinned(List<X509Certificate> chain) { 94 for (X509Certificate cert : chain) { 95 String fingerprint = getFingerprint(cert); 96 if (pinnedFingerprints.contains(fingerprint)) { 97 return false; 98 } 99 } 100 logPinFailure(cn, chain); 101 return enforcing; 102 } 103 104 private static String getFingerprint(X509Certificate cert) { 105 try { 106 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 107 byte[] encoded = cert.getPublicKey().getEncoded(); 108 byte[] fingerprint = dgst.digest(encoded); 109 return IntegralToString.bytesToHexString(fingerprint, false); 110 } catch (NoSuchAlgorithmException e) { 111 throw new AssertionError(e); 112 } 113 } 114 115 private void addPins(String[] pins) { 116 for (String pin : pins) { 117 validatePin(pin); 118 } 119 Collections.addAll(pinnedFingerprints, pins); 120 } 121 122 private static void validatePin(String pin) { 123 // check to make sure the length is correct 124 if (pin.length() != 128) { 125 throw new IllegalArgumentException("Pin is not a valid length"); 126 } 127 // check to make sure that it's a valid hex string 128 try { 129 new BigInteger(pin, 16); 130 } catch (NumberFormatException e) { 131 throw new IllegalArgumentException("Pin is not a valid hex string", e); 132 } 133 } 134 135 private void logPinFailure(String cn, List<X509Certificate> chain) { 136 Object[] values = new Object[chain.size() + 1]; 137 values[0] = (Object) cn; 138 for (int i=0; i < chain.size(); i++) { 139 values[i+1] = chain.get(i).toString(); 140 } 141 EventLogger.writeEvent(90100, values); 142 } 143} 144 145