1 /* 2 * Copyright (C) 2015 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.net.config; 18 19 import android.app.Activity; 20 import android.os.Build; 21 import android.test.ActivityUnitTestCase; 22 import android.util.ArraySet; 23 import android.util.Pair; 24 import java.io.ByteArrayInputStream; 25 import java.io.IOException; 26 import java.net.Socket; 27 import java.net.URL; 28 import java.security.cert.Certificate; 29 import java.security.cert.CertificateFactory; 30 import java.security.cert.X509Certificate; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.HashSet; 34 import java.util.Set; 35 import javax.net.ssl.HttpsURLConnection; 36 import javax.net.ssl.SSLContext; 37 import javax.net.ssl.SSLHandshakeException; 38 import javax.net.ssl.TrustManager; 39 40 import com.android.org.conscrypt.TrustedCertificateStore; 41 42 public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 43 44 public NetworkSecurityConfigTests() { 45 super(Activity.class); 46 } 47 48 // SHA-256 of the G2 intermediate CA for android.com (as of 10/2015). 49 private static final byte[] G2_SPKI_SHA256 50 = hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776"); 51 52 private static final byte[] TEST_CA_BYTES 53 = hexToBytes( 54 "3082036130820249a003020102020900bd54597d6750ea62300d06092a86" 55 + "4886f70d01010b05003047310b3009060355040613025553310b30090603" 56 + "5504080c0243413110300e060355040a0c07416e64726f69643119301706" 57 + "035504030c104e53436f6e6669672054657374204341301e170d31363032" 58 + "32343030313130325a170d3136303332353030313130325a3047310b3009" 59 + "060355040613025553310b300906035504080c0243413110300e06035504" 60 + "0a0c07416e64726f69643119301706035504030c104e53436f6e66696720" 61 + "5465737420434130820122300d06092a864886f70d01010105000382010f" 62 + "003082010a0282010100e15ce8fd5794029841e760d68d6e0159c9c67630" 63 + "089775bc728d83dae7e29e23fe5f6e113b789f4c5b22f052300ec6d5faa5" 64 + "724432e7bac96682792ef6e9617c939c4329dce8788cbdf3a11b621fac9e" 65 + "2edbec2d7e5e07296bbb544b89263137a6a31573a2362e05ca8ff9c886bf" 66 + "52df4ff93c45475145a40a83f2670e23669220a5a4bf2c6860edb78d3022" 67 + "192fb5dc5e8c118f70870f89da292dfe522751462f020ed556653c8b07f8" 68 + "89712a6e8196c457a637439e3073d7d917ab55aa51a146826367f7b5922a" 69 + "64fb2f95099de21eb98341fa76faa79ffbda123fe5b8adc614b16174e8b0" 70 + "dfdac2bbc4d526d2487ad2b009d53996ec23ffbd732112efa66b02030100" 71 + "01a350304e301d0603551d0e04160414f66e1a95486c879edd60a5756bc2" 72 + "f1f4677e128e301f0603551d23041830168014f66e1a95486c879edd60a5" 73 + "756bc2f1f4677e128e300c0603551d13040530030101ff300d06092a8648" 74 + "86f70d01010b05000382010100d2856130dccae24e5f8901900d94bc642f" 75 + "85466ab7cfa1066399077a168cd4b56603a9e2af9d2e58aec13101e338a4" 76 + "8e95e9c7a84d7991f0d381d4965eaada1b80fbbd8277445f449babe64f53" 77 + "ba625387460b592a1a97b14b8251115e6610350021a6e716ae22b905f8d4" 78 + "eae24e668e71b12ab51fd2f2bb600e074487dec720c3db14dbca504844b6" 79 + "933bb0248283ea95464747689c37d706d4839c7d0e9bd86abf98ddce5d36" 80 + "8b38bfe5062353e28d5be378827fade1caa6bba3df9cd9ebf83d839eae52" 81 + "780181f31973f15f982686ba6d899f7b644fd1f26c8ebb99f4c986faaf4c" 82 + "1b9e3d9d391943ce3fb9fa2e631bd66b8ef3d47fd85acf09ea3a30f15f"); 83 84 private static final X509Certificate TEST_CA_CERT; 85 86 static { 87 try { 88 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 89 Certificate cert = factory.generateCertificate(new ByteArrayInputStream(TEST_CA_BYTES)); 90 TEST_CA_CERT = (X509Certificate) cert; 91 } catch (Exception e) { 92 throw new RuntimeException(e); 93 } 94 } 95 96 97 private static byte[] hexToBytes(String s) { 98 int len = s.length(); 99 byte[] data = new byte[len / 2]; 100 for (int i = 0; i < len; i += 2) { 101 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit( 102 s.charAt(i + 1), 16)); 103 } 104 return data; 105 } 106 107 108 /** 109 * Return a NetworkSecurityConfig that has an empty TrustAnchor set. This should always cause a 110 * SSLHandshakeException when used for a connection. 111 */ 112 private NetworkSecurityConfig getEmptyConfig() { 113 return new NetworkSecurityConfig.Builder().build(); 114 } 115 116 private NetworkSecurityConfig getSystemStoreConfig() { 117 return new NetworkSecurityConfig.Builder() 118 .addCertificatesEntryRef( 119 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) 120 .build(); 121 } 122 123 public void testEmptyConfig() throws Exception { 124 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 125 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 126 ConfigSource testSource = 127 new TestConfigSource(domainMap, getEmptyConfig()); 128 SSLContext context = TestUtils.getSSLContext(testSource); 129 TestUtils.assertConnectionFails(context, "android.com", 443); 130 } 131 132 public void testEmptyPerNetworkSecurityConfig() throws Exception { 133 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 134 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 135 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 136 new Domain("android.com", true), getEmptyConfig())); 137 NetworkSecurityConfig defaultConfig = getSystemStoreConfig(); 138 SSLContext context = TestUtils.getSSLContext(new TestConfigSource(domainMap, defaultConfig)); 139 TestUtils.assertConnectionFails(context, "android.com", 443); 140 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 141 } 142 143 public void testBadPin() throws Exception { 144 ArraySet<Pin> pins = new ArraySet<Pin>(); 145 pins.add(new Pin("SHA-256", new byte[0])); 146 NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() 147 .setPinSet(new PinSet(pins, Long.MAX_VALUE)) 148 .addCertificatesEntryRef( 149 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) 150 .build(); 151 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 152 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 153 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 154 new Domain("android.com", true), domain)); 155 SSLContext context 156 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig())); 157 TestUtils.assertConnectionFails(context, "android.com", 443); 158 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 159 } 160 161 public void testGoodPin() throws Exception { 162 ArraySet<Pin> pins = new ArraySet<Pin>(); 163 pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); 164 NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() 165 .setPinSet(new PinSet(pins, Long.MAX_VALUE)) 166 .addCertificatesEntryRef( 167 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) 168 .build(); 169 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 170 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 171 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 172 new Domain("android.com", true), domain)); 173 SSLContext context 174 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 175 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 176 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 177 } 178 179 public void testOverridePins() throws Exception { 180 // Use a bad pin + granting the system CA store the ability to override pins. 181 ArraySet<Pin> pins = new ArraySet<Pin>(); 182 pins.add(new Pin("SHA-256", new byte[0])); 183 NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() 184 .setPinSet(new PinSet(pins, Long.MAX_VALUE)) 185 .addCertificatesEntryRef( 186 new CertificatesEntryRef(SystemCertificateSource.getInstance(), true)) 187 .build(); 188 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 189 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 190 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 191 new Domain("android.com", true), domain)); 192 SSLContext context 193 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 194 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 195 } 196 197 public void testMostSpecificNetworkSecurityConfig() throws Exception { 198 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 199 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 200 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 201 new Domain("android.com", true), getEmptyConfig())); 202 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 203 new Domain("developer.android.com", false), getSystemStoreConfig())); 204 SSLContext context 205 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 206 TestUtils.assertConnectionFails(context, "android.com", 443); 207 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 208 } 209 210 public void testSubdomainIncluded() throws Exception { 211 // First try connecting to a subdomain of a domain entry that includes subdomains. 212 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 213 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 214 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 215 new Domain("android.com", true), getSystemStoreConfig())); 216 SSLContext context 217 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 218 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 219 // Now try without including subdomains. 220 domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 221 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 222 new Domain("android.com", false), getSystemStoreConfig())); 223 context = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 224 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 225 } 226 227 public void testConfigBuilderUsesParents() throws Exception { 228 // Check that a builder with a parent uses the parent's values when non is set. 229 NetworkSecurityConfig config = new NetworkSecurityConfig.Builder() 230 .setParent(NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1)) 231 .build(); 232 assert(!config.getTrustAnchors().isEmpty()); 233 } 234 235 public void testConfigBuilderParentLoop() throws Exception { 236 NetworkSecurityConfig.Builder config1 = new NetworkSecurityConfig.Builder(); 237 NetworkSecurityConfig.Builder config2 = new NetworkSecurityConfig.Builder(); 238 config1.setParent(config2); 239 try { 240 config2.setParent(config1); 241 fail("Loop in NetworkSecurityConfig parents"); 242 } catch (IllegalArgumentException expected) { 243 } 244 } 245 246 public void testWithUrlConnection() throws Exception { 247 ArraySet<Pin> pins = new ArraySet<Pin>(); 248 pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); 249 NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() 250 .setPinSet(new PinSet(pins, Long.MAX_VALUE)) 251 .addCertificatesEntryRef( 252 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) 253 .build(); 254 ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap 255 = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); 256 domainMap.add(new Pair<Domain, NetworkSecurityConfig>( 257 new Domain("android.com", true), domain)); 258 SSLContext context 259 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); 260 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 261 TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443); 262 TestUtils.assertUrlConnectionFails(context, "google.com", 443); 263 } 264 265 public void testUserAddedCaOptIn() throws Exception { 266 TrustedCertificateStore store = new TrustedCertificateStore(); 267 try { 268 // Install the test CA. 269 store.installCertificate(TEST_CA_CERT); 270 NetworkSecurityConfig preNConfig = 271 NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.M, 1).build(); 272 NetworkSecurityConfig nConfig = 273 NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1).build(); 274 Set<TrustAnchor> preNAnchors = preNConfig.getTrustAnchors(); 275 Set<TrustAnchor> nAnchors = nConfig.getTrustAnchors(); 276 Set<X509Certificate> preNCerts = new HashSet<X509Certificate>(); 277 for (TrustAnchor anchor : preNAnchors) { 278 preNCerts.add(anchor.certificate); 279 } 280 Set<X509Certificate> nCerts = new HashSet<X509Certificate>(); 281 for (TrustAnchor anchor : nAnchors) { 282 nCerts.add(anchor.certificate); 283 } 284 assertTrue(preNCerts.contains(TEST_CA_CERT)); 285 assertFalse(nCerts.contains(TEST_CA_CERT)); 286 } finally { 287 // Delete the user added CA. We don't know the alias so just delete them all. 288 for (String alias : store.aliases()) { 289 if (store.isUser(alias)) { 290 try { 291 store.deleteCertificateEntry(alias); 292 } catch (Exception ignored) { 293 } 294 } 295 } 296 } 297 } 298 } 299