blob: f7066a6f45f60cc50066044473239a74d46da7c2 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security.net.config;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Pair;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
public class XmlConfigTests extends AndroidTestCase {
private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
public void testEmptyConfigFile() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
// Try some connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "google.com", 443);
}
public void testEmptyAnchors() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertTrue(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionFails(context, "android.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionFails(context, "google.com", 443);
}
public void testBasicDomainConfig() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertTrue(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
// Check android.com.
config = appConfig.getConfigForHostname("android.com");
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionFails(context, "google.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
// Check that sockets created without the hostname fail with per-domain configs
SSLSocket socket = (SSLSocket) context.getSocketFactory()
.createSocket(InetAddress.getByName("android.com"), 443);
try {
socket.startHandshake();
socket.getInputStream();
fail();
} catch (IOException expected) {
}
}
public void testBasicPinning() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
PinSet pinSet = config.getPins();
assertFalse(pinSet.pins.isEmpty());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testExpiredPin() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
PinSet pinSet = config.getPins();
assertFalse(pinSet.pins.isEmpty());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testOverridesPins() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
PinSet pinSet = config.getPins();
assertFalse(pinSet.pins.isEmpty());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testBadPin() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
PinSet pinSet = config.getPins();
assertFalse(pinSet.pins.isEmpty());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionFails(context, "android.com", 443);
TestUtils.assertUrlConnectionFails(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testMultipleDomains() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
// Both android.com and google.com should use the same config
NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
assertEquals(config, other);
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "google.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testMultipleDomainConfigs() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Should be two different config objects
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
MoreAsserts.assertNotEqual(config, other);
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "google.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testIncludeSubdomains() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
TestUtils.assertConnectionFails(context, "google.com", 443);
}
public void testAttributes() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertTrue(config.isHstsEnforced());
assertFalse(config.isCleartextTrafficPermitted());
}
public void testResourcePemCertificateSource() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertEquals(2, config.getTrustAnchors().size());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionFails(context, "google.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testResourceDerCertificateSource() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
assertTrue(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertEquals(2, config.getTrustAnchors().size());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
TestUtils.assertUrlConnectionFails(context, "google.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testNestedDomainConfigs() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
MoreAsserts.assertNotEqual(parent, child);
MoreAsserts.assertEmpty(parent.getPins().pins);
MoreAsserts.assertNotEmpty(child.getPins().pins);
// Check that the child inherited the cleartext value and anchors.
assertFalse(child.isCleartextTrafficPermitted());
MoreAsserts.assertNotEmpty(child.getTrustAnchors());
// Test connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testNestedDomainConfigsOverride() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
MoreAsserts.assertNotEqual(parent, child);
assertTrue(parent.isCleartextTrafficPermitted());
assertFalse(child.isCleartextTrafficPermitted());
}
public void testDebugOverridesDisabled() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
Set<TrustAnchor> anchors = config.getTrustAnchors();
MoreAsserts.assertEmpty(anchors);
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionFails(context, "android.com", 443);
TestUtils.assertConnectionFails(context, "developer.android.com", 443);
}
public void testBasicDebugOverrides() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
Set<TrustAnchor> anchors = config.getTrustAnchors();
MoreAsserts.assertNotEmpty(anchors);
for (TrustAnchor anchor : anchors) {
assertTrue(anchor.overridesPins);
}
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testDebugOverridesWithDomain() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
Set<TrustAnchor> anchors = config.getTrustAnchors();
boolean foundDebugCA = false;
for (TrustAnchor anchor : anchors) {
if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
foundDebugCA = true;
assertTrue(anchor.overridesPins);
}
}
assertTrue(foundDebugCA);
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testDebugInherit() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
Set<TrustAnchor> anchors = config.getTrustAnchors();
boolean foundDebugCA = false;
for (TrustAnchor anchor : anchors) {
if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
foundDebugCA = true;
assertTrue(anchor.overridesPins);
}
}
assertTrue(foundDebugCA);
assertTrue(anchors.size() > 1);
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
private void testBadConfig(int configId) throws Exception {
try {
XmlConfigSource source = new XmlConfigSource(getContext(), configId);
ApplicationConfig appConfig = new ApplicationConfig(source);
appConfig.getConfigForHostname("android.com");
fail("Bad config " + getContext().getResources().getResourceName(configId)
+ " did not fail to parse");
} catch (RuntimeException e) {
MoreAsserts.assertAssignableFrom(XmlConfigSource.ParserException.class,
e.getCause());
}
}
public void testBadConfig0() throws Exception {
testBadConfig(R.xml.bad_config0);
}
public void testBadConfig1() throws Exception {
testBadConfig(R.xml.bad_config1);
}
public void testBadConfig2() throws Exception {
testBadConfig(R.xml.bad_config2);
}
public void testBadConfig3() throws Exception {
testBadConfig(R.xml.bad_config3);
}
public void testBadConfig4() throws Exception {
testBadConfig(R.xml.bad_config4);
}
public void testBadConfig5() throws Exception {
testBadConfig(R.xml.bad_config4);
}
public void testTrustManagerKeystore() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
Provider provider = new NetworkSecurityConfigProvider();
TrustManagerFactory tmf =
TrustManagerFactory.getInstance("PKIX", provider);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null);
int i = 0;
for (X509Certificate cert : SystemCertificateSource.getInstance().getCertificates()) {
keystore.setEntry(String.valueOf(i),
new KeyStore.TrustedCertificateEntry(cert),
null);
i++;
}
tmf.init(keystore);
TrustManager[] tms = tmf.getTrustManagers();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tms, null);
TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
}
public void testDebugDedup() throws Exception {
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
PinSet pinSet = config.getPins();
assertFalse(pinSet.pins.isEmpty());
// Check that all TrustAnchors come from the override pins debug source.
for (TrustAnchor anchor : config.getTrustAnchors()) {
assertTrue(anchor.overridesPins);
}
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
}
public void testExtraDebugResource() throws Exception {
XmlConfigSource source =
new XmlConfigSource(getContext(), R.xml.extra_debug_resource, true);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
MoreAsserts.assertNotEmpty(config.getTrustAnchors());
// Check that the _debug file is ignored if debug is false.
source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource, false);
appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
config = appConfig.getConfigForHostname("");
MoreAsserts.assertEmpty(config.getTrustAnchors());
}
public void testExtraDebugResourceIgnored() throws Exception {
// Verify that parsing the extra debug config resource fails only when debugging is true.
XmlConfigSource source =
new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, false);
ApplicationConfig appConfig = new ApplicationConfig(source);
// Force parsing the config file.
appConfig.getConfigForHostname("");
source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, true);
appConfig = new ApplicationConfig(source);
try {
appConfig.getConfigForHostname("");
fail("Bad extra debug resource did not fail to parse");
} catch (RuntimeException expected) {
}
}
public void testDomainWhitespaceTrimming() throws Exception {
XmlConfigSource source =
new XmlConfigSource(getContext(), R.xml.domain_whitespace, false);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname("");
MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com"));
MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("android.com"));
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
}