diff options
7 files changed, 199 insertions, 18 deletions
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java index 1c787b407086..503854ed64ff 100644 --- a/core/java/android/security/net/config/NetworkSecurityConfig.java +++ b/core/java/android/security/net/config/NetworkSecurityConfig.java @@ -227,10 +227,14 @@ public final class NetworkSecurityConfig { return Collections.<CertificatesEntryRef>emptyList(); } - public boolean hasCertificateEntryRefs() { + public boolean hasCertificatesEntryRefs() { return mCertificatesEntryRefs != null; } + List<CertificatesEntryRef> getCertificatesEntryRefs() { + return mCertificatesEntryRefs; + } + public NetworkSecurityConfig build() { boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); boolean hstsEnforced = getEffectiveHstsEnforced(); diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java index 6abfb66f9450..1706e95513ff 100644 --- a/core/java/android/security/net/config/XmlConfigSource.java +++ b/core/java/android/security/net/config/XmlConfigSource.java @@ -27,8 +27,13 @@ import java.util.Set; * @hide */ public class XmlConfigSource implements ConfigSource { + private static final int CONFIG_BASE = 0; + private static final int CONFIG_DOMAIN = 1; + private static final int CONFIG_DEBUG = 2; + private final Object mLock = new Object(); private final int mResourceId; + private final boolean mDebugBuild; private boolean mInitialized; private NetworkSecurityConfig mDefaultConfig; @@ -36,8 +41,13 @@ public class XmlConfigSource implements ConfigSource { private Context mContext; public XmlConfigSource(Context context, int resourceId) { + this(context, resourceId, false); + } + + public XmlConfigSource(Context context, int resourceId, boolean debugBuild) { mResourceId = resourceId; mContext = context; + mDebugBuild = debugBuild; } public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { @@ -50,6 +60,19 @@ public class XmlConfigSource implements ConfigSource { return mDefaultConfig; } + private static final String getConfigString(int configType) { + switch (configType) { + case CONFIG_BASE: + return "base-config"; + case CONFIG_DOMAIN: + return "domain-config"; + case CONFIG_DEBUG: + return "debug-overrides"; + default: + throw new IllegalArgumentException("Unknown config type: " + configType); + } + } + private void ensureInitialized() { synchronized (mLock) { if (mInitialized) { @@ -147,9 +170,11 @@ public class XmlConfigSource implements ConfigSource { return new Domain(domain, includeSubdomains); } - private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser) + private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser, + boolean defaultOverridePins) throws IOException, XmlPullParserException, ParserException { - boolean overridePins = parser.getAttributeBooleanValue(null, "overridePins", false); + boolean overridePins = + parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins); int sourceId = parser.getAttributeResourceValue(null, "src", -1); String sourceString = parser.getAttributeValue(null, "src"); CertificateSource source = null; @@ -171,14 +196,15 @@ public class XmlConfigSource implements ConfigSource { return new CertificatesEntryRef(source, overridePins); } - private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser) + private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser, + boolean defaultOverridePins) throws IOException, XmlPullParserException, ParserException { int outerDepth = parser.getDepth(); List<CertificatesEntryRef> anchors = new ArrayList<>(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { String tagName = parser.getName(); if (tagName.equals("certificates")) { - anchors.add(parseCertificatesEntry(parser)); + anchors.add(parseCertificatesEntry(parser, defaultOverridePins)); } else { XmlUtils.skipCurrentTag(parser); } @@ -188,7 +214,7 @@ public class XmlConfigSource implements ConfigSource { private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry( XmlResourceParser parser, Set<String> seenDomains, - NetworkSecurityConfig.Builder parentBuilder, boolean baseConfig) + NetworkSecurityConfig.Builder parentBuilder, int configType) throws IOException, XmlPullParserException, ParserException { List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder(); @@ -196,6 +222,7 @@ public class XmlConfigSource implements ConfigSource { Set<Domain> domains = new ArraySet<>(); boolean seenPinSet = false; boolean seenTrustAnchors = false; + boolean defaultOverridePins = configType == CONFIG_DEBUG; String configName = parser.getName(); int outerDepth = parser.getDepth(); // Add this builder now so that this builder occurs before any of its children. This @@ -219,8 +246,9 @@ public class XmlConfigSource implements ConfigSource { while (XmlUtils.nextElementWithin(parser, outerDepth)) { String tagName = parser.getName(); if ("domain".equals(tagName)) { - if (baseConfig) { - throw new ParserException(parser, "domain element not allowed in base-config"); + if (configType != CONFIG_DOMAIN) { + throw new ParserException(parser, + "domain element not allowed in " + getConfigString(configType)); } Domain domain = parseDomain(parser, seenDomains); domains.add(domain); @@ -229,12 +257,13 @@ public class XmlConfigSource implements ConfigSource { throw new ParserException(parser, "Multiple trust-anchor elements not allowed"); } - builder.addCertificatesEntryRefs(parseTrustAnchors(parser)); + builder.addCertificatesEntryRefs( + parseTrustAnchors(parser, defaultOverridePins)); seenTrustAnchors = true; } else if ("pin-set".equals(tagName)) { - if (baseConfig) { + if (configType != CONFIG_DOMAIN) { throw new ParserException(parser, - "pin-set element not allowed in base-config"); + "pin-set element not allowed in " + getConfigString(configType)); } if (seenPinSet) { throw new ParserException(parser, "Multiple pin-set elements not allowed"); @@ -242,41 +271,68 @@ public class XmlConfigSource implements ConfigSource { builder.setPinSet(parsePinSet(parser)); seenPinSet = true; } else if ("domain-config".equals(tagName)) { - if (baseConfig) { + if (configType != CONFIG_DOMAIN) { throw new ParserException(parser, - "Nested domain-config not allowed in base-config"); + "Nested domain-config not allowed in " + getConfigString(configType)); } - builders.addAll(parseConfigEntry(parser, seenDomains, builder, false)); + builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType)); } else { XmlUtils.skipCurrentTag(parser); } } - if (!baseConfig && domains.isEmpty()) { + if (configType == CONFIG_DOMAIN && domains.isEmpty()) { throw new ParserException(parser, "No domain elements in domain-config"); } return builders; } + private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder, + NetworkSecurityConfig.Builder builder) { + if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) { + return; + } + // Don't add trust anchors if not already present, the builder will inherit the anchors + // from its parent, and that's where the trust anchors should be added. + if (!builder.hasCertificatesEntryRefs()) { + return; + } + + builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs()); + } + private void parseNetworkSecurityConfig(XmlResourceParser parser) throws IOException, XmlPullParserException, ParserException { Set<String> seenDomains = new ArraySet<>(); List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); NetworkSecurityConfig.Builder baseConfigBuilder = null; + NetworkSecurityConfig.Builder debugConfigBuilder = null; boolean seenDebugOverrides = false; boolean seenBaseConfig = false; XmlUtils.beginDocument(parser, "network-security-config"); int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { - // TODO: support debug-override. if ("base-config".equals(parser.getName())) { if (seenBaseConfig) { throw new ParserException(parser, "Only one base-config allowed"); } seenBaseConfig = true; - baseConfigBuilder = parseConfigEntry(parser, seenDomains, null, true).get(0).first; + baseConfigBuilder = + parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first; } else if ("domain-config".equals(parser.getName())) { - builders.addAll(parseConfigEntry(parser, seenDomains, baseConfigBuilder, false)); + builders.addAll( + parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN)); + } else if ("debug-overrides".equals(parser.getName())) { + if (seenDebugOverrides) { + throw new ParserException(parser, "Only one debug-overrides allowed"); + } + if (mDebugBuild) { + debugConfigBuilder = + parseConfigEntry(parser, seenDomains, null, CONFIG_DEBUG).get(0).first; + } else { + XmlUtils.skipCurrentTag(parser); + } + seenDebugOverrides = true; } else { XmlUtils.skipCurrentTag(parser); } @@ -286,8 +342,10 @@ public class XmlConfigSource implements ConfigSource { // there. If there is no base config use the platform default. NetworkSecurityConfig.Builder platformDefaultBuilder = NetworkSecurityConfig.getDefaultBuilder(); + addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder); if (baseConfigBuilder != null) { baseConfigBuilder.setParent(platformDefaultBuilder); + addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder); } else { baseConfigBuilder = platformDefaultBuilder; } @@ -306,6 +364,7 @@ public class XmlConfigSource implements ConfigSource { if (builder.getParent() == null) { builder.setParent(baseConfigBuilder); } + addDebugAnchorsIfNeeded(debugConfigBuilder, builder); NetworkSecurityConfig config = builder.build(); for (Domain domain : domains) { configs.add(new Pair<>(domain, config)); diff --git a/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem new file mode 100644 index 000000000000..81648d984d64 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDITCCAgmgAwIBAgIJAP/YiWztz/J7MA0GCSqGSIb3DQEBCwUAMCcxFjAUBgNV +BAMMDVRlc3QgZGVidWcgQ0ExDTALBgNVBAoMBEFPU1AwHhcNMTUxMTA5MjEyNjQ2 +WhcNMTgwODI5MjEyNjQ2WjAnMRYwFAYDVQQDDA1UZXN0IGRlYnVnIENBMQ0wCwYD +VQQKDARBT1NQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPFmkOJj +ehjfvdDr2qTcBWNqNATrW1SuM88Vj00ubUFQ4tZElozj8YnQOw1FeC79c1k88b8R +6jcqYYp/mw2JYoD6yWcFPHo5BplIpk0EhIUARH/aeoclHvsUN2GGDyTO0vf0CfJn +9Wp6lSLjyq7V/6tYdk+0cL632t56MHp8TCO+AaveYP1T8JZqx0/50xNcsK7lIqNa +ctWyRGFxR4ifdVsgkw9WhAB/Ow2uOwN9uLGqzsCd+yXW2weX52EIivoTGZfJo+U8 +Fi0ygnCHBv2jsJA7yWLhHmZ4ijsVtfutIKmN0w+DHkl6S25girXhy0zJp/1QvHGm +jaF60V1gw471jQIDAQABo1AwTjAdBgNVHQ4EFgQUoq66jncy83L5eeyW1g78s/uq +iyQwHwYDVR0jBBgwFoAUoq66jncy83L5eeyW1g78s/uqiyQwDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEAohytuH4CdX0gO8EGVDRVurRH7LO69lwd/6Iw +hJ1lIK/mzj5RM2itVGTkintyHCLu5giVkHn4FHg4X9qzZaTPOcXv9ntQNS2nacZe +bY8nfhsAhstJT4nIOWHE3FrZkMDOK6nZHIzfscX3V/VVq5MeA+WzXwmKp6MBNr+E +oUegXCGjd26Bl6SFz3rD7Qh+dzSTtyf/ECzXaMjpZu3k6fb4EgRz6vdBCHKKtpv6 +Mxcr0nLwdI6LnAGXvJLV4sj+l6Ngg00EeyorG8ATgtmsUrXXOR1e+yDCQv6fjQfs +CWYztECAUE9hfCXJwb0TBrq9YeJAvcO7iE6S0Pq+X3xNtetE1A== +-----END CERTIFICATE----- diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml new file mode 100644 index 000000000000..8da93173e6ec --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <debug-overrides> + <trust-anchors> + <certificates src="system" /> + </trust-anchors> + </debug-overrides> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml new file mode 100644 index 000000000000..24eed7a4e943 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="@raw/ca_certs_pem" /> + </trust-anchors> + </domain-config> + <debug-overrides> + <trust-anchors> + <certificates src="@raw/test_debug_ca" /> + </trust-anchors> + </debug-overrides> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml new file mode 100644 index 000000000000..ce0cbc874ca6 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <debug-overrides> + <trust-anchors> + <certificates src="@raw/test_debug_ca" /> + </trust-anchors> + </debug-overrides> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java index f52a27995854..43fa830f733e 100644 --- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java @@ -26,6 +26,7 @@ import java.net.Socket; import java.net.URL; 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; @@ -33,6 +34,8 @@ import javax.net.ssl.TrustManager; 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); @@ -274,6 +277,68 @@ public class XmlConfigTests extends AndroidTestCase { 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); |