diff options
Diffstat (limited to 'luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java')
-rw-r--r-- | luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java new file mode 100644 index 0000000..4cdd8d2 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java @@ -0,0 +1,184 @@ +/* + * 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.apache.harmony.xnet.provider.jsse; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.net.ssl.DefaultHostnameVerifier; +import libcore.io.IoUtils; +import libcore.util.BasicLruCache; + +/** + * This class provides a simple interface for cert pinning. + */ +public class CertPinManager { + + private long lastModified; + + private final Map<String, PinListEntry> entries = new HashMap<String, PinListEntry>(); + private final BasicLruCache<String, String> hostnameCache = new BasicLruCache<String, String>(10); + private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier(); + + private boolean initialized = false; + private static final boolean DEBUG = false; + + private final File pinFile; + private final TrustedCertificateStore certStore; + + public CertPinManager(TrustedCertificateStore store) throws PinManagerException { + pinFile = new File("/data/misc/keychain/pins"); + certStore = store; + rebuild(); + } + + /** Test only */ + public CertPinManager(String path, TrustedCertificateStore store) throws PinManagerException { + if (path == null) { + throw new NullPointerException("path == null"); + } + pinFile = new File(path); + certStore = store; + rebuild(); + } + + /** + * This is the public interface for cert pinning. + * + * Given a hostname and a certificate chain this verifies that the chain includes + * certs from the pinned list provided. + * + * If the chain doesn't include those certs and is in enforcing mode, then this method + * returns true and the certificate check should fail. + */ + public boolean chainIsNotPinned(String hostname, List<X509Certificate> chain) + throws PinManagerException { + // lookup the entry + PinListEntry entry = lookup(hostname); + + // return its result or false if there's no pin + if (entry != null) { + return entry.chainIsNotPinned(chain); + } + return false; + } + + private synchronized void rebuild() throws PinManagerException { + // reread the pin file + String pinFileContents = readPinFile(); + + if (pinFileContents != null) { + // rebuild the pinned certs + for (String entry : getPinFileEntries(pinFileContents)) { + try { + PinListEntry pin = new PinListEntry(entry, certStore); + entries.put(pin.getCommonName(), pin); + } catch (PinEntryException e) { + log("Pinlist contains a malformed pin: " + entry, e); + } + } + + // clear the cache + hostnameCache.evictAll(); + + // set the last modified time + lastModified = pinFile.lastModified(); + + // we've been fully initialized and are ready to go + initialized = true; + } + } + + private String readPinFile() throws PinManagerException { + try { + return IoUtils.readFileAsString(pinFile.getPath()); + } catch (FileNotFoundException e) { + // there's no pin list, all certs are unpinned + return null; + } catch (IOException e) { + // this is unexpected, fail + throw new PinManagerException("Unexpected error reading pin list; failing.", e); + } + } + + private static String[] getPinFileEntries(String pinFileContents) { + return pinFileContents.split("\n"); + } + + private synchronized PinListEntry lookup(String hostname) throws PinManagerException { + + // if we don't have any data, don't bother + if (!initialized) { + return null; + } + + // check to see if our cache is valid + if (cacheIsNotValid()) { + rebuild(); + } + + // if so, check the hostname cache + String cn = hostnameCache.get(hostname); + if (cn != null) { + // if we hit, return the corresponding entry + return entries.get(cn); + } + + // otherwise, get the matching cn + cn = getMatchingCN(hostname); + if (cn != null) { + hostnameCache.put(hostname, cn); + // we have a matching CN, return that entry + return entries.get(cn); + } + + // if we got here, we don't have a matching CN for this hostname + return null; + } + + private boolean cacheIsNotValid() { + return pinFile.lastModified() != lastModified; + } + + private String getMatchingCN(String hostname) { + String bestMatch = ""; + for (String cn : entries.keySet()) { + // skip shorter CNs since they can't be better matches + if (cn.length() < bestMatch.length()) { + continue; + } + // now verify that the CN matches at all + if (verifier.verifyHostName(hostname, cn)) { + bestMatch = cn; + } + } + return bestMatch; + } + + private static void log(String s, Exception e) { + if (DEBUG) { + System.out.println("PINFILE: " + s); + if (e != null) { + e.printStackTrace(); + } + } + } +} |