Add xml source for network security configuration
XmlConfigSource parses an ApplicationConfig from an xml resource.
Currently this supports app-wide default configuration via the
base-config element, per domain via the domain-config element and
inheritance of unset properties at parse time.
Inheritance of unset properties is currently only:
domain-config -> base-config -> platform default configuration
Where the most specific value is used.
For example: If the base-config specifies trust anchors, all connections
will use those anchors except for connections to a domain which has a
domain-config that specifies trust anchors, in which case the
domain-config's trust anchors will be used. If the domain-config or
base-config don't set trust anchors, or don't exist, then the platform
default trust anchors will be used.
Nested domain-config entries, debug-overrides, and thorough
documentation of the xml format will follow in later commits.
Change-Id: I1232ff1e8079a81b340bc12e142f0889f6947aa0
diff --git a/core/java/android/security/net/config/Pin.java b/core/java/android/security/net/config/Pin.java
index 8567431..94520e2 100644
--- a/core/java/android/security/net/config/Pin.java
+++ b/core/java/android/security/net/config/Pin.java
@@ -30,6 +30,26 @@
this.digest = digest;
mHashCode = Arrays.hashCode(digest) ^ digestAlgorithm.hashCode();
}
+
+ /**
+ * @hide
+ */
+ public static boolean isSupportedDigestAlgorithm(String algorithm) {
+ // Currently only SHA-256 is supported. SHA-512 if/once Chromium networking stack
+ // supports it.
+ return "SHA-256".equalsIgnoreCase(algorithm);
+ }
+
+ /**
+ * @hide
+ */
+ public static int getDigestLength(String algorithm) {
+ if ("SHA-256".equalsIgnoreCase(algorithm)) {
+ return 32;
+ }
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + algorithm);
+ }
+
@Override
public int hashCode() {
return mHashCode;
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
new file mode 100644
index 0000000..77c9bb6
--- /dev/null
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -0,0 +1,310 @@
+package android.security.net.config;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
+import android.util.Base64;
+import android.util.Pair;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * {@link ConfigSource} based on an XML configuration file.
+ *
+ * @hide
+ */
+public class XmlConfigSource implements ConfigSource {
+ private final Object mLock = new Object();
+ private final int mResourceId;
+
+ private boolean mInitialized;
+ private NetworkSecurityConfig mDefaultConfig;
+ private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
+ private Context mContext;
+
+ public XmlConfigSource(Context context, int resourceId) {
+ mResourceId = resourceId;
+ mContext = context;
+ }
+
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ ensureInitialized();
+ return mDomainMap;
+ }
+
+ public NetworkSecurityConfig getDefaultConfig() {
+ ensureInitialized();
+ return mDefaultConfig;
+ }
+
+ private void ensureInitialized() {
+ synchronized (mLock) {
+ if (mInitialized) {
+ return;
+ }
+ try (XmlResourceParser parser = mContext.getResources().getXml(mResourceId)) {
+ parseNetworkSecurityConfig(parser);
+ mContext = null;
+ mInitialized = true;
+ } catch (Resources.NotFoundException | XmlPullParserException | IOException
+ | ParserException e) {
+ throw new RuntimeException("Failed to parse XML configuration from "
+ + mContext.getResources().getResourceEntryName(mResourceId), e);
+ }
+ }
+ }
+
+ private Pin parsePin(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ String digestAlgorithm = parser.getAttributeValue(null, "digest");
+ if (!Pin.isSupportedDigestAlgorithm(digestAlgorithm)) {
+ throw new ParserException(parser, "Unsupported pin digest algorithm: "
+ + digestAlgorithm);
+ }
+ if (parser.next() != XmlPullParser.TEXT) {
+ throw new ParserException(parser, "Missing pin digest");
+ }
+ String digest = parser.getText();
+ byte[] decodedDigest = null;
+ try {
+ decodedDigest = Base64.decode(digest, 0);
+ } catch (IllegalArgumentException e) {
+ throw new ParserException(parser, "Invalid pin digest", e);
+ }
+ int expectedLength = Pin.getDigestLength(digestAlgorithm);
+ if (decodedDigest.length != expectedLength) {
+ throw new ParserException(parser, "digest length " + decodedDigest.length
+ + " does not match expected length for " + digestAlgorithm + " of "
+ + expectedLength);
+ }
+ if (parser.next() != XmlPullParser.END_TAG) {
+ throw new ParserException(parser, "pin contains additional elements");
+ }
+ return new Pin(digestAlgorithm, decodedDigest);
+ }
+
+ private PinSet parsePinSet(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ String expirationDate = parser.getAttributeValue(null, "expiration");
+ long expirationTimestampMilis = Long.MAX_VALUE;
+ if (expirationDate != null) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setLenient(false);
+ Date date = sdf.parse(expirationDate);
+ if (date == null) {
+ throw new ParserException(parser, "Invalid expiration date in pin-set");
+ }
+ expirationTimestampMilis = date.getTime();
+ } catch (ParseException e) {
+ throw new ParserException(parser, "Invalid expiration date in pin-set", e);
+ }
+ }
+
+ int outerDepth = parser.getDepth();
+ Set<Pin> pins = new ArraySet<>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ if (tagName.equals("pin")) {
+ pins.add(parsePin(parser));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return new PinSet(pins, expirationTimestampMilis);
+ }
+
+ private Domain parseDomain(XmlResourceParser parser, Set<String> seenDomains)
+ throws IOException, XmlPullParserException, ParserException {
+ boolean includeSubdomains =
+ parser.getAttributeBooleanValue(null, "includeSubdomains", false);
+ if (parser.next() != XmlPullParser.TEXT) {
+ throw new ParserException(parser, "Domain name missing");
+ }
+ String domain = parser.getText().toLowerCase(Locale.US);
+ if (parser.next() != XmlPullParser.END_TAG) {
+ throw new ParserException(parser, "domain contains additional elements");
+ }
+ // Domains are matched using a most specific match, so don't allow duplicates.
+ // includeSubdomains isn't relevant here, both android.com + subdomains and android.com
+ // match for android.com equally. Do not allow any duplicates period.
+ if (!seenDomains.add(domain)) {
+ throw new ParserException(parser, domain + " has already been specified");
+ }
+ return new Domain(domain, includeSubdomains);
+ }
+
+ private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ boolean overridePins = parser.getAttributeBooleanValue(null, "overridePins", false);
+ int sourceId = parser.getAttributeResourceValue(null, "src", -1);
+ String sourceString = parser.getAttributeValue(null, "src");
+ CertificateSource source = null;
+ if (sourceString == null) {
+ throw new ParserException(parser, "certificates element missing src attribute");
+ }
+ if (sourceId != -1) {
+ // TODO: Cache ResourceCertificateSources by sourceId
+ source = new ResourceCertificateSource(sourceId, mContext);
+ } else if ("system".equals(sourceString)) {
+ source = SystemCertificateSource.getInstance();
+ } else if ("user".equals(sourceString)) {
+ source = UserCertificateSource.getInstance();
+ } else {
+ throw new ParserException(parser, "Unknown certificates src. "
+ + "Should be one of system|user|@resourceVal");
+ }
+ XmlUtils.skipCurrentTag(parser);
+ return new CertificatesEntryRef(source, overridePins);
+ }
+
+ private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser)
+ 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));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return anchors;
+ }
+
+ private Pair<NetworkSecurityConfig.Builder, Set<Domain>> parseConfigEntry(
+ XmlResourceParser parser, Set<String> seenDomains, boolean baseConfig)
+ throws IOException, XmlPullParserException, ParserException {
+ NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
+ Set<Domain> domains = new ArraySet<>();
+ boolean seenPinSet = false;
+ boolean seenTrustAnchors = false;
+ String configName = parser.getName();
+ int outerDepth = parser.getDepth();
+ // Parse config attributes. Only set values that are present, config inheritence will
+ // handle the rest.
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String name = parser.getAttributeName(i);
+ if ("hstsEnforced".equals(name)) {
+ builder.setHstsEnforced(
+ parser.getAttributeBooleanValue(i,
+ NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED));
+ } else if ("cleartextTrafficPermitted".equals(name)) {
+ builder.setCleartextTrafficPermitted(
+ parser.getAttributeBooleanValue(i,
+ NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED));
+ }
+ }
+ // Parse the config elements.
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ // TODO: Support nested domain-config entries.
+ if ("domain".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser, "domain element not allowed in base-config");
+ }
+ Domain domain = parseDomain(parser, seenDomains);
+ domains.add(domain);
+ } else if ("trust-anchors".equals(tagName)) {
+ if (seenTrustAnchors) {
+ throw new ParserException(parser,
+ "Multiple trust-anchor elements not allowed");
+ }
+ builder.addCertificatesEntryRefs(parseTrustAnchors(parser));
+ seenTrustAnchors = true;
+ } else if ("pin-set".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser,
+ "pin-set element not allowed in base-config");
+ }
+ if (seenPinSet) {
+ throw new ParserException(parser, "Multiple pin-set elements not allowed");
+ }
+ builder.setPinSet(parsePinSet(parser));
+ seenPinSet = true;
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ if (!baseConfig && domains.isEmpty()) {
+ throw new ParserException(parser, "No domain elements in domain-config");
+ }
+ return new Pair<>(builder, domains);
+ }
+
+ 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;
+ 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, true).first;
+ } else if ("domain-config".equals(parser.getName())) {
+ builders.add(parseConfigEntry(parser, seenDomains, false));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ // Use the platform default as the parent of the base config for any values not provided
+ // there. If there is no base config use the platform default.
+ NetworkSecurityConfig.Builder platformDefaultBuilder =
+ NetworkSecurityConfig.getDefaultBuilder();
+ if (baseConfigBuilder != null) {
+ baseConfigBuilder.setParent(platformDefaultBuilder);
+ } else {
+ baseConfigBuilder = platformDefaultBuilder;
+ }
+ // Build the per-domain config mapping.
+ Set<Pair<Domain, NetworkSecurityConfig>> configs = new ArraySet<>();
+
+ for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) {
+ NetworkSecurityConfig.Builder builder = entry.first;
+ Set<Domain> domains = entry.second;
+ // Use the base-config for inheriting any unset values in the domain-config entry.
+ builder.setParent(baseConfigBuilder);
+ NetworkSecurityConfig config = builder.build();
+ for (Domain domain : domains) {
+ configs.add(new Pair<>(domain, config));
+ }
+ }
+ mDefaultConfig = baseConfigBuilder.build();
+ mDomainMap = configs;
+ }
+
+ public static class ParserException extends Exception {
+
+ public ParserException(XmlPullParser parser, String message, Throwable cause) {
+ super(message + " at: " + parser.getPositionDescription(), cause);
+ }
+
+ public ParserException(XmlPullParser parser, String message) {
+ this(parser, message, null);
+ }
+ }
+}
diff --git a/tests/NetworkSecurityConfigTest/AndroidManifest.xml b/tests/NetworkSecurityConfigTest/AndroidManifest.xml
index 811a3f4..4c1fbd3 100644
--- a/tests/NetworkSecurityConfigTest/AndroidManifest.xml
+++ b/tests/NetworkSecurityConfigTest/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.security.tests"
+ package="android.security.net.config"
android:sharedUserId="android.uid.system">
<application>
@@ -23,7 +23,7 @@
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.security.tests"
+ android:targetPackage="android.security.net.config"
android:label="ANSC Tests">
</instrumentation>
</manifest>
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
new file mode 100644
index 0000000..235bd47
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
Binary files differ
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
new file mode 100644
index 0000000..413e3c0
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/xml/attributes.xml b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
new file mode 100644
index 0000000..eff13c8
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config cleartextTrafficPermitted="false" hstsEnforced="true">
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
new file mode 100644
index 0000000..6af855d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Bad pin digest -->
+ <pin digest="I am probably not an algorithm">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
new file mode 100644
index 0000000..d683b74a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Unknown pin digest -->
+ <pin>1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
new file mode 100644
index 0000000..6f3f8b4
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- empty digest -->
+ <pin digest="SHA-256"></pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
new file mode 100644
index 0000000..fb2126c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <!-- Same domain name used in two configs -->
+ <domain>android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
new file mode 100644
index 0000000..95972ce
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- domains are not allowed in base-config -->
+ <domain>android.com</domain>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
new file mode 100644
index 0000000..8b6b721
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- pins are not allowed in base-config -->
+ <pin-set>
+ </pin-set>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
new file mode 100644
index 0000000..62a7b88
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain1.xml b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
new file mode 100644
index 0000000..6d8565c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
new file mode 100644
index 0000000..1bd94b6
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
new file mode 100644
index 0000000..8093b9d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
new file mode 100644
index 0000000..f9f8465
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <!-- Invalid pin that has expired -->
+ <pin-set expiration="2015-01-01">
+ <pin digest="SHA-256">aaaaaaaaaaa2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
new file mode 100644
index 0000000..df08467
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
new file mode 100644
index 0000000..9743c5f
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml
new file mode 100644
index 0000000..785714a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ <trust-anchors>
+ <certificates src="system" overridePins="true" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
new file mode 100644
index 0000000..1773d280
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
new file mode 100644
index 0000000..dfd6fd9
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_der" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
new file mode 100644
index 0000000..894f29b
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
new file mode 100644
index 0000000..482b26c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain includeSubdomains="true">android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 11d8136..9f48d56 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -50,49 +50,6 @@
return data;
}
- private void assertConnectionFails(SSLContext context, String host, int port)
- throws Exception {
- try {
- Socket s = context.getSocketFactory().createSocket(host, port);
- s.getInputStream();
- fail("Expected connection to " + host + ":" + port + " to fail.");
- } catch (SSLHandshakeException expected) {
- }
- }
-
- private void assertConnectionSucceeds(SSLContext context, String host, int port)
- throws Exception {
- Socket s = context.getSocketFactory().createSocket(host, port);
- s.getInputStream();
- }
-
- private void assertUrlConnectionFails(SSLContext context, String host, int port)
- throws Exception {
- URL url = new URL("https://" + host + ":" + port);
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
- connection.setSSLSocketFactory(context.getSocketFactory());
- try {
- connection.getInputStream();
- fail("Connection to " + host + ":" + port + " expected to fail");
- } catch (SSLHandshakeException expected) {
- // ignored.
- }
- }
-
- private void assertUrlConnectionSucceeds(SSLContext context, String host, int port)
- throws Exception {
- URL url = new URL("https://" + host + ":" + port);
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
- connection.setSSLSocketFactory(context.getSocketFactory());
- connection.getInputStream();
- }
-
- private SSLContext getSSLContext(ConfigSource source) throws Exception {
- ApplicationConfig config = new ApplicationConfig(source);
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, new TrustManager[] {config.getTrustManager()}, null);
- return context;
- }
/**
@@ -115,8 +72,8 @@
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
ConfigSource testSource =
new TestConfigSource(domainMap, getEmptyConfig());
- SSLContext context = getSSLContext(testSource);
- assertConnectionFails(context, "android.com", 443);
+ SSLContext context = TestUtils.getSSLContext(testSource);
+ TestUtils.assertConnectionFails(context, "android.com", 443);
}
public void testEmptyPerNetworkSecurityConfig() throws Exception {
@@ -125,9 +82,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), getEmptyConfig()));
NetworkSecurityConfig defaultConfig = getSystemStoreConfig();
- SSLContext context = getSSLContext(new TestConfigSource(domainMap, defaultConfig));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "google.com", 443);
+ SSLContext context = TestUtils.getSSLContext(new TestConfigSource(domainMap, defaultConfig));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testBadPin() throws Exception {
@@ -143,9 +100,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "google.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testGoodPin() throws Exception {
@@ -161,9 +118,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "android.com", 443);
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testOverridePins() throws Exception {
@@ -180,8 +137,8 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
}
public void testMostSpecificNetworkSecurityConfig() throws Exception {
@@ -192,9 +149,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("developer.android.com", false), getSystemStoreConfig()));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testSubdomainIncluded() throws Exception {
@@ -204,14 +161,14 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), getSystemStoreConfig()));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
// Now try without including subdomains.
domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", false), getSystemStoreConfig()));
- context = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionFails(context, "developer.android.com", 443);
+ context = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
}
public void testConfigBuilderUsesParents() throws Exception {
@@ -246,9 +203,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertUrlConnectionSucceeds(context, "android.com", 443);
- assertUrlConnectionSucceeds(context, "developer.android.com", 443);
- assertUrlConnectionFails(context, "google.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
}
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
new file mode 100644
index 0000000..43c0e57
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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 java.net.Socket;
+import java.net.URL;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+
+import junit.framework.Assert;
+
+public final class TestUtils extends Assert {
+
+ private TestUtils() {
+ }
+
+ public static void assertConnectionFails(SSLContext context, String host, int port)
+ throws Exception {
+ try {
+ Socket s = context.getSocketFactory().createSocket(host, port);
+ s.getInputStream();
+ fail("Expected connection to " + host + ":" + port + " to fail.");
+ } catch (SSLHandshakeException expected) {
+ }
+ }
+
+ public static void assertConnectionSucceeds(SSLContext context, String host, int port)
+ throws Exception {
+ Socket s = context.getSocketFactory().createSocket(host, port);
+ s.getInputStream();
+ }
+
+ public static void assertUrlConnectionFails(SSLContext context, String host, int port)
+ throws Exception {
+ URL url = new URL("https://" + host + ":" + port);
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setSSLSocketFactory(context.getSocketFactory());
+ try {
+ connection.getInputStream();
+ fail("Connection to " + host + ":" + port + " expected to fail");
+ } catch (SSLHandshakeException expected) {
+ // ignored.
+ }
+ }
+
+ public static void assertUrlConnectionSucceeds(SSLContext context, String host, int port)
+ throws Exception {
+ URL url = new URL("https://" + host + ":" + port);
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setSSLSocketFactory(context.getSocketFactory());
+ connection.getInputStream();
+ }
+
+ public static SSLContext getSSLContext(ConfigSource source) throws Exception {
+ ApplicationConfig config = new ApplicationConfig(source);
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, new TrustManager[] {config.getTrustManager()}, null);
+ return context;
+ }
+}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
new file mode 100644
index 0000000..4914d06
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -0,0 +1,284 @@
+/*
+ * 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.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+
+public class XmlConfigTests extends AndroidTestCase {
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+}