diff options
| author | 2015-11-05 19:05:47 +0000 | |
|---|---|---|
| committer | 2015-11-05 19:05:47 +0000 | |
| commit | f386e9da362600a44e9c32860a57451b2e2a404f (patch) | |
| tree | 6bd6866719edcfd105358b9c64d36292fc294530 /tests | |
| parent | ce8bf86d6d6d3342d6b94c90fcfe382070127eb2 (diff) | |
| parent | b4b53b0741b6ff75842d6630d5d1010c4efa766c (diff) | |
Merge "Add initial network security config implementation" am: 8c35820720 am: 0bafbbfcb4
am: b4b53b0741
* commit 'b4b53b0741b6ff75842d6630d5d1010c4efa766c':
Add initial network security config implementation
Diffstat (limited to 'tests')
5 files changed, 357 insertions, 0 deletions
diff --git a/tests/NetworkSecurityConfigTest/Android.mk b/tests/NetworkSecurityConfigTest/Android.mk new file mode 100644 index 000000000000..a63162d9ba09 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests +LOCAL_CERTIFICATE := platform + +LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := NetworkSecurityConfigTests + +include $(BUILD_PACKAGE) diff --git a/tests/NetworkSecurityConfigTest/AndroidManifest.xml b/tests/NetworkSecurityConfigTest/AndroidManifest.xml new file mode 100644 index 000000000000..811a3f4f4f80 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.tests" + android:sharedUserId="android.uid.system"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="android.security.tests" + android:label="ANSC Tests"> + </instrumentation> +</manifest> diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java new file mode 100644 index 000000000000..9a1fe151a2dc --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java @@ -0,0 +1,241 @@ +/* + * 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.app.Activity; +import android.test.ActivityUnitTestCase; +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 NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { + + public NetworkSecurityConfigTests() { + super(Activity.class); + } + + // SHA-256 of the G2 intermediate CA for android.com (as of 10/2015). + private static final byte[] G2_SPKI_SHA256 + = hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776"); + + private static byte[] hexToBytes(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit( + s.charAt(i + 1), 16)); + } + 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; + } + + + /** + * Return a NetworkSecurityConfig that has an empty TrustAnchor set. This should always cause a + * SSLHandshakeException when used for a connection. + */ + private NetworkSecurityConfig getEmptyConfig() { + return new NetworkSecurityConfig(true, false, + new PinSet(new ArraySet<Pin>(), -1), + new ArrayList<CertificatesEntryRef>()); + } + + private NetworkSecurityConfig getSystemStoreConfig() { + ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>(); + defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false)); + return new NetworkSecurityConfig(true, false, new PinSet(new ArraySet<Pin>(), + -1), defaultSource); + } + + public void testEmptyConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + ConfigSource testSource = + new TestConfigSource(domainMap, getEmptyConfig()); + SSLContext context = getSSLContext(testSource); + assertConnectionFails(context, "android.com", 443); + } + + public void testEmptyPerNetworkSecurityConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), getEmptyConfig())); + ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>(); + defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false)); + NetworkSecurityConfig defaultConfig = new NetworkSecurityConfig(true, false, + new PinSet(new ArraySet<Pin>(), -1), + defaultSource); + SSLContext context = getSSLContext(new TestConfigSource(domainMap, defaultConfig)); + assertConnectionFails(context, "android.com", 443); + assertConnectionSucceeds(context, "google.com", 443); + } + + public void testBadPin() throws Exception { + ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>(); + systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false)); + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", new byte[0])); + NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false, + new PinSet(pins, Long.MAX_VALUE), + systemSource); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + 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); + } + + public void testGoodPin() throws Exception { + ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>(); + systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false)); + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); + NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false, + new PinSet(pins, Long.MAX_VALUE), + systemSource); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + 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); + } + + public void testOverridePins() throws Exception { + // Use a bad pin + granting the system CA store the ability to override pins. + ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>(); + systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), true)); + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", new byte[0])); + NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false, + new PinSet(pins, Long.MAX_VALUE), + systemSource); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), domain)); + SSLContext context + = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + assertConnectionSucceeds(context, "android.com", 443); + } + + public void testMostSpecificNetworkSecurityConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), getEmptyConfig())); + 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); + } + + public void testSubdomainIncluded() throws Exception { + // First try connecting to a subdomain of a domain entry that includes subdomains. + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + 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); + // 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); + } + + public void testWithUrlConnection() throws Exception { + ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>(); + systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false)); + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); + NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false, + new PinSet(pins, Long.MAX_VALUE), + systemSource); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + 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); + } +} diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java new file mode 100644 index 000000000000..92eadc06cd49 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java @@ -0,0 +1,33 @@ +/* + * 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.util.Set; +import java.security.cert.X509Certificate; + +/** @hide */ +public class TestCertificateSource implements CertificateSource { + + private final Set<X509Certificate> mCertificates; + public TestCertificateSource(Set<X509Certificate> certificates) { + mCertificates = certificates; + } + + public Set<X509Certificate> getCertificates() { + return mCertificates; + } +} diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java new file mode 100644 index 000000000000..609f481a312c --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java @@ -0,0 +1,39 @@ +/* + * 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.util.Pair; +import java.util.Set; + +/** @hide */ +public class TestConfigSource implements ConfigSource { + private final Set<Pair<Domain, NetworkSecurityConfig>> mConfigs; + private final NetworkSecurityConfig mDefaultConfig; + public TestConfigSource(Set<Pair<Domain, NetworkSecurityConfig>> configs, + NetworkSecurityConfig defaultConfig) { + mConfigs = configs; + mDefaultConfig = defaultConfig; + } + + public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { + return mConfigs; + } + + public NetworkSecurityConfig getDefaultConfig() { + return mDefaultConfig; + } +} |