summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/idmap/create.cpp4
-rw-r--r--cmds/idmap/scan.cpp4
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java78
-rw-r--r--cmds/sm/src/com/android/commands/sm/Sm.java1
-rw-r--r--core/java/android/security/net/config/ApplicationConfig.java132
-rw-r--r--core/java/android/security/net/config/CertificateSource.java25
-rw-r--r--core/java/android/security/net/config/CertificatesEntryRef.java41
-rw-r--r--core/java/android/security/net/config/ConfigSource.java26
-rw-r--r--core/java/android/security/net/config/Domain.java57
-rw-r--r--core/java/android/security/net/config/NetworkSecurityConfig.java242
-rw-r--r--core/java/android/security/net/config/NetworkSecurityTrustManager.java135
-rw-r--r--core/java/android/security/net/config/Pin.java78
-rw-r--r--core/java/android/security/net/config/PinSet.java46
-rw-r--r--core/java/android/security/net/config/ResourceCertificateSource.java72
-rw-r--r--core/java/android/security/net/config/RootTrustManager.java74
-rw-r--r--core/java/android/security/net/config/SystemCertificateSource.java101
-rw-r--r--core/java/android/security/net/config/TrustAnchor.java33
-rw-r--r--core/java/android/security/net/config/UserCertificateSource.java92
-rw-r--r--core/java/android/security/net/config/XmlConfigSource.java328
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rw-r--r--core/jni/Android.mk2
-rw-r--r--libs/hwui/ShadowTessellator.cpp5
-rw-r--r--services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java8
-rw-r--r--tests/NetworkSecurityConfigTest/Android.mk15
-rw-r--r--tests/NetworkSecurityConfigTest/AndroidManifest.xml29
-rw-r--r--tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.derbin0 -> 1473 bytes
-rw-r--r--tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem35
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/attributes.xml5
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml10
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml10
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml10
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml10
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml7
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml8
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml9
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/domain1.xml14
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/empty_config.xml3
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml7
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml10
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml21
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml15
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml18
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml12
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/override_pins.xml12
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/pins1.xml9
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml13
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml13
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/subdomains.xml14
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java211
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java33
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java39
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java76
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java313
53 files changed, 2508 insertions, 59 deletions
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 6b2f46099b80..c13d318f7449 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -1,6 +1,6 @@
#include "idmap.h"
-#include <UniquePtr.h>
+#include <memory>
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
#include <androidfw/ZipFileRO.h>
@@ -15,7 +15,7 @@ using namespace android;
namespace {
int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc)
{
- UniquePtr<ZipFileRO> zip(ZipFileRO::open(zip_path));
+ std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(zip_path));
if (zip.get() == NULL) {
return -1;
}
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index f1b2f9e72ca0..6d30f0d0ce34 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -4,7 +4,7 @@
#include "idmap.h"
-#include <UniquePtr.h>
+#include <memory>
#include <androidfw/ResourceTypes.h>
#include <androidfw/StreamingZipInflater.h>
#include <androidfw/ZipFileRO.h>
@@ -120,7 +120,7 @@ namespace {
int parse_apk(const char *path, const char *target_package_name)
{
- UniquePtr<ZipFileRO> zip(ZipFileRO::open(path));
+ std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(path));
if (zip.get() == NULL) {
ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path);
return -1;
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 4a0a49b4e777..005153178298 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -764,8 +764,7 @@ public final class Pm {
String optionData = nextOptionData();
if (optionData == null || !isNumber(optionData)) {
System.err.println("Error: no USER_ID specified");
- showUsage();
- return 1;
+ return showUsage();
} else {
userId = Integer.parseInt(optionData);
}
@@ -868,8 +867,7 @@ public final class Pm {
}
} else {
System.err.println("Error: unknown option: " + opt);
- showUsage();
- return 1;
+ return showUsage();
}
}
@@ -877,16 +875,14 @@ public final class Pm {
final String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified.");
- showUsage();
- return 1;
+ return showUsage();
}
// State to apply; {always|ask|never|undefined}, required
final String modeString = nextArg();
if (modeString == null) {
System.err.println("Error: no app link state specified.");
- showUsage();
- return 1;
+ return showUsage();
}
final int newMode;
@@ -955,8 +951,7 @@ public final class Pm {
}
} else {
System.err.println("Error: unknown option: " + opt);
- showUsage();
- return 1;
+ return showUsage();
}
}
@@ -964,8 +959,7 @@ public final class Pm {
final String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified.");
- showUsage();
- return 1;
+ return showUsage();
}
try {
@@ -1370,8 +1364,7 @@ public final class Pm {
String optionData = nextOptionData();
if (optionData == null || !isNumber(optionData)) {
System.err.println("Error: no USER_ID specified");
- showUsage();
- return 1;
+ return showUsage();
} else {
userId = Integer.parseInt(optionData);
}
@@ -1379,8 +1372,7 @@ public final class Pm {
flags |= UserInfo.FLAG_MANAGED_PROFILE;
} else {
System.err.println("Error: unknown option " + opt);
- showUsage();
- return 1;
+ return showUsage();
}
}
String arg = nextArg();
@@ -1557,8 +1549,7 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified");
- showUsage();
- return 1;
+ return showUsage();
}
if (userId == UserHandle.USER_ALL) {
@@ -1626,8 +1617,7 @@ public final class Pm {
String optionData = nextOptionData();
if (optionData == null || !isNumber(optionData)) {
System.err.println("Error: no USER_ID specified");
- showUsage();
- return 1;
+ return showUsage();
} else {
userId = Integer.parseInt(optionData);
}
@@ -1636,8 +1626,7 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified");
- showUsage();
- return 1;
+ return showUsage();
}
ClearDataObserver obs = new ClearDataObserver();
@@ -1698,8 +1687,7 @@ public final class Pm {
String optionData = nextOptionData();
if (optionData == null || !isNumber(optionData)) {
System.err.println("Error: no USER_ID specified");
- showUsage();
- return 1;
+ return showUsage();
} else {
userId = Integer.parseInt(optionData);
}
@@ -1708,8 +1696,7 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package or component specified");
- showUsage();
- return 1;
+ return showUsage();
}
ComponentName cn = ComponentName.unflattenFromString(pkg);
if (cn == null) {
@@ -1747,8 +1734,7 @@ public final class Pm {
String optionData = nextOptionData();
if (optionData == null || !isNumber(optionData)) {
System.err.println("Error: no USER_ID specified");
- showUsage();
- return 1;
+ return showUsage();
} else {
userId = Integer.parseInt(optionData);
}
@@ -1757,8 +1743,7 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package or component specified");
- showUsage();
- return 1;
+ return showUsage();
}
try {
mPm.setApplicationHiddenSettingAsUser(pkg, state, userId);
@@ -1785,14 +1770,12 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified");
- showUsage();
- return 1;
+ return showUsage();
}
String perm = nextArg();
if (perm == null) {
System.err.println("Error: no permission specified");
- showUsage();
- return 1;
+ return showUsage();
}
try {
@@ -1808,8 +1791,7 @@ public final class Pm {
return 1;
} catch (IllegalArgumentException e) {
System.err.println("Bad argument: " + e.toString());
- showUsage();
- return 1;
+ return showUsage();
} catch (SecurityException e) {
System.err.println("Operation not allowed: " + e.toString());
return 1;
@@ -1826,8 +1808,7 @@ public final class Pm {
return 1;
} catch (IllegalArgumentException e) {
System.err.println("Bad argument: " + e.toString());
- showUsage();
- return 1;
+ return showUsage();
} catch (SecurityException e) {
System.err.println("Operation not allowed: " + e.toString());
return 1;
@@ -1838,14 +1819,12 @@ public final class Pm {
final String permission = nextArg();
if (permission == null) {
System.err.println("Error: no permission specified");
- showUsage();
- return 1;
+ return showUsage();
}
final String enforcedRaw = nextArg();
if (enforcedRaw == null) {
System.err.println("Error: no enforcement specified");
- showUsage();
- return 1;
+ return showUsage();
}
final boolean enforced = Boolean.parseBoolean(enforcedRaw);
try {
@@ -1857,8 +1836,7 @@ public final class Pm {
return 1;
} catch (IllegalArgumentException e) {
System.err.println("Bad argument: " + e.toString());
- showUsage();
- return 1;
+ return showUsage();
} catch (SecurityException e) {
System.err.println("Operation not allowed: " + e.toString());
return 1;
@@ -1884,8 +1862,7 @@ public final class Pm {
String size = nextArg();
if (size == null) {
System.err.println("Error: no size specified");
- showUsage();
- return 1;
+ return showUsage();
}
int len = size.length();
long multiplier = 1;
@@ -1899,8 +1876,7 @@ public final class Pm {
multiplier = 1024L*1024L*1024L;
} else {
System.err.println("Invalid suffix: " + c);
- showUsage();
- return 1;
+ return showUsage();
}
size = size.substring(0, len-1);
}
@@ -1909,8 +1885,7 @@ public final class Pm {
sizeVal = Long.parseLong(size) * multiplier;
} catch (NumberFormatException e) {
System.err.println("Error: expected number at: " + size);
- showUsage();
- return 1;
+ return showUsage();
}
String volumeUuid = nextArg();
if ("internal".equals(volumeUuid)) {
@@ -1934,8 +1909,7 @@ public final class Pm {
return 1;
} catch (IllegalArgumentException e) {
System.err.println("Bad argument: " + e.toString());
- showUsage();
- return 1;
+ return showUsage();
} catch (SecurityException e) {
System.err.println("Operation not allowed: " + e.toString());
return 1;
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 0a1ba4d19c5c..1ee60b068694 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -42,6 +42,7 @@ public final class Sm {
} catch (Exception e) {
if (e instanceof IllegalArgumentException) {
showUsage();
+ System.exit(1);
}
Log.e(TAG, "Error", e);
System.err.println("Error: " + e);
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
new file mode 100644
index 000000000000..9bf344a0e2bc
--- /dev/null
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -0,0 +1,132 @@
+/*
+ * 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.Locale;
+import java.util.Set;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An application's network security configuration.
+ *
+ * <p>{@link #getConfigForHostname(String)} provides a means to obtain network security
+ * configuration to be used for communicating with a specific hostname.</p>
+ *
+ * @hide
+ */
+public final class ApplicationConfig {
+ private Set<Pair<Domain, NetworkSecurityConfig>> mConfigs;
+ private NetworkSecurityConfig mDefaultConfig;
+ private X509TrustManager mTrustManager;
+
+ private ConfigSource mConfigSource;
+ private boolean mInitialized;
+ private final Object mLock = new Object();
+
+ public ApplicationConfig(ConfigSource configSource) {
+ mConfigSource = configSource;
+ mInitialized = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean hasPerDomainConfigs() {
+ ensureInitialized();
+ return mConfigs != null && !mConfigs.isEmpty();
+ }
+
+ /**
+ * Get the {@link NetworkSecurityConfig} corresponding to the provided hostname.
+ * When matching the most specific matching domain rule will be used, if no match exists
+ * then the default configuration will be returned.
+ *
+ * {@code NetworkSecurityConfig} objects returned by this method can be safely cached for
+ * {@code hostname}. Subsequent calls with the same hostname will always return the same
+ * {@code NetworkSecurityConfig}.
+ *
+ * @return {@link NetworkSecurityConfig} to be used to determine
+ * the network security configuration for connections to {@code hostname}.
+ */
+ public NetworkSecurityConfig getConfigForHostname(String hostname) {
+ ensureInitialized();
+ if (hostname.isEmpty() || mConfigs == null) {
+ return mDefaultConfig;
+ }
+ if (hostname.charAt(0) == '.') {
+ throw new IllegalArgumentException("hostname must not begin with a .");
+ }
+ // Domains are case insensitive.
+ hostname = hostname.toLowerCase(Locale.US);
+ // Normalize hostname by removing trailing . if present, all Domain hostnames are
+ // absolute.
+ if (hostname.charAt(hostname.length() - 1) == '.') {
+ hostname = hostname.substring(0, hostname.length() - 1);
+ }
+ // Find the Domain -> NetworkSecurityConfig entry with the most specific matching
+ // Domain entry for hostname.
+ // TODO: Use a smarter data structure for the lookup.
+ Pair<Domain, NetworkSecurityConfig> bestMatch = null;
+ for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) {
+ Domain domain = entry.first;
+ NetworkSecurityConfig config = entry.second;
+ // Check for an exact match.
+ if (domain.hostname.equals(hostname)) {
+ return config;
+ }
+ // Otherwise check if the Domain includes sub-domains and that the hostname is a
+ // sub-domain of the Domain.
+ if (domain.subdomainsIncluded
+ && hostname.endsWith(domain.hostname)
+ && hostname.charAt(hostname.length() - domain.hostname.length() - 1) == '.') {
+ if (bestMatch == null) {
+ bestMatch = entry;
+ } else if (domain.hostname.length() > bestMatch.first.hostname.length()) {
+ bestMatch = entry;
+ }
+ }
+ }
+ if (bestMatch != null) {
+ return bestMatch.second;
+ }
+ // If no match was found use the default configuration.
+ return mDefaultConfig;
+ }
+
+ /**
+ * Returns the {@link X509TrustManager} that implements the checking of trust anchors and
+ * certificate pinning based on this configuration.
+ */
+ public X509TrustManager getTrustManager() {
+ ensureInitialized();
+ return mTrustManager;
+ }
+
+ private void ensureInitialized() {
+ synchronized(mLock) {
+ if (mInitialized) {
+ return;
+ }
+ mConfigs = mConfigSource.getPerDomainConfigs();
+ mDefaultConfig = mConfigSource.getDefaultConfig();
+ mConfigSource = null;
+ mTrustManager = new RootTrustManager(this);
+ mInitialized = true;
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/CertificateSource.java b/core/java/android/security/net/config/CertificateSource.java
new file mode 100644
index 000000000000..386354dc4d57
--- /dev/null
+++ b/core/java/android/security/net/config/CertificateSource.java
@@ -0,0 +1,25 @@
+/*
+ * 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 interface CertificateSource {
+ Set<X509Certificate> getCertificates();
+}
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
new file mode 100644
index 000000000000..2ba38c21c330
--- /dev/null
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ArraySet;
+import java.util.Set;
+import java.security.cert.X509Certificate;
+
+/** @hide */
+public final class CertificatesEntryRef {
+ private final CertificateSource mSource;
+ private final boolean mOverridesPins;
+
+ public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {
+ mSource = source;
+ mOverridesPins = overridesPins;
+ }
+
+ public Set<TrustAnchor> getTrustAnchors() {
+ // TODO: cache this [but handle mutable sources]
+ Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
+ for (X509Certificate cert : mSource.getCertificates()) {
+ anchors.add(new TrustAnchor(cert, mOverridesPins));
+ }
+ return anchors;
+ }
+}
diff --git a/core/java/android/security/net/config/ConfigSource.java b/core/java/android/security/net/config/ConfigSource.java
new file mode 100644
index 000000000000..4adf265c678c
--- /dev/null
+++ b/core/java/android/security/net/config/ConfigSource.java
@@ -0,0 +1,26 @@
+/*
+ * 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 interface ConfigSource {
+ Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs();
+ NetworkSecurityConfig getDefaultConfig();
+}
diff --git a/core/java/android/security/net/config/Domain.java b/core/java/android/security/net/config/Domain.java
new file mode 100644
index 000000000000..5bb727a38033
--- /dev/null
+++ b/core/java/android/security/net/config/Domain.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Locale;
+/** @hide */
+public final class Domain {
+ /**
+ * Lower case hostname for this domain rule.
+ */
+ public final String hostname;
+
+ /**
+ * Whether this domain includes subdomains.
+ */
+ public final boolean subdomainsIncluded;
+
+ public Domain(String hostname, boolean subdomainsIncluded) {
+ if (hostname == null) {
+ throw new NullPointerException("Hostname must not be null");
+ }
+ this.hostname = hostname.toLowerCase(Locale.US);
+ this.subdomainsIncluded = subdomainsIncluded;
+ }
+
+ @Override
+ public int hashCode() {
+ return hostname.hashCode() ^ (subdomainsIncluded ? 1231 : 1237);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Domain)) {
+ return false;
+ }
+ Domain otherDomain = (Domain) other;
+ return otherDomain.subdomainsIncluded == this.subdomainsIncluded &&
+ otherDomain.hostname.equals(this.hostname);
+ }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
new file mode 100644
index 000000000000..1c787b407086
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -0,0 +1,242 @@
+/*
+ * 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.ArraySet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * @hide
+ */
+public final class NetworkSecurityConfig {
+ /** @hide */
+ public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
+ /** @hide */
+ public static final boolean DEFAULT_HSTS_ENFORCED = false;
+ public static final NetworkSecurityConfig DEFAULT = getDefaultBuilder().build();
+
+ private final boolean mCleartextTrafficPermitted;
+ private final boolean mHstsEnforced;
+ private final PinSet mPins;
+ private final List<CertificatesEntryRef> mCertificatesEntryRefs;
+ private Set<TrustAnchor> mAnchors;
+ private final Object mAnchorsLock = new Object();
+ private X509TrustManager mTrustManager;
+ private final Object mTrustManagerLock = new Object();
+
+ private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
+ PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
+ mCleartextTrafficPermitted = cleartextTrafficPermitted;
+ mHstsEnforced = hstsEnforced;
+ mPins = pins;
+ mCertificatesEntryRefs = certificatesEntryRefs;
+ }
+
+ public Set<TrustAnchor> getTrustAnchors() {
+ synchronized (mAnchorsLock) {
+ if (mAnchors != null) {
+ return mAnchors;
+ }
+ Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
+ for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
+ anchors.addAll(ref.getTrustAnchors());
+ }
+ mAnchors = anchors;
+ return anchors;
+ }
+ }
+
+ public boolean isCleartextTrafficPermitted() {
+ return mCleartextTrafficPermitted;
+ }
+
+ public boolean isHstsEnforced() {
+ return mHstsEnforced;
+ }
+
+ public PinSet getPins() {
+ return mPins;
+ }
+
+ public X509TrustManager getTrustManager() {
+ synchronized(mTrustManagerLock) {
+ if (mTrustManager == null) {
+ mTrustManager = new NetworkSecurityTrustManager(this);
+ }
+ return mTrustManager;
+ }
+ }
+
+ void onTrustStoreChange() {
+ synchronized (mAnchorsLock) {
+ mAnchors = null;
+ }
+ }
+
+ /**
+ * Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
+ *
+ * <p>
+ * The default configuration has the following properties:
+ * <ol>
+ * <li>Cleartext traffic is permitted.</li>
+ * <li>HSTS is not enforced.</li>
+ * <li>No certificate pinning is used.</li>
+ * <li>The system and user added trusted certificate stores are trusted for connections.</li>
+ * </ol>
+ *
+ * @hide
+ */
+ public static final Builder getDefaultBuilder() {
+ return new Builder()
+ .setCleartextTrafficPermitted(DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)
+ .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
+ // System certificate store, does not bypass static pins.
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ // User certificate store, does not bypass static pins.
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
+ }
+
+ /**
+ * Builder for creating {@code NetworkSecurityConfig} objects.
+ * @hide
+ */
+ public static final class Builder {
+ private List<CertificatesEntryRef> mCertificatesEntryRefs;
+ private PinSet mPinSet;
+ private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
+ private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED;
+ private boolean mCleartextTrafficPermittedSet = false;
+ private boolean mHstsEnforcedSet = false;
+ private Builder mParentBuilder;
+
+ /**
+ * Sets the parent {@code Builder} for this {@code Builder}.
+ * The parent will be used to determine values not configured in this {@code Builder}
+ * in {@link Builder#build()}, recursively if needed.
+ */
+ public Builder setParent(Builder parent) {
+ // Sanity check to avoid adding loops.
+ Builder current = parent;
+ while (current != null) {
+ if (current == this) {
+ throw new IllegalArgumentException("Loops are not allowed in Builder parents");
+ }
+ current = current.getParent();
+ }
+ mParentBuilder = parent;
+ return this;
+ }
+
+ public Builder getParent() {
+ return mParentBuilder;
+ }
+
+ public Builder setPinSet(PinSet pinSet) {
+ mPinSet = pinSet;
+ return this;
+ }
+
+ private PinSet getEffectivePinSet() {
+ if (mPinSet != null) {
+ return mPinSet;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectivePinSet();
+ }
+ return PinSet.EMPTY_PINSET;
+ }
+
+ public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) {
+ mCleartextTrafficPermitted = cleartextTrafficPermitted;
+ mCleartextTrafficPermittedSet = true;
+ return this;
+ }
+
+ private boolean getEffectiveCleartextTrafficPermitted() {
+ if (mCleartextTrafficPermittedSet) {
+ return mCleartextTrafficPermitted;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveCleartextTrafficPermitted();
+ }
+ return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
+ }
+
+ public Builder setHstsEnforced(boolean hstsEnforced) {
+ mHstsEnforced = hstsEnforced;
+ mHstsEnforcedSet = true;
+ return this;
+ }
+
+ private boolean getEffectiveHstsEnforced() {
+ if (mHstsEnforcedSet) {
+ return mHstsEnforced;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveHstsEnforced();
+ }
+ return DEFAULT_HSTS_ENFORCED;
+ }
+
+ public Builder addCertificatesEntryRef(CertificatesEntryRef ref) {
+ if (mCertificatesEntryRefs == null) {
+ mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
+ }
+ mCertificatesEntryRefs.add(ref);
+ return this;
+ }
+
+ public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) {
+ if (mCertificatesEntryRefs == null) {
+ mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
+ }
+ mCertificatesEntryRefs.addAll(refs);
+ return this;
+ }
+
+ private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() {
+ if (mCertificatesEntryRefs != null) {
+ return mCertificatesEntryRefs;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveCertificatesEntryRefs();
+ }
+ return Collections.<CertificatesEntryRef>emptyList();
+ }
+
+ public boolean hasCertificateEntryRefs() {
+ return mCertificatesEntryRefs != null;
+ }
+
+ public NetworkSecurityConfig build() {
+ boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
+ boolean hstsEnforced = getEffectiveHstsEnforced();
+ PinSet pinSet = getEffectivePinSet();
+ List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();
+ return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
new file mode 100644
index 000000000000..e69082d3deec
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -0,0 +1,135 @@
+/*
+ * 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 com.android.org.conscrypt.TrustManagerImpl;
+
+import android.util.ArrayMap;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * {@link X509TrustManager} that implements the trust anchor and pinning for a
+ * given {@link NetworkSecurityConfig}.
+ * @hide
+ */
+public class NetworkSecurityTrustManager implements X509TrustManager {
+ // TODO: Replace this with a general X509TrustManager and use duck-typing.
+ private final TrustManagerImpl mDelegate;
+ private final NetworkSecurityConfig mNetworkSecurityConfig;
+
+ public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
+ if (config == null) {
+ throw new NullPointerException("config must not be null");
+ }
+ mNetworkSecurityConfig = config;
+ // TODO: Create our own better KeyStoreImpl
+ try {
+ KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
+ store.load(null);
+ int certNum = 0;
+ for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
+ store.setEntry(String.valueOf(certNum++),
+ new KeyStore.TrustedCertificateEntry(anchor.certificate),
+ null);
+ }
+ mDelegate = new TrustManagerImpl(store);
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ throw new CertificateException("Client authentication not supported");
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+ List<X509Certificate> trustedChain =
+ mDelegate.checkServerTrusted(certs, authType, (String) null);
+ checkPins(trustedChain);
+ }
+
+ private void checkPins(List<X509Certificate> chain) throws CertificateException {
+ PinSet pinSet = mNetworkSecurityConfig.getPins();
+ if (pinSet.pins.isEmpty()
+ || System.currentTimeMillis() > pinSet.expirationTime
+ || !isPinningEnforced(chain)) {
+ return;
+ }
+ Set<String> pinAlgorithms = pinSet.getPinAlgorithms();
+ Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>(
+ pinAlgorithms.size());
+ for (int i = chain.size() - 1; i >= 0 ; i--) {
+ X509Certificate cert = chain.get(i);
+ byte[] encodedSPKI = cert.getPublicKey().getEncoded();
+ for (String algorithm : pinAlgorithms) {
+ MessageDigest md = digestMap.get(algorithm);
+ if (md == null) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ digestMap.put(algorithm, md);
+ }
+ if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) {
+ return;
+ }
+ }
+ }
+
+ // TODO: Throw a subclass of CertificateException which indicates a pinning failure.
+ throw new CertificateException("Pin verification failed");
+ }
+
+ private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {
+ if (chain.isEmpty()) {
+ return false;
+ }
+ X509Certificate anchorCert = chain.get(chain.size() - 1);
+ TrustAnchor chainAnchor = null;
+ // TODO: faster lookup
+ for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
+ if (anchor.certificate.equals(anchorCert)) {
+ chainAnchor = anchor;
+ break;
+ }
+ }
+ if (chainAnchor == null) {
+ throw new CertificateException("Trusted chain does not end in a TrustAnchor");
+ }
+ return !chainAnchor.overridesPins;
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+}
diff --git a/core/java/android/security/net/config/Pin.java b/core/java/android/security/net/config/Pin.java
new file mode 100644
index 000000000000..94520e22bd02
--- /dev/null
+++ b/core/java/android/security/net/config/Pin.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Arrays;
+
+/** @hide */
+public final class Pin {
+ public final String digestAlgorithm;
+ public final byte[] digest;
+
+ private final int mHashCode;
+
+ public Pin(String digestAlgorithm, byte[] digest) {
+ this.digestAlgorithm = digestAlgorithm;
+ 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;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Pin)) {
+ return false;
+ }
+ Pin other = (Pin) obj;
+ if (other.hashCode() != mHashCode) {
+ return false;
+ }
+ if (!Arrays.equals(digest, other.digest)) {
+ return false;
+ }
+ if (!digestAlgorithm.equals(other.digestAlgorithm)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/security/net/config/PinSet.java b/core/java/android/security/net/config/PinSet.java
new file mode 100644
index 000000000000..d3c975eb3101
--- /dev/null
+++ b/core/java/android/security/net/config/PinSet.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ArraySet;
+import java.util.Collections;
+import java.util.Set;
+
+/** @hide */
+public final class PinSet {
+ public static final PinSet EMPTY_PINSET =
+ new PinSet(Collections.<Pin>emptySet(), Long.MAX_VALUE);
+ public final long expirationTime;
+ public final Set<Pin> pins;
+
+ public PinSet(Set<Pin> pins, long expirationTime) {
+ if (pins == null) {
+ throw new NullPointerException("pins must not be null");
+ }
+ this.pins = pins;
+ this.expirationTime = expirationTime;
+ }
+
+ Set<String> getPinAlgorithms() {
+ // TODO: Cache this.
+ Set<String> algorithms = new ArraySet<String>();
+ for (Pin pin : pins) {
+ algorithms.add(pin.digestAlgorithm);
+ }
+ return algorithms;
+ }
+}
diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java
new file mode 100644
index 000000000000..06dd9d42e364
--- /dev/null
+++ b/core/java/android/security/net/config/ResourceCertificateSource.java
@@ -0,0 +1,72 @@
+/*
+ * 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.util.ArraySet;
+import libcore.io.IoUtils;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * {@link CertificateSource} based on certificates contained in an application resource file.
+ * @hide
+ */
+public class ResourceCertificateSource implements CertificateSource {
+ private Set<X509Certificate> mCertificates;
+ private final int mResourceId;
+ private Context mContext;
+ private final Object mLock = new Object();
+
+ public ResourceCertificateSource(int resourceId, Context context) {
+ mResourceId = resourceId;
+ mContext = context.getApplicationContext();
+ }
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ synchronized (mLock) {
+ if (mCertificates != null) {
+ return mCertificates;
+ }
+ Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
+ Collection<? extends Certificate> certs;
+ InputStream in = null;
+ try {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ in = mContext.getResources().openRawResource(mResourceId);
+ certs = factory.generateCertificates(in);
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to load trust anchors from id " + mResourceId,
+ e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ for (Certificate cert : certs) {
+ certificates.add((X509Certificate) cert);
+ }
+ mCertificates = certificates;
+ mContext = null;
+ return mCertificates;
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
new file mode 100644
index 000000000000..1338b9ff97d4
--- /dev/null
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -0,0 +1,74 @@
+/*
+ * 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.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * {@link X509TrustManager} based on an {@link ApplicationConfig}.
+ *
+ * <p>This {@code X509TrustManager} delegates to the specific trust manager for the hostname
+ * being used for the connection (See {@link ApplicationConfig#getConfigForHostname(String)} and
+ * {@link NetworkSecurityTrustManager}).</p>
+ *
+ * Note that if the {@code ApplicationConfig} has per-domain configurations the hostname aware
+ * {@link #checkServerTrusted(X509Certificate[], String String)} must be used instead of the normal
+ * non-aware call.
+ * @hide */
+public class RootTrustManager implements X509TrustManager {
+ private final ApplicationConfig mConfig;
+ private static final X509Certificate[] EMPTY_ISSUERS = new X509Certificate[0];
+
+ public RootTrustManager(ApplicationConfig config) {
+ if (config == null) {
+ throw new NullPointerException("config must not be null");
+ }
+ mConfig = config;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ throw new CertificateException("Client authentication not supported");
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType)
+ throws CertificateException {
+ if (mConfig.hasPerDomainConfigs()) {
+ throw new CertificateException(
+ "Domain specific configurations require that hostname aware"
+ + " checkServerTrusted(X509Certificate[], String, String) is used");
+ }
+ NetworkSecurityConfig config = mConfig.getConfigForHostname("");
+ config.getTrustManager().checkServerTrusted(certs, authType);
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs, String authType, String hostname)
+ throws CertificateException {
+ NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
+ config.getTrustManager().checkServerTrusted(certs, authType);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return EMPTY_ISSUERS;
+ }
+}
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
new file mode 100644
index 000000000000..7649a977ff5c
--- /dev/null
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -0,0 +1,101 @@
+/*
+ * 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.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import libcore.io.IoUtils;
+
+/**
+ * {@link CertificateSource} based on the system trusted CA store.
+ * @hide
+ */
+public class SystemCertificateSource implements CertificateSource {
+ private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
+ private Set<X509Certificate> mSystemCerts = null;
+ private final Object mLock = new Object();
+
+ private SystemCertificateSource() {
+ }
+
+ public static SystemCertificateSource getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ // TODO: loading all of these is wasteful, we should instead use a keystore style API.
+ synchronized (mLock) {
+ if (mSystemCerts != null) {
+ return mSystemCerts;
+ }
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+
+ final String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
+ final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
+ final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+ final File userRemovedCaDir = new File(configDir, "cacerts-removed");
+ // Sanity check
+ if (!systemCaDir.isDirectory()) {
+ throw new AssertionError(systemCaDir + " is not a directory");
+ }
+
+ Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>();
+ for (String caFile : systemCaDir.list()) {
+ // Skip any CAs in the user's deleted directory.
+ if (new File(userRemovedCaDir, caFile).exists()) {
+ continue;
+ }
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(
+ new FileInputStream(new File(systemCaDir, caFile)));
+ systemCerts.add((X509Certificate) certFactory.generateCertificate(is));
+ } catch (CertificateException | IOException e) {
+ // Don't rethrow to be consistent with conscrypt's cert loading code.
+ continue;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ mSystemCerts = systemCerts;
+ return mSystemCerts;
+ }
+ }
+
+ public void onCertificateStorageChange() {
+ synchronized (mLock) {
+ mSystemCerts = null;
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/TrustAnchor.java b/core/java/android/security/net/config/TrustAnchor.java
new file mode 100644
index 000000000000..b62d85fa37ae
--- /dev/null
+++ b/core/java/android/security/net/config/TrustAnchor.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.security.cert.X509Certificate;
+
+/** @hide */
+public final class TrustAnchor {
+ public final X509Certificate certificate;
+ public final boolean overridesPins;
+
+ public TrustAnchor(X509Certificate certificate, boolean overridesPins) {
+ if (certificate == null) {
+ throw new NullPointerException("certificate");
+ }
+ this.certificate = certificate;
+ this.overridesPins = overridesPins;
+ }
+}
diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java
new file mode 100644
index 000000000000..e9d5aa1e2f34
--- /dev/null
+++ b/core/java/android/security/net/config/UserCertificateSource.java
@@ -0,0 +1,92 @@
+/*
+ * 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.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import libcore.io.IoUtils;
+
+/**
+ * {@link CertificateSource} based on the user-installed trusted CA store.
+ * @hide
+ */
+public class UserCertificateSource implements CertificateSource {
+ private static final UserCertificateSource INSTANCE = new UserCertificateSource();
+ private Set<X509Certificate> mUserCerts = null;
+ private final Object mLock = new Object();
+
+ private UserCertificateSource() {
+ }
+
+ public static UserCertificateSource getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ // TODO: loading all of these is wasteful, we should instead use a keystore style API.
+ synchronized (mLock) {
+ if (mUserCerts != null) {
+ return mUserCerts;
+ }
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+ final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+ final File userCaDir = new File(configDir, "cacerts-added");
+ Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
+ // If the user hasn't added any certificates the directory may not exist.
+ if (userCaDir.isDirectory()) {
+ for (String caFile : userCaDir.list()) {
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(
+ new FileInputStream(new File(userCaDir, caFile)));
+ userCerts.add((X509Certificate) certFactory.generateCertificate(is));
+ } catch (CertificateException | IOException e) {
+ // Don't rethrow to be consistent with conscrypt's cert loading code.
+ continue;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ mUserCerts = userCerts;
+ return mUserCerts;
+ }
+ }
+
+ public void onCertificateStorageChange() {
+ synchronized (mLock) {
+ mUserCerts = null;
+ }
+ }
+}
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..6abfb66f9450
--- /dev/null
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -0,0 +1,328 @@
+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 List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
+ XmlResourceParser parser, Set<String> seenDomains,
+ NetworkSecurityConfig.Builder parentBuilder, boolean baseConfig)
+ throws IOException, XmlPullParserException, ParserException {
+ List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
+ NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
+ builder.setParent(parentBuilder);
+ Set<Domain> domains = new ArraySet<>();
+ boolean seenPinSet = false;
+ boolean seenTrustAnchors = false;
+ String configName = parser.getName();
+ int outerDepth = parser.getDepth();
+ // Add this builder now so that this builder occurs before any of its children. This
+ // makes the final build pass easier.
+ builders.add(new Pair<>(builder, domains));
+ // 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();
+ 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 if ("domain-config".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser,
+ "Nested domain-config not allowed in base-config");
+ }
+ builders.addAll(parseConfigEntry(parser, seenDomains, builder, false));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ if (!baseConfig && domains.isEmpty()) {
+ throw new ParserException(parser, "No domain elements in domain-config");
+ }
+ return builders;
+ }
+
+ 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, null, true).get(0).first;
+ } else if ("domain-config".equals(parser.getName())) {
+ builders.addAll(parseConfigEntry(parser, seenDomains, baseConfigBuilder, 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;
+ // Set the parent of configs that do not have a parent to the base-config. This can
+ // happen if the base-config comes after a domain-config in the file.
+ // Note that this is safe with regards to children because of the order that
+ // parseConfigEntry returns builders, the parent is always before the children. The
+ // children builders will not have build called until _after_ their parents have their
+ // parent set so everything is consistent.
+ if (builder.getParent() == null) {
+ 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/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 77f1efaefafe..4bcfa4caf158 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -519,7 +519,7 @@ public class ZygoteInit {
String args[] = {
"--setuid=1000",
"--setgid=1000",
- "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007",
+ "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007,3009",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 47b2f3b0f6ab..4c68e015e531 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -273,7 +273,7 @@ LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunre
# -Wno-c++11-extensions: Clang warns about Skia using the C++11 override keyword, but this project
# is not being compiled with that level. Remove once this has changed.
-LOCAL_CFLAGS += -Wno-c++11-extensions
+LOCAL_CLANG_CFLAGS += -Wno-c++11-extensions
# b/22414716: thread_local (android/graphics/Paint.cpp) and Clang don't like each other at the
# moment.
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index 09d125839a68..595c10c88554 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -80,6 +80,11 @@ void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque,
ALOGD("light center %f %f %f",
adjustedLightCenter.x, adjustedLightCenter.y, adjustedLightCenter.z);
#endif
+ if (isnan(adjustedLightCenter.x)
+ || isnan(adjustedLightCenter.y)
+ || isnan(adjustedLightCenter.z)) {
+ return;
+ }
// light position (because it's in local space) needs to compensate for receiver transform
// TODO: should apply to light orientation, not just position
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 6fbe8e448476..a7682dc06d18 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.app.DownloadManager;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal.PackagesProvider;
@@ -673,7 +674,12 @@ final class DefaultPermissionGrantPolicy {
Intent intent, int userId) {
ResolveInfo handler = mService.resolveIntent(intent,
intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
- if (handler == null) {
+ if (handler == null || handler.activityInfo == null) {
+ return null;
+ }
+ ActivityInfo activityInfo = handler.activityInfo;
+ if (activityInfo.packageName.equals(mService.mResolveActivity.packageName)
+ && activityInfo.name.equals(mService.mResolveActivity.name)) {
return null;
}
return getSystemPackageLPr(handler.activityInfo.packageName);
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..4c1fbd375e1e
--- /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.net.config"
+ android:sharedUserId="android.uid.system">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ 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 000000000000..235bd4797b78
--- /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 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/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
new file mode 100644
index 000000000000..d45fd77a5f0f
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain includeSubdomains="true">android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ <!-- nested config that adds pins -->
+ <domain-config>
+ <domain>developer.android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+ </domain-config>
+ <base-config cleartextTrafficPermitted="false">
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml
new file mode 100644
index 000000000000..84e06e324513
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config cleartextTrafficPermitted="false">
+ </base-config>
+ <!-- Nested config that overrides parent -->
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">android.com</domain>
+ <domain-config cleartextTrafficPermitted="false">
+ <domain>developer.android.com</domain>
+ </domain-config>
+ </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
new file mode 100644
index 000000000000..9f48d56cb56b
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -0,0 +1,211 @@
+/*
+ * 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;
+ }
+
+
+
+ /**
+ * 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.Builder().build();
+ }
+
+ private NetworkSecurityConfig getSystemStoreConfig() {
+ return new NetworkSecurityConfig.Builder()
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
+ }
+
+ public void testEmptyConfig() throws Exception {
+ ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
+ = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
+ ConfigSource testSource =
+ new TestConfigSource(domainMap, getEmptyConfig());
+ SSLContext context = TestUtils.getSSLContext(testSource);
+ TestUtils.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()));
+ NetworkSecurityConfig defaultConfig = getSystemStoreConfig();
+ 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 {
+ ArraySet<Pin> pins = new ArraySet<Pin>();
+ pins.add(new Pin("SHA-256", new byte[0]));
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
+ ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
+ = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
+ domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
+ new Domain("android.com", true), domain));
+ SSLContext context
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
+ }
+
+ public void testGoodPin() throws Exception {
+ ArraySet<Pin> pins = new ArraySet<Pin>();
+ pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
+ ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
+ = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
+ domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
+ new Domain("android.com", true), domain));
+ SSLContext context
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.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.
+ ArraySet<Pin> pins = new ArraySet<Pin>();
+ pins.add(new Pin("SHA-256", new byte[0]));
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
+ .build();
+ ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
+ = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
+ domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
+ new Domain("android.com", true), domain));
+ SSLContext context
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.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
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.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
+ = 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 = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ }
+
+ public void testConfigBuilderUsesParents() throws Exception {
+ // Check that a builder with a parent uses the parent's values when non is set.
+ NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
+ .setParent(NetworkSecurityConfig.getDefaultBuilder())
+ .build();
+ assert(!config.getTrustAnchors().isEmpty());
+ }
+
+ public void testConfigBuilderParentLoop() throws Exception {
+ NetworkSecurityConfig.Builder config1 = new NetworkSecurityConfig.Builder();
+ NetworkSecurityConfig.Builder config2 = new NetworkSecurityConfig.Builder();
+ config1.setParent(config2);
+ try {
+ config2.setParent(config1);
+ fail("Loop in NetworkSecurityConfig parents");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testWithUrlConnection() throws Exception {
+ ArraySet<Pin> pins = new ArraySet<Pin>();
+ pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
+ ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
+ = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
+ domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
+ new Domain("android.com", true), domain));
+ SSLContext context
+ = 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/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;
+ }
+}
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..f52a27995854
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -0,0 +1,313 @@
+/*
+ * 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);
+ }
+
+ public void testNestedDomainConfigs() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
+ NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
+ MoreAsserts.assertNotEqual(parent, child);
+ MoreAsserts.assertEmpty(parent.getPins().pins);
+ MoreAsserts.assertNotEmpty(child.getPins().pins);
+ // Check that the child inherited the cleartext value and anchors.
+ assertFalse(child.isCleartextTrafficPermitted());
+ MoreAsserts.assertNotEmpty(child.getTrustAnchors());
+ // Test connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ }
+
+ public void testNestedDomainConfigsOverride() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
+ NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
+ MoreAsserts.assertNotEqual(parent, child);
+ assertTrue(parent.isCleartextTrafficPermitted());
+ assertFalse(child.isCleartextTrafficPermitted());
+ }
+
+ 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);
+ }
+}