1 /* 2 * Copyright (C) 2016 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 android.security.cts; 18 19 import android.content.res.AssetManager; 20 import android.net.http.X509TrustManagerExtensions; 21 import android.platform.test.annotations.SecurityTest; 22 import android.test.AndroidTestCase; 23 24 import java.io.File; 25 import java.io.InputStream; 26 import java.util.Arrays; 27 import java.util.List; 28 import java.security.KeyStore; 29 import java.security.Provider; 30 import java.security.Security; 31 import java.security.cert.CertificateException; 32 import java.security.cert.CertificateFactory; 33 import java.security.cert.X509Certificate; 34 35 import javax.net.ssl.TrustManager; 36 import javax.net.ssl.TrustManagerFactory; 37 import javax.net.ssl.X509TrustManager; 38 39 /** 40 * Test that all {@link X509TrustManager} build the correct certificate chain during 41 * {@link X509TrustManagerExtensions#checkServerTrusted(X509Certificate[], String, String)} when 42 * multiple possible certificate paths exist. 43 */ 44 @SecurityTest 45 public class X509CertChainBuildingTest extends AndroidTestCase { 46 private static final String CERT_ASSET_DIR = "path_building"; 47 48 /* Certificates for tests. These are initialized in setUp. 49 * All certificates use 2048 bit RSA keys and SHA-256 digests unless otherwise specified. 50 * First certificate graph: 51 * 52 * rootA: A root CA 53 * rootASha1: rootA but with SHA-1 as the digest algorithm. 54 * rootB: Another root CA 55 * leaf1: Certificate issued by rootA 56 * rootAtoB: rootA cross signed by rootB 57 * rootBtoA: rootB cross signed by rootA 58 * 59 * [A] <-------> [B] 60 * | 61 * v 62 * [leaf1] 63 * Second certificate graph: 64 * 65 * intermediateA: Intermediate I issued by rootA 66 * intermediateB: Intermediate I issued by rootB 67 * leaf2: Leaf issued by I 68 * 69 * [A] [B] 70 * \ / 71 * [I] 72 * | 73 * v 74 * [leaf2] 75 * 76 * These can be generated by running cts/tools/utils/certificates.py 77 */ 78 private X509Certificate rootA; 79 private X509Certificate rootASha1; 80 private X509Certificate rootB; 81 private X509Certificate rootAtoB; 82 private X509Certificate rootBtoA; 83 private X509Certificate leaf1; 84 private X509Certificate leaf2; 85 private X509Certificate intermediateA; 86 private X509Certificate intermediateB; 87 88 @Override 89 public void setUp() throws Exception { 90 super.setUp(); 91 rootA = loadCertificate("a.pem"); 92 rootASha1 = loadCertificate("a_sha1.pem"); 93 rootB = loadCertificate("b.pem"); 94 leaf1 = loadCertificate("leaf1.pem"); 95 leaf2 = loadCertificate("leaf2.pem"); 96 rootAtoB = loadCertificate("a_to_b.pem"); 97 rootBtoA = loadCertificate("b_to_a.pem"); 98 intermediateA = loadCertificate("intermediate_a.pem"); 99 intermediateB = loadCertificate("intermediate_b.pem"); 100 } 101 102 public void testBasicChain() throws Exception { 103 assertExactPath(new X509Certificate[] {leaf1, rootA}, 104 new X509Certificate[] {leaf1}, 105 new X509Certificate[] {rootA}); 106 } 107 public void testCrossSign() throws Exception { 108 // First try a path that doesn't have the cross signed A to B certificate. 109 assertNoPath(new X509Certificate[] {leaf1, rootA}, new X509Certificate[] {rootB}); 110 // Now try with one valid chain (leaf1 -> rootAtoB -> rootB). 111 assertExactPath(new X509Certificate[] {leaf1, rootAtoB, rootB}, 112 new X509Certificate[] {leaf1, rootAtoB}, 113 new X509Certificate[] {rootB}); 114 // Now try with two possible chains present only one of which chains to a trusted root. 115 assertExactPath(new X509Certificate[] {leaf1, rootAtoB, rootB}, 116 new X509Certificate[] {leaf1, rootA, rootAtoB}, 117 new X509Certificate[] {rootB}); 118 } 119 120 public void testUntrustedLoop() throws Exception { 121 // Verify that providing all the certificates doesn't cause the path building to get stuck 122 // in the loop caused by the cross signed certificates. 123 assertNoPath(new X509Certificate[] {leaf1, rootAtoB, rootBtoA, rootA, rootB}, 124 new X509Certificate[] {}); 125 } 126 127 public void testAvoidCrossSigned() throws Exception { 128 // Check that leaf1 -> rootA is preferred over using the cross signed cert when both rootA 129 // and rootB are trusted. 130 assertExactPath(new X509Certificate[] {leaf1, rootA}, 131 new X509Certificate[] {leaf1, rootAtoB}, 132 new X509Certificate[] {rootA, rootB}); 133 } 134 135 public void testSelfIssuedPreferred() throws Exception { 136 // Check that when there are multiple possible trusted issuers that we prefer self-issued 137 // certificates over bridge versions of the same certificate. 138 assertExactPath(new X509Certificate[] {leaf1, rootA}, 139 new X509Certificate[] {leaf1, rootAtoB}, 140 new X509Certificate[] {rootA, rootAtoB, rootB}); 141 } 142 143 public void testBridgeCrossing() throws Exception { 144 // Check that when provided with leaf2, intermediateA, intermediateB, rootA that it builds 145 // the leaf2 -> intermediateB -> B path. 146 assertExactPath(new X509Certificate[] {leaf2, intermediateB, rootB}, 147 new X509Certificate[] {leaf2, intermediateA, rootA, intermediateB}, 148 new X509Certificate[] {rootB}); 149 } 150 151 public void testDigestOrdering() throws Exception { 152 // Check that leaf1 -> rootASha1 is valid 153 assertExactPath(new X509Certificate[] {leaf1, rootASha1}, 154 new X509Certificate[] {leaf1}, 155 new X509Certificate[] {rootASha1}); 156 // Check that when a SHA-256 and SHA-1 are available the SHA-256 cert is used 157 assertExactPath(new X509Certificate[] {leaf1, rootA}, 158 new X509Certificate[] {leaf1}, 159 new X509Certificate[] {rootASha1, rootA}); 160 } 161 162 private X509Certificate loadCertificate(String file) throws Exception { 163 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 164 AssetManager assetManager = getContext().getAssets(); 165 InputStream in = null; 166 try { 167 in = assetManager.open(new File(CERT_ASSET_DIR, file).toString()); 168 return (X509Certificate) certFactory.generateCertificate(in); 169 } finally { 170 if (in != null) { 171 in.close(); 172 } 173 } 174 } 175 176 private static X509TrustManager getTrustManager(KeyStore ks, Provider p) throws Exception { 177 TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", p); 178 tmf.init(ks); 179 for (TrustManager tm : tmf.getTrustManagers()) { 180 if (tm instanceof X509TrustManager) { 181 return (X509TrustManager) tm; 182 } 183 } 184 fail("Unable to find X509TrustManager"); 185 return null; 186 } 187 188 /** 189 * Asserts that all PKIX TrustManagerFactory providers build the expected certificate path or 190 * throw a {@code CertificateException} if {@code expected} is {@code null}. 191 */ 192 private static void assertExactPath(X509Certificate[] expected, X509Certificate[] bagOfCerts, 193 X509Certificate[] roots) throws Exception { 194 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 195 ks.load(null); 196 int i = 0; 197 for (X509Certificate root : roots) { 198 ks.setEntry(String.valueOf(i++), new KeyStore.TrustedCertificateEntry(root), null); 199 } 200 Provider[] providers = Security.getProviders("TrustManagerFactory.PKIX"); 201 assertNotNull(providers); 202 assertTrue("No providers found", providers.length != 0); 203 for (Provider p : providers) { 204 try { 205 X509TrustManager tm = getTrustManager(ks, p); 206 X509TrustManagerExtensions xtm = new X509TrustManagerExtensions(tm); 207 List<X509Certificate> result; 208 try { 209 result = xtm.checkServerTrusted(bagOfCerts, "RSA", null); 210 } catch (CertificateException e) { 211 if (expected == null) { 212 // Exception expected. 213 continue; 214 } 215 throw e; 216 } 217 if (expected == null) { 218 fail("checkServerTrusted expected to fail, instead returned: " + result); 219 } 220 assertEquals(Arrays.asList(expected), result); 221 } catch (Exception e) { 222 throw new Exception("Failed with provider " + p, e); 223 } 224 } 225 } 226 227 private static void assertNoPath(X509Certificate[] bagOfCerts, X509Certificate[] roots) 228 throws Exception { 229 assertExactPath(null, bagOfCerts, roots); 230 } 231 232 } 233