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