1/*
2 * Copyright (C) 2014 Square, Inc.
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 */
16package com.squareup.okhttp;
17
18import java.security.GeneralSecurityException;
19import java.util.Set;
20import javax.net.ssl.SSLPeerUnverifiedException;
21import com.squareup.okhttp.internal.HeldCertificate;
22import okio.ByteString;
23import org.junit.Test;
24
25import static com.squareup.okhttp.TestUtil.setOf;
26import static org.junit.Assert.assertEquals;
27import static org.junit.Assert.assertFalse;
28import static org.junit.Assert.assertNull;
29import static org.junit.Assert.assertTrue;
30import static org.junit.Assert.fail;
31
32public final class CertificatePinnerTest {
33  static HeldCertificate certA1;
34  static String certA1Pin;
35  static ByteString certA1PinBase64;
36
37  static HeldCertificate certB1;
38  static String certB1Pin;
39  static ByteString certB1PinBase64;
40
41  static HeldCertificate certC1;
42  static String certC1Pin;
43
44  static {
45    try {
46      certA1 = new HeldCertificate.Builder()
47          .serialNumber("100")
48          .build();
49      certA1Pin = CertificatePinner.pin(certA1.certificate);
50      certA1PinBase64 = pinToBase64(certA1Pin);
51
52      certB1 = new HeldCertificate.Builder()
53          .serialNumber("200")
54          .build();
55      certB1Pin = CertificatePinner.pin(certB1.certificate);
56      certB1PinBase64 = pinToBase64(certB1Pin);
57
58      certC1 = new HeldCertificate.Builder()
59          .serialNumber("300")
60          .build();
61      certC1Pin = CertificatePinner.pin(certC1.certificate);
62    } catch (GeneralSecurityException e) {
63      throw new AssertionError(e);
64    }
65  }
66
67  static ByteString pinToBase64(String pin) {
68    return ByteString.decodeBase64(pin.substring("sha1/".length()));
69  }
70
71  @Test public void malformedPin() throws Exception {
72    CertificatePinner.Builder builder = new CertificatePinner.Builder();
73    try {
74      builder.add("example.com", "md5/DmxUShsZuNiqPQsX2Oi9uv2sCnw=");
75      fail();
76    } catch (IllegalArgumentException expected) {
77    }
78  }
79
80  @Test public void malformedBase64() throws Exception {
81    CertificatePinner.Builder builder = new CertificatePinner.Builder();
82    try {
83      builder.add("example.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw*");
84      fail();
85    } catch (IllegalArgumentException expected) {
86    }
87  }
88
89  /** Multiple certificates generated from the same keypair have the same pin. */
90  @Test public void sameKeypairSamePin() throws Exception {
91    HeldCertificate heldCertificateA2 = new HeldCertificate.Builder()
92        .keyPair(certA1.keyPair)
93        .serialNumber("101")
94        .build();
95    String keypairACertificate2Pin = CertificatePinner.pin(heldCertificateA2.certificate);
96
97    HeldCertificate heldCertificateB2 = new HeldCertificate.Builder()
98        .keyPair(certB1.keyPair)
99        .serialNumber("201")
100        .build();
101    String keypairBCertificate2Pin = CertificatePinner.pin(heldCertificateB2.certificate);
102
103    assertTrue(certA1Pin.equals(keypairACertificate2Pin));
104    assertTrue(certB1Pin.equals(keypairBCertificate2Pin));
105    assertFalse(certA1Pin.equals(certB1Pin));
106  }
107
108  @Test public void successfulCheck() throws Exception {
109    CertificatePinner certificatePinner = new CertificatePinner.Builder()
110        .add("example.com", certA1Pin)
111        .build();
112
113    certificatePinner.check("example.com", certA1.certificate);
114  }
115
116  @Test public void successfulMatchAcceptsAnyMatchingCertificate() throws Exception {
117    CertificatePinner certificatePinner = new CertificatePinner.Builder()
118        .add("example.com", certB1Pin)
119        .build();
120
121    certificatePinner.check("example.com", certA1.certificate, certB1.certificate);
122  }
123
124  @Test public void unsuccessfulCheck() throws Exception {
125    CertificatePinner certificatePinner = new CertificatePinner.Builder()
126        .add("example.com", certA1Pin)
127        .build();
128
129    try {
130      certificatePinner.check("example.com", certB1.certificate);
131      fail();
132    } catch (SSLPeerUnverifiedException expected) {
133    }
134  }
135
136  @Test public void multipleCertificatesForOneHostname() throws Exception {
137    CertificatePinner certificatePinner = new CertificatePinner.Builder()
138        .add("example.com", certA1Pin, certB1Pin)
139        .build();
140
141    certificatePinner.check("example.com", certA1.certificate);
142    certificatePinner.check("example.com", certB1.certificate);
143  }
144
145  @Test public void multipleHostnamesForOneCertificate() throws Exception {
146    CertificatePinner certificatePinner = new CertificatePinner.Builder()
147        .add("example.com", certA1Pin)
148        .add("www.example.com", certA1Pin)
149        .build();
150
151    certificatePinner.check("example.com", certA1.certificate);
152    certificatePinner.check("www.example.com", certA1.certificate);
153  }
154
155  @Test public void absentHostnameMatches() throws Exception {
156    CertificatePinner certificatePinner = new CertificatePinner.Builder().build();
157    certificatePinner.check("example.com", certA1.certificate);
158  }
159
160  @Test public void successfulCheckForWildcardHostname() throws Exception {
161    CertificatePinner certificatePinner = new CertificatePinner.Builder()
162        .add("*.example.com", certA1Pin)
163        .build();
164
165    certificatePinner.check("a.example.com", certA1.certificate);
166  }
167
168  @Test public void successfulMatchAcceptsAnyMatchingCertificateForWildcardHostname() throws Exception {
169    CertificatePinner certificatePinner = new CertificatePinner.Builder()
170        .add("*.example.com", certB1Pin)
171        .build();
172
173    certificatePinner.check("a.example.com", certA1.certificate, certB1.certificate);
174  }
175
176  @Test public void unsuccessfulCheckForWildcardHostname() throws Exception {
177    CertificatePinner certificatePinner = new CertificatePinner.Builder()
178        .add("*.example.com", certA1Pin)
179        .build();
180
181    try {
182      certificatePinner.check("a.example.com", certB1.certificate);
183      fail();
184    } catch (SSLPeerUnverifiedException expected) {
185    }
186  }
187
188  @Test public void multipleCertificatesForOneWildcardHostname() throws Exception {
189    CertificatePinner certificatePinner = new CertificatePinner.Builder()
190        .add("*.example.com", certA1Pin, certB1Pin)
191        .build();
192
193    certificatePinner.check("a.example.com", certA1.certificate);
194    certificatePinner.check("a.example.com", certB1.certificate);
195  }
196
197  @Test public void successfulCheckForOneHostnameWithWildcardAndDirectCertificate() throws Exception {
198    CertificatePinner certificatePinner = new CertificatePinner.Builder()
199        .add("*.example.com", certA1Pin)
200        .add("a.example.com", certB1Pin)
201        .build();
202
203    certificatePinner.check("a.example.com", certA1.certificate);
204    certificatePinner.check("a.example.com", certB1.certificate);
205  }
206
207  @Test public void unsuccessfulCheckForOneHostnameWithWildcardAndDirectCertificate() throws Exception {
208    CertificatePinner certificatePinner = new CertificatePinner.Builder()
209        .add("*.example.com", certA1Pin)
210        .add("a.example.com", certB1Pin)
211        .build();
212
213    try {
214      certificatePinner.check("a.example.com", certC1.certificate);
215      fail();
216    } catch (SSLPeerUnverifiedException expected) {
217    }
218  }
219
220  @Test public void successfulFindMatchingPins() {
221    CertificatePinner certificatePinner = new CertificatePinner.Builder()
222        .add("first.com", certA1Pin, certB1Pin)
223        .add("second.com", certC1Pin)
224        .build();
225
226    Set<ByteString> expectedPins = setOf(certA1PinBase64, certB1PinBase64);
227    Set<ByteString> matchedPins = certificatePinner.findMatchingPins("first.com");
228
229    assertEquals(expectedPins, matchedPins);
230  }
231
232  @Test public void successfulFindMatchingPinsForWildcardAndDirectCertificates() {
233    CertificatePinner certificatePinner = new CertificatePinner.Builder()
234        .add("*.example.com", certA1Pin)
235        .add("a.example.com", certB1Pin)
236        .add("b.example.com", certC1Pin)
237        .build();
238
239    Set<ByteString> expectedPins = setOf(certA1PinBase64, certB1PinBase64);
240    Set<ByteString> matchedPins = certificatePinner.findMatchingPins("a.example.com");
241
242    assertEquals(expectedPins, matchedPins);
243  }
244
245  @Test public void wildcardHostnameShouldNotMatchThroughDot() throws Exception {
246    CertificatePinner certificatePinner = new CertificatePinner.Builder()
247        .add("*.example.com", certA1Pin)
248        .build();
249
250    assertNull(certificatePinner.findMatchingPins("example.com"));
251    assertNull(certificatePinner.findMatchingPins("a.b.example.com"));
252  }
253}
254