Merge "Add support for debug-overrides configuration" am: f1e813ea33
am: 467804448d
* commit '467804448d6b13e04b4d9bc20fd195baa1f07e90':
Add support for debug-overrides configuration
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
19
tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
Normal file
19
tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
Normal file
@@ -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-----
|
||||
12
tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
Normal file
12
tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
Normal file
@@ -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>
|
||||
14
tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
Normal file
14
tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
Normal file
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user