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