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 17 package org.conscrypt; 18 19 import java.io.File; 20 import java.io.FileWriter; 21 import java.security.cert.Certificate; 22 import java.security.cert.CertificateException; 23 import java.security.cert.X509Certificate; 24 import java.security.KeyStore; 25 import java.security.MessageDigest; 26 import java.security.Principal; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 import javax.net.ssl.SSLPeerUnverifiedException; 31 import javax.net.ssl.SSLSession; 32 import javax.net.ssl.SSLSessionContext; 33 import javax.net.ssl.TrustManagerFactory; 34 import javax.net.ssl.X509TrustManager; 35 import junit.framework.TestCase; 36 import libcore.java.security.TestKeyStore; 37 38 public 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 IntegralToString.bytesToHexString(fingerprint, false); 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 String algorithm = TrustManagerFactory.getDefaultAlgorithm(); 178 TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); 179 tmf.init(keyStore); 180 return (X509TrustManager) tmf.getTrustManagers()[0]; 181 } 182 183 private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin) 184 throws Exception { 185 // build the cert pin manager 186 CertPinManager cm = certManager(hostname, pin); 187 // insert it into the trust manager 188 KeyStore keyStore = TestKeyStore.createKeyStore(); 189 keyStore.setCertificateEntry("alias", ca); 190 return new TrustManagerImpl(keyStore, cm); 191 } 192 193 private TrustManagerImpl trustManagerUserInstalled( 194 X509Certificate caKeyStore, X509Certificate caUserStore, String hostname, 195 X509Certificate pin) throws Exception { 196 // build the cert pin manager 197 CertPinManager cm = certManager(hostname, pin); 198 199 // install at least one cert in the store (requirement) 200 KeyStore keyStore = TestKeyStore.createKeyStore(); 201 keyStore.setCertificateEntry("alias", caKeyStore); 202 203 // install a cert into the user installed store 204 final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir")); 205 final File DIR_TEST = new File(DIR_TEMP, "test"); 206 final File system = new File(DIR_TEST, "system-test"); 207 final File added = new File(DIR_TEST, "added-test"); 208 final File deleted = new File(DIR_TEST, "deleted-test"); 209 210 TrustedCertificateStore tcs = new TrustedCertificateStore(system, added, deleted); 211 added.mkdirs(); 212 tcs.installCertificate(caUserStore); 213 return new TrustManagerImpl(keyStore, cm, tcs); 214 } 215 216 private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception { 217 String pinString = ""; 218 if (pin != null) { 219 pinString = hostname + "=true|" + getFingerprint(pin); 220 } 221 // write it to a pinfile 222 String path = writeTmpPinFile(pinString); 223 // build the certpinmanager 224 return new CertPinManager(path, new TrustedCertificateStore()); 225 } 226 227 private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception { 228 if (tm instanceof TrustManagerImpl) { 229 TrustManagerImpl tmi = (TrustManagerImpl) tm; 230 tmi.checkServerTrusted(chain, "RSA"); 231 } 232 tm.checkServerTrusted(chain, "RSA"); 233 } 234 235 private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, 236 X509Certificate[] fullChain) throws Exception { 237 assertValidPinned(chain, tm, hostname, fullChain, false); 238 } 239 240 private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, 241 X509Certificate[] fullChain, boolean expectUserInstalled) 242 throws Exception { 243 if (tm instanceof TrustManagerImpl) { 244 TrustManagerImpl tmi = (TrustManagerImpl) tm; 245 List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", 246 new MySSLSession(hostname)); 247 assertEquals(checkedChain, Arrays.asList(fullChain)); 248 boolean chainContainsUserInstalled = false; 249 for (X509Certificate cert : checkedChain) { 250 if (tmi.isUserAddedCertificate(cert)) { 251 chainContainsUserInstalled = true; 252 } 253 } 254 assertEquals(expectUserInstalled, chainContainsUserInstalled); 255 } 256 tm.checkServerTrusted(chain, "RSA"); 257 } 258 259 private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) { 260 try { 261 tm.checkClientTrusted(chain, "RSA"); 262 fail(); 263 } catch (CertificateException expected) { 264 } 265 try { 266 tm.checkServerTrusted(chain, "RSA"); 267 fail(); 268 } catch (CertificateException expected) { 269 } 270 } 271 272 private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname) 273 throws Exception { 274 assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl); 275 try { 276 TrustManagerImpl tmi = (TrustManagerImpl) tm; 277 tmi.checkServerTrusted(chain, "RSA", new MySSLSession(hostname)); 278 fail(); 279 } catch (CertificateException expected) { 280 } 281 } 282 283 private class MySSLSession implements SSLSession { 284 private final String hostname; 285 286 public MySSLSession(String hostname) { 287 this.hostname = hostname; 288 } 289 290 @Override 291 public int getApplicationBufferSize() { 292 throw new UnsupportedOperationException(); 293 } 294 295 @Override 296 public String getCipherSuite() { 297 throw new UnsupportedOperationException(); 298 } 299 300 @Override 301 public long getCreationTime() { 302 throw new UnsupportedOperationException(); 303 } 304 305 @Override 306 public byte[] getId() { 307 throw new UnsupportedOperationException(); 308 } 309 310 @Override 311 public long getLastAccessedTime() { 312 throw new UnsupportedOperationException(); 313 } 314 315 @Override 316 public Certificate[] getLocalCertificates() { 317 throw new UnsupportedOperationException(); 318 } 319 320 @Override 321 public Principal getLocalPrincipal() { 322 throw new UnsupportedOperationException(); 323 } 324 325 @Override 326 public int getPacketBufferSize() { 327 throw new UnsupportedOperationException(); 328 } 329 330 @Override 331 public javax.security.cert.X509Certificate[] getPeerCertificateChain() 332 throws SSLPeerUnverifiedException { 333 throw new UnsupportedOperationException(); 334 } 335 336 @Override 337 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { 338 throw new UnsupportedOperationException(); 339 } 340 341 @Override 342 public String getPeerHost() { 343 return hostname; 344 } 345 346 @Override 347 public int getPeerPort() { 348 throw new UnsupportedOperationException(); 349 } 350 351 @Override 352 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 353 throw new UnsupportedOperationException(); 354 } 355 356 @Override 357 public String getProtocol() { 358 throw new UnsupportedOperationException(); 359 } 360 361 @Override 362 public SSLSessionContext getSessionContext() { 363 throw new UnsupportedOperationException(); 364 } 365 366 @Override 367 public Object getValue(String name) { 368 throw new UnsupportedOperationException(); 369 } 370 371 @Override 372 public String[] getValueNames() { 373 throw new UnsupportedOperationException(); 374 } 375 376 @Override 377 public void invalidate() { 378 throw new UnsupportedOperationException(); 379 } 380 381 @Override 382 public boolean isValid() { 383 throw new UnsupportedOperationException(); 384 } 385 386 @Override 387 public void putValue(String name, Object value) { 388 throw new UnsupportedOperationException(); 389 } 390 391 @Override 392 public void removeValue(String name) { 393 throw new UnsupportedOperationException(); 394 } 395 } 396 } 397