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.content.Context; 20import android.test.AndroidTestCase; 21import android.test.MoreAsserts; 22import android.util.ArraySet; 23import android.util.Pair; 24import java.io.IOException; 25import java.net.InetAddress; 26import java.net.Socket; 27import java.net.URL; 28import java.security.KeyStore; 29import java.security.Provider; 30import java.security.Security; 31import java.security.cert.X509Certificate; 32import java.util.ArrayList; 33import java.util.Collections; 34import java.util.Set; 35import javax.net.ssl.HttpsURLConnection; 36import javax.net.ssl.SSLContext; 37import javax.net.ssl.SSLHandshakeException; 38import javax.net.ssl.SSLSocket; 39import javax.net.ssl.TrustManager; 40import javax.net.ssl.TrustManagerFactory; 41 42public class XmlConfigTests extends AndroidTestCase { 43 44 private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA"; 45 46 public void testEmptyConfigFile() throws Exception { 47 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config); 48 ApplicationConfig appConfig = new ApplicationConfig(source); 49 assertFalse(appConfig.hasPerDomainConfigs()); 50 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 51 assertNotNull(config); 52 // Check defaults. 53 assertTrue(config.isCleartextTrafficPermitted()); 54 assertFalse(config.isHstsEnforced()); 55 assertFalse(config.getTrustAnchors().isEmpty()); 56 PinSet pinSet = config.getPins(); 57 assertTrue(pinSet.pins.isEmpty()); 58 // Try some connections. 59 SSLContext context = TestUtils.getSSLContext(source); 60 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 61 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 62 TestUtils.assertUrlConnectionSucceeds(context, "google.com", 443); 63 } 64 65 public void testEmptyAnchors() throws Exception { 66 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust); 67 ApplicationConfig appConfig = new ApplicationConfig(source); 68 assertFalse(appConfig.hasPerDomainConfigs()); 69 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 70 assertNotNull(config); 71 // Check defaults. 72 assertTrue(config.isCleartextTrafficPermitted()); 73 assertFalse(config.isHstsEnforced()); 74 assertTrue(config.getTrustAnchors().isEmpty()); 75 PinSet pinSet = config.getPins(); 76 assertTrue(pinSet.pins.isEmpty()); 77 SSLContext context = TestUtils.getSSLContext(source); 78 TestUtils.assertConnectionFails(context, "android.com", 443); 79 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 80 TestUtils.assertUrlConnectionFails(context, "google.com", 443); 81 } 82 83 public void testBasicDomainConfig() throws Exception { 84 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1); 85 ApplicationConfig appConfig = new ApplicationConfig(source); 86 assertTrue(appConfig.hasPerDomainConfigs()); 87 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 88 assertNotNull(config); 89 // Check defaults. 90 assertTrue(config.isCleartextTrafficPermitted()); 91 assertFalse(config.isHstsEnforced()); 92 assertTrue(config.getTrustAnchors().isEmpty()); 93 PinSet pinSet = config.getPins(); 94 assertTrue(pinSet.pins.isEmpty()); 95 // Check android.com. 96 config = appConfig.getConfigForHostname("android.com"); 97 assertTrue(config.isCleartextTrafficPermitted()); 98 assertFalse(config.isHstsEnforced()); 99 assertFalse(config.getTrustAnchors().isEmpty()); 100 pinSet = config.getPins(); 101 assertTrue(pinSet.pins.isEmpty()); 102 // Try connections. 103 SSLContext context = TestUtils.getSSLContext(source); 104 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 105 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 106 TestUtils.assertUrlConnectionFails(context, "google.com", 443); 107 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 108 // Check that sockets created without the hostname fail with per-domain configs 109 SSLSocket socket = (SSLSocket) context.getSocketFactory() 110 .createSocket(InetAddress.getByName("android.com"), 443); 111 try { 112 socket.startHandshake(); 113 socket.getInputStream(); 114 fail(); 115 } catch (IOException expected) { 116 } 117 } 118 119 public void testBasicPinning() throws Exception { 120 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1); 121 ApplicationConfig appConfig = new ApplicationConfig(source); 122 assertTrue(appConfig.hasPerDomainConfigs()); 123 // Check android.com. 124 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 125 PinSet pinSet = config.getPins(); 126 assertFalse(pinSet.pins.isEmpty()); 127 // Try connections. 128 SSLContext context = TestUtils.getSSLContext(source); 129 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 130 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 131 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 132 } 133 134 public void testExpiredPin() throws Exception { 135 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin); 136 ApplicationConfig appConfig = new ApplicationConfig(source); 137 assertTrue(appConfig.hasPerDomainConfigs()); 138 // Check android.com. 139 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 140 PinSet pinSet = config.getPins(); 141 assertFalse(pinSet.pins.isEmpty()); 142 // Try connections. 143 SSLContext context = TestUtils.getSSLContext(source); 144 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 145 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 146 } 147 148 public void testOverridesPins() throws Exception { 149 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins); 150 ApplicationConfig appConfig = new ApplicationConfig(source); 151 assertTrue(appConfig.hasPerDomainConfigs()); 152 // Check android.com. 153 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 154 PinSet pinSet = config.getPins(); 155 assertFalse(pinSet.pins.isEmpty()); 156 // Try connections. 157 SSLContext context = TestUtils.getSSLContext(source); 158 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 159 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 160 } 161 162 public void testBadPin() throws Exception { 163 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin); 164 ApplicationConfig appConfig = new ApplicationConfig(source); 165 assertTrue(appConfig.hasPerDomainConfigs()); 166 // Check android.com. 167 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 168 PinSet pinSet = config.getPins(); 169 assertFalse(pinSet.pins.isEmpty()); 170 // Try connections. 171 SSLContext context = TestUtils.getSSLContext(source); 172 TestUtils.assertConnectionFails(context, "android.com", 443); 173 TestUtils.assertUrlConnectionFails(context, "android.com", 443); 174 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 175 } 176 177 public void testMultipleDomains() throws Exception { 178 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains); 179 ApplicationConfig appConfig = new ApplicationConfig(source); 180 assertTrue(appConfig.hasPerDomainConfigs()); 181 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 182 assertTrue(config.isCleartextTrafficPermitted()); 183 assertFalse(config.isHstsEnforced()); 184 assertFalse(config.getTrustAnchors().isEmpty()); 185 PinSet pinSet = config.getPins(); 186 assertTrue(pinSet.pins.isEmpty()); 187 // Both android.com and google.com should use the same config 188 NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com"); 189 assertEquals(config, other); 190 // Try connections. 191 SSLContext context = TestUtils.getSSLContext(source); 192 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 193 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 194 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 195 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 196 } 197 198 public void testMultipleDomainConfigs() throws Exception { 199 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs); 200 ApplicationConfig appConfig = new ApplicationConfig(source); 201 assertTrue(appConfig.hasPerDomainConfigs()); 202 // Should be two different config objects 203 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 204 NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com"); 205 MoreAsserts.assertNotEqual(config, other); 206 // Try connections. 207 SSLContext context = TestUtils.getSSLContext(source); 208 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 209 TestUtils.assertConnectionSucceeds(context, "google.com", 443); 210 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 211 } 212 213 public void testIncludeSubdomains() throws Exception { 214 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains); 215 ApplicationConfig appConfig = new ApplicationConfig(source); 216 assertTrue(appConfig.hasPerDomainConfigs()); 217 // Try connections. 218 SSLContext context = TestUtils.getSSLContext(source); 219 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 220 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 221 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 222 TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443); 223 TestUtils.assertConnectionFails(context, "google.com", 443); 224 } 225 226 public void testAttributes() throws Exception { 227 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes); 228 ApplicationConfig appConfig = new ApplicationConfig(source); 229 assertFalse(appConfig.hasPerDomainConfigs()); 230 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 231 assertTrue(config.isHstsEnforced()); 232 assertFalse(config.isCleartextTrafficPermitted()); 233 } 234 235 public void testResourcePemCertificateSource() throws Exception { 236 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem); 237 ApplicationConfig appConfig = new ApplicationConfig(source); 238 // Check android.com. 239 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 240 assertTrue(config.isCleartextTrafficPermitted()); 241 assertFalse(config.isHstsEnforced()); 242 assertEquals(2, config.getTrustAnchors().size()); 243 // Try connections. 244 SSLContext context = TestUtils.getSSLContext(source); 245 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 246 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 247 TestUtils.assertUrlConnectionFails(context, "google.com", 443); 248 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 249 } 250 251 public void testResourceDerCertificateSource() throws Exception { 252 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der); 253 ApplicationConfig appConfig = new ApplicationConfig(source); 254 // Check android.com. 255 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 256 assertTrue(config.isCleartextTrafficPermitted()); 257 assertFalse(config.isHstsEnforced()); 258 assertEquals(2, config.getTrustAnchors().size()); 259 // Try connections. 260 SSLContext context = TestUtils.getSSLContext(source); 261 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 262 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 263 TestUtils.assertUrlConnectionFails(context, "google.com", 443); 264 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 265 } 266 267 public void testNestedDomainConfigs() throws Exception { 268 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains); 269 ApplicationConfig appConfig = new ApplicationConfig(source); 270 assertTrue(appConfig.hasPerDomainConfigs()); 271 NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com"); 272 NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com"); 273 MoreAsserts.assertNotEqual(parent, child); 274 MoreAsserts.assertEmpty(parent.getPins().pins); 275 MoreAsserts.assertNotEmpty(child.getPins().pins); 276 // Check that the child inherited the cleartext value and anchors. 277 assertFalse(child.isCleartextTrafficPermitted()); 278 MoreAsserts.assertNotEmpty(child.getTrustAnchors()); 279 // Test connections. 280 SSLContext context = TestUtils.getSSLContext(source); 281 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 282 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 283 } 284 285 public void testNestedDomainConfigsOverride() throws Exception { 286 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override); 287 ApplicationConfig appConfig = new ApplicationConfig(source); 288 assertTrue(appConfig.hasPerDomainConfigs()); 289 NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com"); 290 NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com"); 291 MoreAsserts.assertNotEqual(parent, child); 292 assertTrue(parent.isCleartextTrafficPermitted()); 293 assertFalse(child.isCleartextTrafficPermitted()); 294 } 295 296 public void testDebugOverridesDisabled() throws Exception { 297 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false); 298 ApplicationConfig appConfig = new ApplicationConfig(source); 299 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 300 Set<TrustAnchor> anchors = config.getTrustAnchors(); 301 MoreAsserts.assertEmpty(anchors); 302 SSLContext context = TestUtils.getSSLContext(source); 303 TestUtils.assertConnectionFails(context, "android.com", 443); 304 TestUtils.assertConnectionFails(context, "developer.android.com", 443); 305 } 306 307 public void testBasicDebugOverrides() throws Exception { 308 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true); 309 ApplicationConfig appConfig = new ApplicationConfig(source); 310 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 311 Set<TrustAnchor> anchors = config.getTrustAnchors(); 312 MoreAsserts.assertNotEmpty(anchors); 313 for (TrustAnchor anchor : anchors) { 314 assertTrue(anchor.overridesPins); 315 } 316 SSLContext context = TestUtils.getSSLContext(source); 317 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 318 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 319 } 320 321 public void testDebugOverridesWithDomain() throws Exception { 322 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true); 323 ApplicationConfig appConfig = new ApplicationConfig(source); 324 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 325 Set<TrustAnchor> anchors = config.getTrustAnchors(); 326 boolean foundDebugCA = false; 327 for (TrustAnchor anchor : anchors) { 328 if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) { 329 foundDebugCA = true; 330 assertTrue(anchor.overridesPins); 331 } 332 } 333 assertTrue(foundDebugCA); 334 SSLContext context = TestUtils.getSSLContext(source); 335 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 336 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 337 } 338 339 public void testDebugInherit() throws Exception { 340 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true); 341 ApplicationConfig appConfig = new ApplicationConfig(source); 342 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 343 Set<TrustAnchor> anchors = config.getTrustAnchors(); 344 boolean foundDebugCA = false; 345 for (TrustAnchor anchor : anchors) { 346 if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) { 347 foundDebugCA = true; 348 assertTrue(anchor.overridesPins); 349 } 350 } 351 assertTrue(foundDebugCA); 352 assertTrue(anchors.size() > 1); 353 SSLContext context = TestUtils.getSSLContext(source); 354 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 355 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 356 } 357 358 private void testBadConfig(int configId) throws Exception { 359 try { 360 XmlConfigSource source = new XmlConfigSource(getContext(), configId); 361 ApplicationConfig appConfig = new ApplicationConfig(source); 362 appConfig.getConfigForHostname("android.com"); 363 fail("Bad config " + getContext().getResources().getResourceName(configId) 364 + " did not fail to parse"); 365 } catch (RuntimeException e) { 366 MoreAsserts.assertAssignableFrom(XmlConfigSource.ParserException.class, 367 e.getCause()); 368 } 369 } 370 371 public void testBadConfig0() throws Exception { 372 testBadConfig(R.xml.bad_config0); 373 } 374 375 public void testBadConfig1() throws Exception { 376 testBadConfig(R.xml.bad_config1); 377 } 378 379 public void testBadConfig2() throws Exception { 380 testBadConfig(R.xml.bad_config2); 381 } 382 383 public void testBadConfig3() throws Exception { 384 testBadConfig(R.xml.bad_config3); 385 } 386 387 public void testBadConfig4() throws Exception { 388 testBadConfig(R.xml.bad_config4); 389 } 390 391 public void testBadConfig5() throws Exception { 392 testBadConfig(R.xml.bad_config4); 393 } 394 395 public void testTrustManagerKeystore() throws Exception { 396 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true); 397 ApplicationConfig appConfig = new ApplicationConfig(source); 398 Provider provider = new NetworkSecurityConfigProvider(); 399 TrustManagerFactory tmf = 400 TrustManagerFactory.getInstance("PKIX", provider); 401 KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); 402 keystore.load(null); 403 int i = 0; 404 for (X509Certificate cert : SystemCertificateSource.getInstance().getCertificates()) { 405 keystore.setEntry(String.valueOf(i), 406 new KeyStore.TrustedCertificateEntry(cert), 407 null); 408 i++; 409 } 410 tmf.init(keystore); 411 TrustManager[] tms = tmf.getTrustManagers(); 412 SSLContext context = SSLContext.getInstance("TLS"); 413 context.init(null, tms, null); 414 TestUtils.assertConnectionSucceeds(context, "android.com" , 443); 415 } 416 417 public void testDebugDedup() throws Exception { 418 XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true); 419 ApplicationConfig appConfig = new ApplicationConfig(source); 420 assertTrue(appConfig.hasPerDomainConfigs()); 421 // Check android.com. 422 NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com"); 423 PinSet pinSet = config.getPins(); 424 assertFalse(pinSet.pins.isEmpty()); 425 // Check that all TrustAnchors come from the override pins debug source. 426 for (TrustAnchor anchor : config.getTrustAnchors()) { 427 assertTrue(anchor.overridesPins); 428 } 429 // Try connections. 430 SSLContext context = TestUtils.getSSLContext(source); 431 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 432 TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); 433 } 434 435 public void testExtraDebugResource() throws Exception { 436 XmlConfigSource source = 437 new XmlConfigSource(getContext(), R.xml.extra_debug_resource, true); 438 ApplicationConfig appConfig = new ApplicationConfig(source); 439 assertFalse(appConfig.hasPerDomainConfigs()); 440 NetworkSecurityConfig config = appConfig.getConfigForHostname(""); 441 MoreAsserts.assertNotEmpty(config.getTrustAnchors()); 442 443 // Check that the _debug file is ignored if debug is false. 444 source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource, false); 445 appConfig = new ApplicationConfig(source); 446 assertFalse(appConfig.hasPerDomainConfigs()); 447 config = appConfig.getConfigForHostname(""); 448 MoreAsserts.assertEmpty(config.getTrustAnchors()); 449 } 450 451 public void testExtraDebugResourceIgnored() throws Exception { 452 // Verify that parsing the extra debug config resource fails only when debugging is true. 453 XmlConfigSource source = 454 new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, false); 455 ApplicationConfig appConfig = new ApplicationConfig(source); 456 // Force parsing the config file. 457 appConfig.getConfigForHostname(""); 458 459 source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, true); 460 appConfig = new ApplicationConfig(source); 461 try { 462 appConfig.getConfigForHostname(""); 463 fail("Bad extra debug resource did not fail to parse"); 464 } catch (RuntimeException expected) { 465 } 466 } 467 468 public void testDomainWhitespaceTrimming() throws Exception { 469 XmlConfigSource source = 470 new XmlConfigSource(getContext(), R.xml.domain_whitespace, false); 471 ApplicationConfig appConfig = new ApplicationConfig(source); 472 NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname(""); 473 MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com")); 474 MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("android.com")); 475 SSLContext context = TestUtils.getSSLContext(source); 476 TestUtils.assertConnectionSucceeds(context, "android.com", 443); 477 TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); 478 } 479} 480