diff options
27 files changed, 951 insertions, 69 deletions
diff --git a/core/java/android/security/net/config/Pin.java b/core/java/android/security/net/config/Pin.java index 856743198073..94520e22bd02 100644 --- a/core/java/android/security/net/config/Pin.java +++ b/core/java/android/security/net/config/Pin.java @@ -30,6 +30,26 @@ public final class Pin { 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 000000000000..77c9bb60191f --- /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 811a3f4f4f80..4c1fbd375e1e 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 Binary files differnew file mode 100644 index 000000000000..235bd4797b78 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der 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 000000000000..413e3c07d2ab --- /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 000000000000..eff13c80c343 --- /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 000000000000..6af855d12d5f --- /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 000000000000..d683b74ae197 --- /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 000000000000..6f3f8b43d653 --- /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 000000000000..fb2126c0f778 --- /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 000000000000..95972cedfc90 --- /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 000000000000..8b6b72151269 --- /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 000000000000..62a7b8819d87 --- /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 000000000000..6d8565c35717 --- /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 000000000000..1bd94b648d22 --- /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 000000000000..8093b9d05153 --- /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 000000000000..f9f846526a92 --- /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 000000000000..df08467af744 --- /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 000000000000..9743c5f0c4a0 --- /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 000000000000..785714a8f19e --- /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 000000000000..1773d28094a3 --- /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 000000000000..dfd6fd9cc373 --- /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 000000000000..894f29b4027c --- /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 000000000000..482b26c18716 --- /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 11d8136b9f5d..9f48d56cb56b 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { = 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { 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 000000000000..43c0e5708233 --- /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 000000000000..4914d06e2311 --- /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); + } +} |