1/* 2 * Copyright (C) 2011 The Android Open Source Project 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 */ 16 17package org.conscrypt; 18 19import java.io.File; 20import java.io.FileWriter; 21import java.security.cert.Certificate; 22import java.security.cert.CertificateException; 23import java.security.cert.X509Certificate; 24import java.security.KeyStore; 25import java.security.MessageDigest; 26import java.security.Principal; 27import java.util.ArrayList; 28import java.util.Arrays; 29import java.util.List; 30import javax.net.ssl.SSLPeerUnverifiedException; 31import javax.net.ssl.SSLSession; 32import javax.net.ssl.SSLSessionContext; 33import javax.net.ssl.TrustManagerFactory; 34import javax.net.ssl.X509TrustManager; 35import junit.framework.TestCase; 36import libcore.java.security.TestKeyStore; 37 38public class TrustManagerImplTest extends TestCase { 39 40 private List<File> tmpFiles = new ArrayList<File>(); 41 42 private String getFingerprint(X509Certificate cert) throws Exception { 43 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 44 byte[] encoded = cert.getPublicKey().getEncoded(); 45 byte[] fingerprint = dgst.digest(encoded); 46 return Hex.bytesToHexString(fingerprint); 47 } 48 49 private String writeTmpPinFile(String text) throws Exception { 50 File tmp = File.createTempFile("pins", null); 51 FileWriter fstream = new FileWriter(tmp); 52 fstream.write(text); 53 fstream.close(); 54 tmpFiles.add(tmp); 55 return tmp.getPath(); 56 } 57 58 @Override 59 public void tearDown() throws Exception { 60 try { 61 for (File f : tmpFiles) { 62 f.delete(); 63 } 64 tmpFiles.clear(); 65 } finally { 66 super.tearDown(); 67 } 68 } 69 70 /** 71 * Ensure that our non-standard behavior of learning to trust new 72 * intermediate CAs does not regress. http://b/3404902 73 */ 74 public void testLearnIntermediate() throws Exception { 75 // chain3 should be server/intermediate/root 76 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 77 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 78 X509Certificate root = chain3[2]; 79 X509Certificate intermediate = chain3[1]; 80 X509Certificate server = chain3[0]; 81 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 82 X509Certificate[] chain1 = new X509Certificate[] { server }; 83 84 // Normal behavior 85 assertValid(chain3, trustManager(root)); 86 assertValid(chain2, trustManager(root)); 87 assertInvalid(chain1, trustManager(root)); 88 assertValid(chain3, trustManager(intermediate)); 89 assertValid(chain2, trustManager(intermediate)); 90 assertValid(chain1, trustManager(intermediate)); 91 assertValid(chain3, trustManager(server)); 92 assertValid(chain2, trustManager(server)); 93 assertValid(chain1, trustManager(server)); 94 95 // non-standard behavior 96 X509TrustManager tm = trustManager(root); 97 // fail on short chain with only root trusted 98 assertInvalid(chain1, tm); 99 // succeed on longer chain, learn intermediate 100 assertValid(chain2, tm); 101 // now we can validate the short chain 102 assertValid(chain1, tm); 103 } 104 105 // We should ignore duplicate cruft in the certificate chain 106 // See https://code.google.com/p/android/issues/detail?id=52295 http://b/8313312 107 public void testDuplicateInChain() throws Exception { 108 // chain3 should be server/intermediate/root 109 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 110 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 111 X509Certificate root = chain3[2]; 112 X509Certificate intermediate = chain3[1]; 113 X509Certificate server = chain3[0]; 114 115 X509Certificate[] chain4 = new X509Certificate[] { server, intermediate, 116 server, intermediate 117 }; 118 assertValid(chain4, trustManager(root)); 119 } 120 121 public void testGetFullChain() throws Exception { 122 // build the trust manager 123 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 124 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 125 X509Certificate root = chain3[2]; 126 X509TrustManager tm = trustManager(root); 127 128 // build the chains we'll use for testing 129 X509Certificate intermediate = chain3[1]; 130 X509Certificate server = chain3[0]; 131 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 132 X509Certificate[] chain1 = new X509Certificate[] { server }; 133 134 assertTrue(tm instanceof TrustManagerImpl); 135 TrustManagerImpl tmi = (TrustManagerImpl) tm; 136 List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", new MySSLSession( 137 "purple.com")); 138 assertEquals(Arrays.asList(chain3), certs); 139 certs = tmi.checkServerTrusted(chain1, "RSA", new MySSLSession("purple.com")); 140 assertEquals(Arrays.asList(chain3), certs); 141 } 142 143 public void testCertPinning() throws Exception { 144 // chain3 should be server/intermediate/root 145 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 146 X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain(); 147 X509Certificate root = chain3[2]; 148 X509Certificate intermediate = chain3[1]; 149 X509Certificate server = chain3[0]; 150 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 151 X509Certificate[] chain1 = new X509Certificate[] { server }; 152 153 // test without a hostname, expecting failure 154 assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null); 155 // test without a hostname, expecting success 156 assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3); 157 // test an unpinned hostname that should fail 158 assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com"); 159 // test an unpinned hostname that should succeed 160 assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3); 161 // test a pinned hostname that should fail 162 assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com"); 163 // test a pinned hostname that should succeed 164 assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com", 165 chain2); 166 // test a pinned hostname that chains to user installed that should succeed 167 assertValidPinned(chain2, trustManagerUserInstalled( 168 (X509Certificate)TestKeyStore.getIntermediateCa2().getPrivateKey("RSA", "RSA") 169 .getCertificateChain()[1], intermediate, "gugle.com", server), "gugle.com", 170 chain2, true); 171 } 172 173 private X509TrustManager trustManager(X509Certificate ca) throws Exception { 174 KeyStore keyStore = TestKeyStore.createKeyStore(); 175 keyStore.setCertificateEntry("alias", ca); 176 177 return new TrustManagerImpl(keyStore); 178 } 179 180 private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin) 181 throws Exception { 182 // build the cert pin manager 183 CertPinManager cm = certManager(hostname, pin); 184 // insert it into the trust manager 185 KeyStore keyStore = TestKeyStore.createKeyStore(); 186 keyStore.setCertificateEntry("alias", ca); 187 return new TrustManagerImpl(keyStore, cm); 188 } 189 190 private TrustManagerImpl trustManagerUserInstalled( 191 X509Certificate caKeyStore, X509Certificate caUserStore, String hostname, 192 X509Certificate pin) throws Exception { 193 // build the cert pin manager 194 CertPinManager cm = certManager(hostname, pin); 195 196 // install at least one cert in the store (requirement) 197 KeyStore keyStore = TestKeyStore.createKeyStore(); 198 keyStore.setCertificateEntry("alias", caKeyStore); 199 200 // install a cert into the user installed store 201 final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir")); 202 final File DIR_TEST = new File(DIR_TEMP, "test"); 203 final File system = new File(DIR_TEST, "system-test"); 204 final File added = new File(DIR_TEST, "added-test"); 205 final File deleted = new File(DIR_TEST, "deleted-test"); 206 207 TrustedCertificateStore tcs = new TrustedCertificateStore(system, added, deleted); 208 added.mkdirs(); 209 tcs.installCertificate(caUserStore); 210 return new TrustManagerImpl(keyStore, cm, tcs); 211 } 212 213 private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception { 214 String pinString = ""; 215 if (pin != null) { 216 pinString = hostname + "=true|" + getFingerprint(pin); 217 } 218 // write it to a pinfile 219 String path = writeTmpPinFile(pinString); 220 // build the certpinmanager 221 return new CertPinManager(path, new TrustedCertificateStore()); 222 } 223 224 private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception { 225 if (tm instanceof TrustManagerImpl) { 226 TrustManagerImpl tmi = (TrustManagerImpl) tm; 227 tmi.checkServerTrusted(chain, "RSA"); 228 } 229 tm.checkServerTrusted(chain, "RSA"); 230 } 231 232 private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, 233 X509Certificate[] fullChain) throws Exception { 234 assertValidPinned(chain, tm, hostname, fullChain, false); 235 } 236 237 private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, 238 X509Certificate[] fullChain, boolean expectUserInstalled) 239 throws Exception { 240 if (tm instanceof TrustManagerImpl) { 241 TrustManagerImpl tmi = (TrustManagerImpl) tm; 242 List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", 243 new MySSLSession(hostname)); 244 assertEquals(checkedChain, Arrays.asList(fullChain)); 245 boolean chainContainsUserInstalled = false; 246 for (X509Certificate cert : checkedChain) { 247 if (tmi.isUserAddedCertificate(cert)) { 248 chainContainsUserInstalled = true; 249 } 250 } 251 assertEquals(expectUserInstalled, chainContainsUserInstalled); 252 } 253 tm.checkServerTrusted(chain, "RSA"); 254 } 255 256 private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) { 257 try { 258 tm.checkClientTrusted(chain, "RSA"); 259 fail(); 260 } catch (CertificateException expected) { 261 } 262 try { 263 tm.checkServerTrusted(chain, "RSA"); 264 fail(); 265 } catch (CertificateException expected) { 266 } 267 } 268 269 private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname) 270 throws Exception { 271 assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl); 272 try { 273 TrustManagerImpl tmi = (TrustManagerImpl) tm; 274 tmi.checkServerTrusted(chain, "RSA", new MySSLSession(hostname)); 275 fail(); 276 } catch (CertificateException expected) { 277 } 278 } 279 280 private class MySSLSession implements SSLSession { 281 private final String hostname; 282 283 public MySSLSession(String hostname) { 284 this.hostname = hostname; 285 } 286 287 @Override 288 public int getApplicationBufferSize() { 289 throw new UnsupportedOperationException(); 290 } 291 292 @Override 293 public String getCipherSuite() { 294 throw new UnsupportedOperationException(); 295 } 296 297 @Override 298 public long getCreationTime() { 299 throw new UnsupportedOperationException(); 300 } 301 302 @Override 303 public byte[] getId() { 304 throw new UnsupportedOperationException(); 305 } 306 307 @Override 308 public long getLastAccessedTime() { 309 throw new UnsupportedOperationException(); 310 } 311 312 @Override 313 public Certificate[] getLocalCertificates() { 314 throw new UnsupportedOperationException(); 315 } 316 317 @Override 318 public Principal getLocalPrincipal() { 319 throw new UnsupportedOperationException(); 320 } 321 322 @Override 323 public int getPacketBufferSize() { 324 throw new UnsupportedOperationException(); 325 } 326 327 @Override 328 public javax.security.cert.X509Certificate[] getPeerCertificateChain() 329 throws SSLPeerUnverifiedException { 330 throw new UnsupportedOperationException(); 331 } 332 333 @Override 334 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { 335 throw new UnsupportedOperationException(); 336 } 337 338 @Override 339 public String getPeerHost() { 340 return hostname; 341 } 342 343 @Override 344 public int getPeerPort() { 345 throw new UnsupportedOperationException(); 346 } 347 348 @Override 349 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 350 throw new UnsupportedOperationException(); 351 } 352 353 @Override 354 public String getProtocol() { 355 throw new UnsupportedOperationException(); 356 } 357 358 @Override 359 public SSLSessionContext getSessionContext() { 360 throw new UnsupportedOperationException(); 361 } 362 363 @Override 364 public Object getValue(String name) { 365 throw new UnsupportedOperationException(); 366 } 367 368 @Override 369 public String[] getValueNames() { 370 throw new UnsupportedOperationException(); 371 } 372 373 @Override 374 public void invalidate() { 375 throw new UnsupportedOperationException(); 376 } 377 378 @Override 379 public boolean isValid() { 380 throw new UnsupportedOperationException(); 381 } 382 383 @Override 384 public void putValue(String name, Object value) { 385 throw new UnsupportedOperationException(); 386 } 387 388 @Override 389 public void removeValue(String name) { 390 throw new UnsupportedOperationException(); 391 } 392 } 393} 394