1e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/* 2e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Copyright (C) 2014 Square, Inc. 3e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 4e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Licensed under the Apache License, Version 2.0 (the "License"); 5e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * you may not use this file except in compliance with the License. 6e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * You may obtain a copy of the License at 7e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 8e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * http://www.apache.org/licenses/LICENSE-2.0 9e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 10e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Unless required by applicable law or agreed to in writing, software 11e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * distributed under the License is distributed on an "AS IS" BASIS, 12e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * See the License for the specific language governing permissions and 14e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * limitations under the License. 15e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 16e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpackage com.squareup.okhttp; 17e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 18e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport com.squareup.okhttp.internal.Util; 19e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.security.cert.Certificate; 20e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.security.cert.X509Certificate; 21e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.Arrays; 22e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.LinkedHashMap; 2371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fullerimport java.util.LinkedHashSet; 24e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.List; 25e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport java.util.Map; 2671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fullerimport java.util.Set; 27e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport javax.net.ssl.SSLPeerUnverifiedException; 28e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerimport okio.ByteString; 29e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 3071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fullerimport static java.util.Collections.unmodifiableSet; 31e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 32e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller/** 33e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Constrains which certificates are trusted. Pinning certificates defends 34e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * against attacks on certificate authorities. It also prevents connections 35e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * through man-in-the-middle certificate authorities either known or unknown to 36e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * the application's user. 37e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 38e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <p>This class currently pins a certificate's Subject Public Key Info as 39e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * described on <a href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins 40e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * are base-64 SHA-1 hashes, consistent with the format Chromium uses for <a 41e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * href="http://goo.gl/XDh6je">static certificates</a>. See Chromium's <a 42e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * href="http://goo.gl/4CCnGs">pinsets</a> for hostnames that are pinned in that 43e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * browser. 44e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 45e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <h3>Setting up Certificate Pinning</h3> 46e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * The easiest way to pin a host is turn on pinning with a broken configuration 47e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * and read the expected configuration when the connection fails. Be sure to 48e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * do this on a trusted network, and without man-in-the-middle tools like <a 49e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * href="http://charlesproxy.com">Charles</a> or <a 50e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * href="http://fiddlertool.com">Fiddler</a>. 51e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 52e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <p>For example, to pin {@code https://publicobject.com}, start with a broken 53e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * configuration: <pre> {@code 54e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 55e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * String hostname = "publicobject.com"; 56e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * CertificatePinner certificatePinner = new CertificatePinner.Builder() 576c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer * .add(hostname, "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=") 58e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .build(); 59e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * OkHttpClient client = new OkHttpClient(); 60e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * client.setCertificatePinner(certificatePinner); 61e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 62e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Request request = new Request.Builder() 63e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .url("https://" + hostname) 64e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .build(); 65e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * client.newCall(request).execute(); 66e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * }</pre> 67e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 68e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * As expected, this fails with a certificate pinning exception: <pre> {@code 69e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 70e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure! 71e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Peer certificate chain: 72e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL 73e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA 74e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority 75e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root 76e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Pinned certificates for publicobject.com: 776c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer * sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA= 78e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * at com.squareup.okhttp.CertificatePinner.check(CertificatePinner.java) 79e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * at com.squareup.okhttp.Connection.upgradeToTls(Connection.java) 80e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * at com.squareup.okhttp.Connection.connect(Connection.java) 81e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java) 82e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * }</pre> 83e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 84e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Follow up by pasting the public key hashes from the exception into the 85e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * certificate pinner's configuration: <pre> {@code 86e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 87e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * CertificatePinner certificatePinner = new CertificatePinner.Builder() 88e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") 89e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=") 90e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=") 91e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=") 92e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * .build(); 93e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * }</pre> 94e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 9571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * Pinning is per-hostname and/or per-wildcard pattern. To pin both 9671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * {@code publicobject.com} and {@code www.publicobject.com}, you must 9771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * configure both hostnames. 9871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * 9971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * <p>Wildcard pattern rules: 10071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * <ol> 10171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * <li>Asterisk {@code *} is only permitted in the left-most 10271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * domain name label and must be the only character in that label 10371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * (i.e., must match the whole left-most label). For example, 10471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * {@code *.example.com} is permitted, while {@code *a.example.com}, 10571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * {@code a*.example.com}, {@code a*b.example.com}, {@code a.*.example.com} 10671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * are not permitted. 10771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * <li>Asterisk {@code *} cannot match across domain name labels. 10871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * For example, {@code *.example.com} matches {@code test.example.com} 10971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * but does not match {@code sub.test.example.com}. 11071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * <li>Wildcard patterns for single-label domain names are not permitted. 11171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * </ol> 11271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * 11371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * If hostname pinned directly and via wildcard pattern, both 11471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * direct and wildcard pins will be used. For example: {@code *.example.com} pinned 11571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * with {@code pin1} and {@code a.example.com} pinned with {@code pin2}, 11671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * to check {@code a.example.com} both {@code pin1} and {@code pin2} will be used. 117e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 118e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * <h3>Warning: Certificate Pinning is Dangerous!</h3> 119e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Pinning certificates limits your server team's abilities to update their TLS 120e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * certificates. By pinning certificates, you take on additional operational 121e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * complexity and limit your ability to migrate between certificate authorities. 122e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Do not use certificate pinning without the blessing of your server's TLS 123e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * administrator! 1247aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * 1257aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * <h4>Note about self-signed certificates</h4> 1267aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * {@link CertificatePinner} can not be used to pin self-signed certificate 1277aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * if such certificate is not accepted by {@link javax.net.ssl.TrustManager}. 1287aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * 1297aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * @see <a href="https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning"> 1307aeaaefc891f6221f4b2cce536b1c1e816e09794Neil Fuller * OWASP: Certificate and Public Key Pinning</a> 131e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 132e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fullerpublic final class CertificatePinner { 133e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public static final CertificatePinner DEFAULT = new Builder().build(); 134e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 13571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller private final Map<String, Set<ByteString>> hostnameToPins; 136e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 137e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private CertificatePinner(Builder builder) { 1386c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer this.hostnameToPins = Util.immutableMap(builder.hostnameToPins); 139e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 140e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 141e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** 142e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Confirms that at least one of the certificates pinned for {@code hostname} 143e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * is in {@code peerCertificates}. Does nothing if there are no certificates 144e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * pinned for {@code hostname}. OkHttp calls this after a successful TLS 145e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * handshake, but before the connection is used. 146e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * 147e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match 148e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * the certificates pinned for {@code hostname}. 149e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 150e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public void check(String hostname, List<Certificate> peerCertificates) 151e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throws SSLPeerUnverifiedException { 15271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 15371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> pins = findMatchingPins(hostname); 15471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 155e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (pins == null) return; 156e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 157e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller for (int i = 0, size = peerCertificates.size(); i < size; i++) { 158e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i); 159e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (pins.contains(sha1(x509Certificate))) return; // Success! 160e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 161e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 162e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller // If we couldn't find a matching pin, format a nice exception. 163e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller StringBuilder message = new StringBuilder() 164e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller .append("Certificate pinning failure!") 165e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller .append("\n Peer certificate chain:"); 166e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller for (int i = 0, size = peerCertificates.size(); i < size; i++) { 167e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i); 168e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller message.append("\n ").append(pin(x509Certificate)) 169e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller .append(": ").append(x509Certificate.getSubjectDN().getName()); 170e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 171e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller message.append("\n Pinned certificates for ").append(hostname).append(":"); 17271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller for (ByteString pin : pins) { 173e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller message.append("\n sha1/").append(pin.base64()); 174e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 175e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new SSLPeerUnverifiedException(message.toString()); 176e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 177e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 178e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** @deprecated replaced with {@link #check(String, List)}. */ 179e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public void check(String hostname, Certificate... peerCertificates) 180e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throws SSLPeerUnverifiedException { 181e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller check(hostname, Arrays.asList(peerCertificates)); 182e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 183e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 184e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** 18571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * Returns list of matching certificates' pins for the hostname 18671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * or {@code null} if hostname does not have pinned certificates. 18771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller */ 18871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> findMatchingPins(String hostname) { 18971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> directPins = hostnameToPins.get(hostname); 19071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> wildcardPins = null; 19171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 19271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller int indexOfFirstDot = hostname.indexOf('.'); 19371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller int indexOfLastDot = hostname.lastIndexOf('.'); 19471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 19571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller // Skip hostnames with one dot symbol for wildcard pattern search 19671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller // example.com will be skipped 19771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller // a.example.com won't be skipped 19871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller if (indexOfFirstDot != indexOfLastDot) { 19971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller // a.example.com -> search for wildcard pattern *.example.com 20071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller wildcardPins = hostnameToPins.get("*." + hostname.substring(indexOfFirstDot + 1)); 20171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller } 20271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 20371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller if (directPins == null && wildcardPins == null) return null; 20471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 20571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller if (directPins != null && wildcardPins != null) { 20671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> pins = new LinkedHashSet<>(); 20771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller pins.addAll(directPins); 20871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller pins.addAll(wildcardPins); 20971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller return pins; 21071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller } 21171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 21271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller if (directPins != null) return directPins; 21371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 21471b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller return wildcardPins; 21571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller } 21671b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller 21771b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller /** 218e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * Returns the SHA-1 of {@code certificate}'s public key. This uses the 219e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * mechanism Moxie Marlinspike describes in <a 220e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller * href="https://github.com/moxie0/AndroidPinning">Android Pinning</a>. 221e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 222e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public static String pin(Certificate certificate) { 223e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (!(certificate instanceof X509Certificate)) { 224e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new IllegalArgumentException("Certificate pinning requires X509 certificates"); 225e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 226e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return "sha1/" + sha1((X509Certificate) certificate).base64(); 227e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 228e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 229e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller private static ByteString sha1(X509Certificate x509Certificate) { 230e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded())); 231e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 232e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 233e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** Builds a configured certificate pinner. */ 234e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public static final class Builder { 23571b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller private final Map<String, Set<ByteString>> hostnameToPins = new LinkedHashMap<>(); 236e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 237e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller /** 23871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * Pins certificates for {@code hostname}. 23971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * 24071b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * @param hostname lower-case host name or wildcard pattern such as {@code *.example.com}. 24171b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * @param pins SHA-1 hashes. Each pin is a SHA-1 hash of a 24271b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * certificate's Subject Public Key Info, base64-encoded and prefixed with 24371b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller * {@code sha1/}. 244e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller */ 245e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public Builder add(String hostname, String... pins) { 246e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (hostname == null) throw new IllegalArgumentException("hostname == null"); 247e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 24871b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> hostPins = new LinkedHashSet<>(); 24971b9f47b26fb57ac3e436a19519c6e3ec70e86ebNeil Fuller Set<ByteString> previousPins = hostnameToPins.put(hostname, unmodifiableSet(hostPins)); 250e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (previousPins != null) { 251e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller hostPins.addAll(previousPins); 252e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 253e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 254e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller for (String pin : pins) { 255e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (!pin.startsWith("sha1/")) { 256e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new IllegalArgumentException("pins must start with 'sha1/': " + pin); 257e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 258e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller ByteString decodedPin = ByteString.decodeBase64(pin.substring("sha1/".length())); 259e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller if (decodedPin == null) { 260e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller throw new IllegalArgumentException("pins must be base64: " + pin); 261e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 262e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller hostPins.add(decodedPin); 263e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 264e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 265e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return this; 266e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 267e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller 268e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller public CertificatePinner build() { 269e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller return new CertificatePinner(this); 270e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 271e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller } 272e78f117bcbd6b57d783737107f445ef75ecb474aNeil Fuller} 273