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