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.CertificateException; 22 import java.security.cert.X509Certificate; 23 import java.security.KeyStore; 24 import java.security.MessageDigest; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import javax.net.ssl.TrustManager; 29 import javax.net.ssl.TrustManagerFactory; 30 import javax.net.ssl.X509TrustManager; 31 import junit.framework.TestCase; 32 import libcore.java.security.TestKeyStore; 33 34 public class TrustManagerImplTest extends TestCase { 35 36 private List<File> tmpFiles = new ArrayList<File>(); 37 38 private String getFingerprint(X509Certificate cert) throws Exception { 39 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 40 byte[] encoded = cert.getPublicKey().getEncoded(); 41 byte[] fingerprint = dgst.digest(encoded); 42 return IntegralToString.bytesToHexString(fingerprint, false); 43 } 44 45 private String writeTmpPinFile(String text) throws Exception { 46 File tmp = File.createTempFile("pins", null); 47 FileWriter fstream = new FileWriter(tmp); 48 fstream.write(text); 49 fstream.close(); 50 tmpFiles.add(tmp); 51 return tmp.getPath(); 52 } 53 54 @Override 55 public void tearDown() throws Exception { 56 try { 57 for (File f : tmpFiles) { 58 f.delete(); 59 } 60 tmpFiles.clear(); 61 } finally { 62 super.tearDown(); 63 } 64 } 65 66 /** 67 * Ensure that our non-standard behavior of learning to trust new 68 * intermediate CAs does not regress. http://b/3404902 69 */ 70 public void testLearnIntermediate() throws Exception { 71 // chain3 should be server/intermediate/root 72 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 73 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 74 X509Certificate root = chain3[2]; 75 X509Certificate intermediate = chain3[1]; 76 X509Certificate server = chain3[0]; 77 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 78 X509Certificate[] chain1 = new X509Certificate[] { server }; 79 80 // Normal behavior 81 assertValid(chain3, trustManager(root)); 82 assertValid(chain2, trustManager(root)); 83 assertInvalid(chain1, trustManager(root)); 84 assertValid(chain3, trustManager(intermediate)); 85 assertValid(chain2, trustManager(intermediate)); 86 assertValid(chain1, trustManager(intermediate)); 87 assertValid(chain3, trustManager(server)); 88 assertValid(chain2, trustManager(server)); 89 assertValid(chain1, trustManager(server)); 90 91 // non-standard behavior 92 X509TrustManager tm = trustManager(root); 93 // fail on short chain with only root trusted 94 assertInvalid(chain1, tm); 95 // succeed on longer chain, learn intermediate 96 assertValid(chain2, tm); 97 // now we can validate the short chain 98 assertValid(chain1, tm); 99 } 100 101 // We should ignore duplicate cruft in the certificate chain 102 // See https://code.google.com/p/android/issues/detail?id=52295 http://b/8313312 103 public void testDuplicateInChain() throws Exception { 104 // chain3 should be server/intermediate/root 105 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 106 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 107 X509Certificate root = chain3[2]; 108 X509Certificate intermediate = chain3[1]; 109 X509Certificate server = chain3[0]; 110 111 X509Certificate[] chain4 = new X509Certificate[] { server, intermediate, 112 server, intermediate 113 }; 114 assertValid(chain4, trustManager(root)); 115 } 116 117 public void testGetFullChain() throws Exception { 118 // build the trust manager 119 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 120 X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); 121 X509Certificate root = chain3[2]; 122 X509TrustManager tm = trustManager(root); 123 124 // build the chains we'll use for testing 125 X509Certificate intermediate = chain3[1]; 126 X509Certificate server = chain3[0]; 127 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 128 X509Certificate[] chain1 = new X509Certificate[] { server }; 129 130 assertTrue(tm instanceof TrustManagerImpl); 131 TrustManagerImpl tmi = (TrustManagerImpl) tm; 132 List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com"); 133 assertEquals(Arrays.asList(chain3), certs); 134 certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com"); 135 assertEquals(Arrays.asList(chain3), certs); 136 } 137 138 public void testCertPinning() throws Exception { 139 // chain3 should be server/intermediate/root 140 KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); 141 X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain(); 142 X509Certificate root = chain3[2]; 143 X509Certificate intermediate = chain3[1]; 144 X509Certificate server = chain3[0]; 145 X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; 146 X509Certificate[] chain1 = new X509Certificate[] { server }; 147 148 // test without a hostname, expecting failure 149 assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null); 150 // test without a hostname, expecting success 151 assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3); 152 // test an unpinned hostname that should fail 153 assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com"); 154 // test an unpinned hostname that should succeed 155 assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3); 156 // test a pinned hostname that should fail 157 assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com"); 158 // test a pinned hostname that should succeed 159 assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com", 160 chain2); 161 } 162 163 private X509TrustManager trustManager(X509Certificate ca) throws Exception { 164 KeyStore keyStore = TestKeyStore.createKeyStore(); 165 keyStore.setCertificateEntry("alias", ca); 166 167 String algorithm = TrustManagerFactory.getDefaultAlgorithm(); 168 TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); 169 tmf.init(keyStore); 170 return (X509TrustManager) tmf.getTrustManagers()[0]; 171 } 172 173 private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin) 174 throws Exception { 175 // build the cert pin manager 176 CertPinManager cm = certManager(hostname, pin); 177 // insert it into the trust manager 178 KeyStore keyStore = TestKeyStore.createKeyStore(); 179 keyStore.setCertificateEntry("alias", ca); 180 return new TrustManagerImpl(keyStore, cm); 181 } 182 183 private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception { 184 String pinString = ""; 185 if (pin != null) { 186 pinString = hostname + "=true|" + getFingerprint(pin); 187 } 188 // write it to a pinfile 189 String path = writeTmpPinFile(pinString); 190 // build the certpinmanager 191 return new CertPinManager(path, new TrustedCertificateStore()); 192 } 193 194 private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception { 195 if (tm instanceof TrustManagerImpl) { 196 TrustManagerImpl tmi = (TrustManagerImpl) tm; 197 tmi.checkServerTrusted(chain, "RSA"); 198 } 199 tm.checkServerTrusted(chain, "RSA"); 200 } 201 202 private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, 203 X509Certificate[] fullChain) throws Exception { 204 if (tm instanceof TrustManagerImpl) { 205 TrustManagerImpl tmi = (TrustManagerImpl) tm; 206 List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", hostname); 207 assertEquals(checkedChain, Arrays.asList(fullChain)); 208 } 209 tm.checkServerTrusted(chain, "RSA"); 210 } 211 212 private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) { 213 try { 214 tm.checkClientTrusted(chain, "RSA"); 215 fail(); 216 } catch (CertificateException expected) { 217 } 218 try { 219 tm.checkServerTrusted(chain, "RSA"); 220 fail(); 221 } catch (CertificateException expected) { 222 } 223 } 224 225 private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname) 226 throws Exception { 227 assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl); 228 try { 229 TrustManagerImpl tmi = (TrustManagerImpl) tm; 230 tmi.checkServerTrusted(chain, "RSA", hostname); 231 fail(); 232 } catch (CertificateException expected) { 233 } 234 } 235 } 236