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