Merge "Remove unsupported RS graphics API tests."
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 6b2f460..c13d318 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 @@
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 f1b2f9e..6d30f0d 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 @@
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 4a0a49b..0051531 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 @@
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 @@
}
} else {
System.err.println("Error: unknown option: " + opt);
- showUsage();
- return 1;
+ return showUsage();
}
}
@@ -877,16 +875,14 @@
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 @@
}
} else {
System.err.println("Error: unknown option: " + opt);
- showUsage();
- return 1;
+ return showUsage();
}
}
@@ -964,8 +959,7 @@
final String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified.");
- showUsage();
- return 1;
+ return showUsage();
}
try {
@@ -1370,8 +1364,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0a1ba4d1..1ee60b0 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 @@
} 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/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index eb4ceda..7c23c23 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -18,6 +18,9 @@
import com.android.org.conscrypt.TrustManagerImpl;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -34,7 +37,11 @@
*/
public class X509TrustManagerExtensions {
- final TrustManagerImpl mDelegate;
+ private final TrustManagerImpl mDelegate;
+ // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
+ private final X509TrustManager mTrustManager;
+ private final Method mCheckServerTrusted;
+ private final Method mIsUserAddedCertificate;
/**
* Constructs a new X509TrustManagerExtensions wrapper.
@@ -45,10 +52,31 @@
public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
if (tm instanceof TrustManagerImpl) {
mDelegate = (TrustManagerImpl) tm;
- } else {
- mDelegate = null;
- throw new IllegalArgumentException("tm is an instance of " + tm.getClass().getName() +
- " which is not a supported type of X509TrustManager");
+ mTrustManager = null;
+ mCheckServerTrusted = null;
+ mIsUserAddedCertificate = null;
+ return;
+ }
+ // Use duck typing if possible.
+ mDelegate = null;
+ mTrustManager = tm;
+ // Check that the hostname aware checkServerTrusted is present.
+ try {
+ mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
+ X509Certificate[].class,
+ String.class,
+ String.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Required method"
+ + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+ }
+ // Check that isUserAddedCertificate is present.
+ try {
+ mIsUserAddedCertificate = tm.getClass().getMethod("isUserAddedCertificate",
+ X509Certificate.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "Required method isUserAddedCertificate(X509Certificate) missing");
}
}
@@ -64,7 +92,24 @@
*/
public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
String host) throws CertificateException {
- return mDelegate.checkServerTrusted(chain, authType, host);
+ if (mDelegate != null) {
+ return mDelegate.checkServerTrusted(chain, authType, host);
+ } else {
+ try {
+ return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
+ authType, host);
+ } catch (IllegalAccessException e) {
+ throw new CertificateException("Failed to call checkServerTrusted", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof CertificateException) {
+ throw (CertificateException) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new CertificateException("checkServerTrusted failed", e.getCause());
+ }
+ }
}
/**
@@ -78,6 +123,20 @@
* otherwise.
*/
public boolean isUserAddedCertificate(X509Certificate cert) {
- return mDelegate.isUserAddedCertificate(cert);
+ if (mDelegate != null) {
+ return mDelegate.isUserAddedCertificate(cert);
+ } else {
+ try {
+ return (Boolean) mIsUserAddedCertificate.invoke(mTrustManager, cert);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Failed to call isUserAddedCertificate", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new RuntimeException("isUserAddedCertificate failed", e.getCause());
+ }
+ }
+ }
}
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1273772b..1e879f2 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -603,6 +603,32 @@
}
/**
+ * {@hide}
+ * This will be the new name for writeFileDescriptor, for consistency.
+ **/
+ public final void writeRawFileDescriptor(FileDescriptor val) {
+ nativeWriteFileDescriptor(mNativePtr, val);
+ }
+
+ /**
+ * {@hide}
+ * Write an array of FileDescriptor objects into the Parcel.
+ *
+ * @param value The array of objects to be written.
+ */
+ public final void writeRawFileDescriptorArray(FileDescriptor[] value) {
+ if (value != null) {
+ int N = value.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeRawFileDescriptor(value[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /**
* Write a byte value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
@@ -1679,6 +1705,41 @@
return nativeReadFileDescriptor(mNativePtr);
}
+ /**
+ * {@hide}
+ * Read and return a new array of FileDescriptors from the parcel.
+ * @return the FileDescriptor array, or null if the array is null.
+ **/
+ public final FileDescriptor[] createRawFileDescriptorArray() {
+ int N = readInt();
+ if (N < 0) {
+ return null;
+ }
+ FileDescriptor[] f = new FileDescriptor[N];
+ for (int i = 0; i < N; i++) {
+ f[i] = readRawFileDescriptor();
+ }
+ return f;
+ }
+
+ /**
+ * {@hide}
+ * Read an array of FileDescriptors from a parcel.
+ * The passed array must be exactly the length of the array in the parcel.
+ * @return the FileDescriptor array, or null if the array is null.
+ **/
+ public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+ int N = readInt();
+ if (N == val.length) {
+ for (int i=0; i<N; i++) {
+ val[i] = readRawFileDescriptor();
+ }
+ } else {
+ throw new RuntimeException("bad array lengths");
+ }
+ }
+
+
/*package*/ static native FileDescriptor openFileDescriptor(String file,
int mode) throws FileNotFoundException;
/*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
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 0000000..48359d47
--- /dev/null
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -0,0 +1,161 @@
+/*
+ * 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 static ApplicationConfig sInstance;
+ private static Object sLock = new Object();
+
+ 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;
+ }
+ }
+
+ public static void setDefaultInstance(ApplicationConfig config) {
+ synchronized (sLock) {
+ sInstance = config;
+ }
+ }
+
+ public static ApplicationConfig getDefaultInstance() {
+ synchronized (sLock) {
+ return sInstance;
+ }
+ }
+
+ /** @hide */
+ public static ApplicationConfig getPlatformDefault() {
+ return new ApplicationConfig(new ConfigSource() {
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return NetworkSecurityConfig.DEFAULT;
+ }
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return null;
+ }
+ });
+ }
+}
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 0000000..386354d
--- /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 0000000..2ba38c21
--- /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 0000000..4adf265
--- /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 0000000..5bb727a3
--- /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/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java
new file mode 100644
index 0000000..1973ef1
--- /dev/null
+++ b/core/java/android/security/net/config/KeyStoreCertificateSource.java
@@ -0,0 +1,65 @@
+/*
+ * 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.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.Set;
+
+/**
+ * {@link CertificateSource} which provides certificates from trusted certificate entries of a
+ * {@link KeyStore}.
+ */
+class KeyStoreCertificateSource implements CertificateSource {
+ private final Object mLock = new Object();
+ private final KeyStore mKeyStore;
+ private Set<X509Certificate> mCertificates;
+
+ public KeyStoreCertificateSource(KeyStore ks) {
+ mKeyStore = ks;
+ }
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ synchronized (mLock) {
+ if (mCertificates != null) {
+ return mCertificates;
+ }
+ try {
+ Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
+ for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
+ String alias = en.nextElement();
+ if (!mKeyStore.isCertificateEntry(alias)) {
+ continue;
+ }
+ X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
+ if (cert != null) {
+ certificates.add(cert);
+ }
+ }
+ mCertificates = certificates;
+ return mCertificates;
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("Failed to load certificates from KeyStore", e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java
new file mode 100644
index 0000000..8d4f098
--- /dev/null
+++ b/core/java/android/security/net/config/KeyStoreConfigSource.java
@@ -0,0 +1,49 @@
+/*
+ * 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.security.KeyStore;
+import java.security.KeyStoreException;
+import java.util.Set;
+
+/**
+ * {@link ConfigSource} with a single default config based on a {@link KeyStore} and no per domain
+ * configs.
+ */
+class KeyStoreConfigSource implements ConfigSource {
+ private final NetworkSecurityConfig mConfig;
+
+ public KeyStoreConfigSource(KeyStore ks) {
+ mConfig = new NetworkSecurityConfig.Builder()
+ .addCertificatesEntryRef(
+ // Use the KeyStore and do not override pins (of which there are none).
+ new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false))
+ .build();
+ }
+
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return null;
+ }
+
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return mConfig;
+ }
+}
+
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 0000000..9eab80c
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -0,0 +1,261 @@
+/*
+ * 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.ArrayMap;
+import android.util.ArraySet;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+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 NetworkSecurityTrustManager 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;
+ }
+ // Merge trust anchors based on the X509Certificate.
+ // If we see the same certificate in two TrustAnchors, one with overridesPins and one
+ // without, the one with overridesPins wins.
+ Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
+ for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
+ Set<TrustAnchor> anchors = ref.getTrustAnchors();
+ for (TrustAnchor anchor : anchors) {
+ if (anchor.overridesPins) {
+ anchorMap.put(anchor.certificate, anchor);
+ } else if (!anchorMap.containsKey(anchor.certificate)) {
+ anchorMap.put(anchor.certificate, anchor);
+ }
+ }
+ }
+ ArraySet<TrustAnchor> anchors = new ArraySet<TrustAnchor>(anchorMap.size());
+ anchors.addAll(anchorMap.values());
+ mAnchors = anchors;
+ return mAnchors;
+ }
+ }
+
+ public boolean isCleartextTrafficPermitted() {
+ return mCleartextTrafficPermitted;
+ }
+
+ public boolean isHstsEnforced() {
+ return mHstsEnforced;
+ }
+
+ public PinSet getPins() {
+ return mPins;
+ }
+
+ public NetworkSecurityTrustManager 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 hasCertificatesEntryRefs() {
+ return mCertificatesEntryRefs != null;
+ }
+
+ List<CertificatesEntryRef> getCertificatesEntryRefs() {
+ return mCertificatesEntryRefs;
+ }
+
+ 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/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
new file mode 100644
index 0000000..ac762efe
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import java.security.Security;
+import java.security.Provider;
+
+/** @hide */
+public final class NetworkSecurityConfigProvider extends Provider {
+ private static final String LOG_TAG = "NetworkSecurityConfig";
+ private static final String PREFIX =
+ NetworkSecurityConfigProvider.class.getPackage().getName() + ".";
+ public static final String META_DATA_NETWORK_SECURITY_CONFIG =
+ "android.security.net.config";
+ private static final boolean DBG = true;
+
+ public NetworkSecurityConfigProvider() {
+ // TODO: More clever name than this
+ super("AndroidNSSP", 1.0, "Android Network Security Policy Provider");
+ put("TrustManagerFactory.PKIX", PREFIX + "RootTrustManagerFactorySpi");
+ put("Alg.Alias.TrustManagerFactory.X509", "PKIX");
+ }
+
+ public static void install(Context context) {
+ ApplicationInfo info = null;
+ // TODO: This lookup shouldn't be done in the app startup path, it should be done lazily.
+ try {
+ info = context.getPackageManager().getApplicationInfo(context.getPackageName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Failed to look up ApplicationInfo", e);
+ }
+ int configResourceId = 0;
+ if (info != null && info.metaData != null) {
+ configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
+ }
+
+ ApplicationConfig config;
+ if (configResourceId != 0) {
+ boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (DBG) {
+ Log.d(LOG_TAG, "Using Network Security Config from resource "
+ + context.getResources().getResourceEntryName(configResourceId)
+ + " debugBuild: " + debugBuild);
+ }
+ ConfigSource source = new XmlConfigSource(context, configResourceId, debugBuild);
+ config = new ApplicationConfig(source);
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
+ }
+ config = ApplicationConfig.getPlatformDefault();
+ }
+
+ ApplicationConfig.setDefaultInstance(config);
+ int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);
+ if (pos != 1) {
+ throw new RuntimeException("Failed to install provider as highest priority provider."
+ + " Provider was installed at position " + pos);
+ }
+ }
+}
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 0000000..7f5b3ca
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -0,0 +1,154 @@
+/*
+ * 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 {
+ checkServerTrusted(certs, authType, null);
+ }
+
+ /**
+ * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+ * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+ * modify without modifying those callers.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+ String host) throws CertificateException {
+ List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
+ checkPins(trustedChain);
+ return trustedChain;
+ }
+
+ /**
+ * Check if the provided certificate is a user added certificate authority.
+ * This is required by android.net.http.X509TrustManagerExtensions.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ // TODO: Figure out the right way to handle this, and if it is still even used.
+ return false;
+ }
+
+ 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 0000000..94520e2
--- /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 0000000..d3c975e
--- /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 0000000..06dd9d4
--- /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 0000000..b87bf1f
--- /dev/null
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -0,0 +1,89 @@
+/*
+ * 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 java.util.List;
+
+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);
+ }
+
+ /**
+ * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+ * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+ * modify without modifying those callers.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+ String hostname) throws CertificateException {
+ NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
+ return config.getTrustManager().checkServerTrusted(certs, authType, hostname);
+ }
+
+ /**
+ * Check if the provided certificate is a user added certificate authority.
+ * This is required by android.net.http.X509TrustManagerExtensions.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ // TODO: Figure out the right way to handle this, and if it is still even used.
+ return false;
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return EMPTY_ISSUERS;
+ }
+}
diff --git a/core/java/android/security/net/config/RootTrustManagerFactorySpi.java b/core/java/android/security/net/config/RootTrustManagerFactorySpi.java
new file mode 100644
index 0000000..0a1fe88
--- /dev/null
+++ b/core/java/android/security/net/config/RootTrustManagerFactorySpi.java
@@ -0,0 +1,75 @@
+/*
+ * 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.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Set;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.TrustManagerFactorySpi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** @hide */
+public class RootTrustManagerFactorySpi extends TrustManagerFactorySpi {
+ private ApplicationConfig mApplicationConfig;
+ private NetworkSecurityConfig mConfig;
+
+ @Override
+ public void engineInit(ManagerFactoryParameters spec)
+ throws InvalidAlgorithmParameterException {
+ if (!(spec instanceof ApplicationConfigParameters)) {
+ throw new InvalidAlgorithmParameterException("Unsupported spec: " + spec + ". Only "
+ + ApplicationConfigParameters.class.getName() + " supported");
+
+ }
+ mApplicationConfig = ((ApplicationConfigParameters) spec).config;
+ }
+
+ @Override
+ public void engineInit(KeyStore ks) throws KeyStoreException {
+ if (ks != null) {
+ mApplicationConfig = new ApplicationConfig(new KeyStoreConfigSource(ks));
+ } else {
+ mApplicationConfig = ApplicationConfig.getDefaultInstance();
+ }
+ }
+
+ @Override
+ public TrustManager[] engineGetTrustManagers() {
+ if (mApplicationConfig == null) {
+ throw new IllegalStateException("TrustManagerFactory not initialized");
+ }
+ return new TrustManager[] { mApplicationConfig.getTrustManager() };
+ }
+
+ @VisibleForTesting
+ public static final class ApplicationConfigParameters implements ManagerFactoryParameters {
+ public final ApplicationConfig config;
+ public ApplicationConfigParameters(ApplicationConfig config) {
+ this.config = config;
+ }
+ }
+}
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 0000000..7649a97
--- /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 0000000..b62d85f
--- /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 0000000..e9d5aa1
--- /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 0000000..1706e95
--- /dev/null
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -0,0 +1,387 @@
+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 static final int CONFIG_BASE = 0;
+ private static final int CONFIG_DOMAIN = 1;
+ private static final int CONFIG_DEBUG = 2;
+
+ private final Object mLock = new Object();
+ private final int mResourceId;
+ private final boolean mDebugBuild;
+
+ private boolean mInitialized;
+ private NetworkSecurityConfig mDefaultConfig;
+ private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
+ private Context mContext;
+
+ public XmlConfigSource(Context context, int resourceId) {
+ this(context, resourceId, false);
+ }
+
+ public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
+ mResourceId = resourceId;
+ mContext = context;
+ mDebugBuild = debugBuild;
+ }
+
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ ensureInitialized();
+ return mDomainMap;
+ }
+
+ public NetworkSecurityConfig getDefaultConfig() {
+ ensureInitialized();
+ return mDefaultConfig;
+ }
+
+ private static final String getConfigString(int configType) {
+ switch (configType) {
+ case CONFIG_BASE:
+ return "base-config";
+ case CONFIG_DOMAIN:
+ return "domain-config";
+ case CONFIG_DEBUG:
+ return "debug-overrides";
+ default:
+ throw new IllegalArgumentException("Unknown config type: " + configType);
+ }
+ }
+
+ private void ensureInitialized() {
+ synchronized (mLock) {
+ if (mInitialized) {
+ 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,
+ boolean defaultOverridePins)
+ throws IOException, XmlPullParserException, ParserException {
+ boolean overridePins =
+ parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
+ 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,
+ boolean defaultOverridePins)
+ throws IOException, XmlPullParserException, ParserException {
+ int outerDepth = parser.getDepth();
+ List<CertificatesEntryRef> anchors = new ArrayList<>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ if (tagName.equals("certificates")) {
+ anchors.add(parseCertificatesEntry(parser, defaultOverridePins));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return anchors;
+ }
+
+ private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
+ XmlResourceParser parser, Set<String> seenDomains,
+ NetworkSecurityConfig.Builder parentBuilder, int configType)
+ 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;
+ boolean defaultOverridePins = configType == CONFIG_DEBUG;
+ String configName = parser.getName();
+ int outerDepth = parser.getDepth();
+ // Add this builder now so that this builder occurs before any of its children. This
+ // 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 (configType != CONFIG_DOMAIN) {
+ throw new ParserException(parser,
+ "domain element not allowed in " + getConfigString(configType));
+ }
+ 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, defaultOverridePins));
+ seenTrustAnchors = true;
+ } else if ("pin-set".equals(tagName)) {
+ if (configType != CONFIG_DOMAIN) {
+ throw new ParserException(parser,
+ "pin-set element not allowed in " + getConfigString(configType));
+ }
+ 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 (configType != CONFIG_DOMAIN) {
+ throw new ParserException(parser,
+ "Nested domain-config not allowed in " + getConfigString(configType));
+ }
+ builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ if (configType == CONFIG_DOMAIN && domains.isEmpty()) {
+ throw new ParserException(parser, "No domain elements in domain-config");
+ }
+ return builders;
+ }
+
+ private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder,
+ NetworkSecurityConfig.Builder builder) {
+ if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) {
+ return;
+ }
+ // Don't add trust anchors if not already present, the builder will inherit the anchors
+ // from its parent, and that's where the trust anchors should be added.
+ if (!builder.hasCertificatesEntryRefs()) {
+ return;
+ }
+
+ builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs());
+ }
+
+ private void parseNetworkSecurityConfig(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ Set<String> seenDomains = new ArraySet<>();
+ List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
+ NetworkSecurityConfig.Builder baseConfigBuilder = null;
+ NetworkSecurityConfig.Builder debugConfigBuilder = null;
+ boolean seenDebugOverrides = false;
+ boolean seenBaseConfig = false;
+
+ XmlUtils.beginDocument(parser, "network-security-config");
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if ("base-config".equals(parser.getName())) {
+ if (seenBaseConfig) {
+ throw new ParserException(parser, "Only one base-config allowed");
+ }
+ seenBaseConfig = true;
+ baseConfigBuilder =
+ parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first;
+ } else if ("domain-config".equals(parser.getName())) {
+ builders.addAll(
+ parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN));
+ } else if ("debug-overrides".equals(parser.getName())) {
+ if (seenDebugOverrides) {
+ throw new ParserException(parser, "Only one debug-overrides allowed");
+ }
+ if (mDebugBuild) {
+ debugConfigBuilder =
+ parseConfigEntry(parser, seenDomains, null, CONFIG_DEBUG).get(0).first;
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ seenDebugOverrides = true;
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ // 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();
+ addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
+ if (baseConfigBuilder != null) {
+ baseConfigBuilder.setParent(platformDefaultBuilder);
+ addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder);
+ } 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);
+ }
+ addDebugAnchorsIfNeeded(debugConfigBuilder, builder);
+ 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/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42402eb..1d1edaa 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1591,7 +1591,7 @@
if (mPendingConfiguration.seq != 0) {
if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
+ mPendingConfiguration);
- updateConfiguration(mPendingConfiguration, !mFirst);
+ updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
mPendingConfiguration.seq = 0;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 77f1efa..4bcfa4c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -519,7 +519,7 @@
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 47b2f3b..4c68e01 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -273,7 +273,7 @@
# -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/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index cdce77c..734c50e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -608,15 +608,6 @@
kEMIntFast,
kEMJitCompiler,
} executionMode = kEMDefault;
- char profilePeriod[sizeof("-Xprofile-period:")-1 + PROPERTY_VALUE_MAX];
- char profileDuration[sizeof("-Xprofile-duration:")-1 + PROPERTY_VALUE_MAX];
- char profileInterval[sizeof("-Xprofile-interval:")-1 + PROPERTY_VALUE_MAX];
- char profileBackoff[sizeof("-Xprofile-backoff:")-1 + PROPERTY_VALUE_MAX];
- char profileTopKThreshold[sizeof("-Xprofile-top-k-threshold:")-1 + PROPERTY_VALUE_MAX];
- char profileTopKChangeThreshold[sizeof("-Xprofile-top-k-change-threshold:")-1 +
- PROPERTY_VALUE_MAX];
- char profileType[sizeof("-Xprofile-type:")-1 + PROPERTY_VALUE_MAX];
- char profileMaxStackDepth[sizeof("-Xprofile-max-stack-depth:")-1 + PROPERTY_VALUE_MAX];
char localeOption[sizeof("-Duser.locale=") + PROPERTY_VALUE_MAX];
char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:")-1 + PROPERTY_VALUE_MAX];
char nativeBridgeLibrary[sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX];
@@ -835,75 +826,22 @@
addOption(localeOption);
}
- /*
- * Set profiler options
- */
- // Whether or not the profiler should be enabled.
- property_get("dalvik.vm.profiler", propBuf, "0");
- if (propBuf[0] == '1') {
- addOption("-Xenable-profiler");
- }
-
- // Whether the profile should start upon app startup or be delayed by some random offset
- // (in seconds) that is bound between 0 and a fixed value.
- property_get("dalvik.vm.profile.start-immed", propBuf, "0");
- if (propBuf[0] == '1') {
- addOption("-Xprofile-start-immediately");
- }
-
- // Number of seconds during profile runs.
- parseRuntimeOption("dalvik.vm.profile.period-secs", profilePeriod, "-Xprofile-period:");
-
- // Length of each profile run (seconds).
- parseRuntimeOption("dalvik.vm.profile.duration-secs",
- profileDuration,
- "-Xprofile-duration:");
-
- // Polling interval during profile run (microseconds).
- parseRuntimeOption("dalvik.vm.profile.interval-us", profileInterval, "-Xprofile-interval:");
-
- // Coefficient for period backoff. The the period is multiplied
- // by this value after each profile run.
- parseRuntimeOption("dalvik.vm.profile.backoff-coeff", profileBackoff, "-Xprofile-backoff:");
-
- // Top K% of samples that are considered relevant when
- // deciding if the app should be recompiled.
- parseRuntimeOption("dalvik.vm.profile.top-k-thr",
- profileTopKThreshold,
- "-Xprofile-top-k-threshold:");
-
- // The threshold after which a change in the structure of the
- // top K% profiled samples becomes significant and triggers
- // recompilation. A change in profile is considered
- // significant if X% (top-k-change-threshold) of the top K%
- // (top-k-threshold property) samples has changed.
- parseRuntimeOption("dalvik.vm.profile.top-k-ch-thr",
- profileTopKChangeThreshold,
- "-Xprofile-top-k-change-threshold:");
-
- // Type of profile data.
- parseRuntimeOption("dalvik.vm.profiler.type", profileType, "-Xprofile-type:");
-
- // Depth of bounded stack data
- parseRuntimeOption("dalvik.vm.profile.stack-depth",
- profileMaxStackDepth,
- "-Xprofile-max-stack-depth:");
-
- /*
- * Tracing options.
- */
- property_get("dalvik.vm.method-trace", propBuf, "false");
- if (strcmp(propBuf, "true") == 0) {
- addOption("-Xmethod-trace");
- parseRuntimeOption("dalvik.vm.method-trace-file",
- methodTraceFileBuf,
- "-Xmethod-trace-file:");
- parseRuntimeOption("dalvik.vm.method-trace-file-siz",
- methodTraceFileSizeBuf,
- "-Xmethod-trace-file-size:");
- property_get("dalvik.vm.method-trace-stream", propBuf, "false");
+ // Trace files are stored in /data/misc/trace which is writable only in debug mode.
+ property_get("ro.debuggable", propBuf, "0");
+ if (strcmp(propBuf, "1") == 0) {
+ property_get("dalvik.vm.method-trace", propBuf, "false");
if (strcmp(propBuf, "true") == 0) {
- addOption("-Xmethod-trace-stream");
+ addOption("-Xmethod-trace");
+ parseRuntimeOption("dalvik.vm.method-trace-file",
+ methodTraceFileBuf,
+ "-Xmethod-trace-file:");
+ parseRuntimeOption("dalvik.vm.method-trace-file-siz",
+ methodTraceFileSizeBuf,
+ "-Xmethod-trace-file-size:");
+ property_get("dalvik.vm.method-trace-stream", propBuf, "false");
+ if (strcmp(propBuf, "true") == 0) {
+ addOption("-Xmethod-trace-stream");
+ }
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index ba39ba7..85cb4df 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -97,20 +97,21 @@
*/
public static void install() {
Provider[] providers = Security.getProviders();
- int bcProviderPosition = -1;
- for (int position = 0; position < providers.length; position++) {
- Provider provider = providers[position];
+ int bcProviderIndex = -1;
+ for (int i = 0; i < providers.length; i++) {
+ Provider provider = providers[i];
if ("BC".equals(provider.getName())) {
- bcProviderPosition = position;
+ bcProviderIndex = i;
break;
}
}
Security.addProvider(new AndroidKeyStoreProvider());
Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
- if (bcProviderPosition != -1) {
+ if (bcProviderIndex != -1) {
// Bouncy Castle provider found -- install the workaround provider above it.
- Security.insertProviderAt(workaroundProvider, bcProviderPosition);
+ // insertProviderAt uses 1-based positions.
+ Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
} else {
// Bouncy Castle provider not found -- install the workaround provider at lowest
// priority.
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 2292ef4..9621b54 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2325,12 +2325,15 @@
PathTexture* texture = mCaches.pathCache.get(path, paint);
if (!texture) return;
- const AutoTexture autoCleanup(texture);
const float x = texture->left - texture->offset;
const float y = texture->top - texture->offset;
drawPathTexture(texture, x, y, paint);
+
+ if (texture->cleanup) {
+ mCaches.pathCache.remove(path, paint);
+ }
mDirty = true;
}
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 3af640f..3236f6f 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -400,6 +400,13 @@
return texture;
}
+void PathCache::remove(const SkPath* path, const SkPaint* paint)
+{
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mGenerationID = path->getGenerationID();
+ mCache.remove(entry);
+}
+
void PathCache::precache(const SkPath* path, const SkPaint* paint) {
if (!Caches::getInstance().tasks.canRunTasks()) {
return;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 7014863..c529915 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -201,6 +201,7 @@
PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
bool useCenter, const SkPaint* paint);
PathTexture* get(const SkPath* path, const SkPaint* paint);
+ void remove(const SkPath* path, const SkPaint* paint);
/**
* Removes the specified path. This is meant to be called from threads
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index 09d1258..595c10c 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -80,6 +80,11 @@
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/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index b8c9804..3186a8e 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -742,7 +742,7 @@
// vertex's location.
int newPenumbraNumber = indexDelta - 1;
- float accumulatedDeltaLength[newPenumbraNumber];
+ float accumulatedDeltaLength[indexDelta];
float totalDeltaLength = 0;
// To save time, cache the previous umbra vertex info outside the loop
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 6bf5721..445ee6f 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -17,6 +17,8 @@
package android.media;
import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -128,6 +130,9 @@
// use sLock to serialize the accesses.
private static final Object sLock = new Object();
+ // Pattern to check non zero timestamp
+ private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
+
/**
* Reads Exif tags from the specified JPEG file.
*/
@@ -367,7 +372,8 @@
*/
public long getDateTime() {
String dateTimeString = mAttributes.get(TAG_DATETIME);
- if (dateTimeString == null) return -1;
+ if (dateTimeString == null
+ || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
ParsePosition pos = new ParsePosition(0);
try {
@@ -402,7 +408,9 @@
public long getGpsDateTime() {
String date = mAttributes.get(TAG_GPS_DATESTAMP);
String time = mAttributes.get(TAG_GPS_TIMESTAMP);
- if (date == null || time == null) return -1;
+ if (date == null || time == null
+ || (!sNonZeroTimePattern.matcher(date).matches()
+ && !sNonZeroTimePattern.matcher(time).matches())) return -1;
String dateTimeString = date + ' ' + time;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index e8a5e43..14bac4f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -655,9 +655,11 @@
}
}
- PrinterId printerId = mCurrentPrinter.getId();
- final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
- mDestinationSpinner.setSelection(index);
+ if (mCurrentPrinter != null) {
+ PrinterId printerId = mCurrentPrinter.getId();
+ final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+ mDestinationSpinner.setSelection(index);
+ }
}
private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java
index 54180f4..9bbacbc 100644
--- a/rs/java/android/renderscript/ScriptGroup.java
+++ b/rs/java/android/renderscript/ScriptGroup.java
@@ -278,6 +278,8 @@
public ValueAndSize(RenderScript rs, Object obj) {
if (obj instanceof Allocation) {
value = ((Allocation)obj).getID(rs);
+ // Special value for size to tell the runtime and driver that
+ // the value is an Allocation
size = -1;
} else if (obj instanceof Boolean) {
value = ((Boolean)obj).booleanValue() ? 1 : 0;
@@ -289,10 +291,10 @@
value = ((Long)obj).longValue();
size = 8;
} else if (obj instanceof Float) {
- value = ((Float)obj).longValue();
+ value = Float.floatToRawIntBits(((Float)obj).floatValue());
size = 4;
} else if (obj instanceof Double) {
- value = ((Double)obj).longValue();
+ value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
size = 8;
}
}
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index be7071e..113241d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -393,7 +393,6 @@
size_t numValues, numDependencies;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
RsClosure* depClosures;
RsScriptFieldID* depFieldIDs;
@@ -430,15 +429,6 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
if (depClosures == nullptr) {
goto exit;
@@ -459,7 +449,7 @@
ret = (jlong)(uintptr_t)rsClosureCreate(
(RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues,
depClosures, numDependencies,
depFieldIDs, numDependencies);
@@ -511,7 +501,6 @@
size_t numValues;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
if (fieldIDs_length != values_length || values_length != sizes_length) {
ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
@@ -534,18 +523,9 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
ret = (jlong)(uintptr_t)rsInvokeClosureCreate(
(RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues);
exit:
@@ -561,15 +541,17 @@
static void
nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jint index, jlong value, jint size) {
+ // Size is signed with -1 indicating the value is an Allocation
rsClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
- (uintptr_t)value, (size_t)size);
+ (uintptr_t)value, size);
}
static void
nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jlong fieldID, jlong value, jint size) {
+ // Size is signed with -1 indicating the value is an Allocation
rsClosureSetGlobal((RsContext)con, (RsClosure)closureID,
- (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+ (RsScriptFieldID)fieldID, (int64_t)value, size);
}
static long
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
index b4613d6..a8e27a8 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -372,6 +372,9 @@
@Override
public void onDestroy() {
+ if (mMagnificationController != null) {
+ mMagnificationController.cancelAnimation();
+ }
mScreenStateObserver.destroy();
mWindowManager.setMagnificationCallbacks(null);
}
@@ -988,10 +991,14 @@
return mCurrentMagnificationSpec.scale > 1.0f;
}
- public void reset(boolean animate) {
+ public void cancelAnimation() {
if (mTransformationAnimator.isRunning()) {
mTransformationAnimator.cancel();
}
+ }
+
+ public void reset(boolean animate) {
+ cancelAnimation();
mCurrentMagnificationSpec.clear();
if (animate) {
animateMangificationSpec(mSentMagnificationSpec,
@@ -1056,9 +1063,7 @@
centerY) == 0) {
return;
}
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
- }
+ cancelAnimation();
if (DEBUG_MAGNIFICATION_CONTROLLER) {
Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX
+ " offsetY: " + centerY);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index c228422..9eb66dd 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -88,6 +88,7 @@
private SettingsObserver mSettingObserver;
native static boolean vibratorExists();
+ native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
@@ -195,6 +196,7 @@
}
VibratorService(Context context) {
+ vibratorInit();
// Reset the hardware to a default state, in case this is a runtime
// restart instead of a fresh boot.
vibratorOff();
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 6fbe8e4..a5c6180 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.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 @@
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);
@@ -842,7 +848,7 @@
return false;
}
PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
- if (sysPkg != null) {
+ if (sysPkg != null && sysPkg.pkg != null) {
if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
return false;
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 64278ed..03fbd19 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -22,32 +22,69 @@
#include <utils/misc.h>
#include <utils/Log.h>
-#include <hardware_legacy/vibrator.h>
+#include <hardware/vibrator.h>
#include <stdio.h>
namespace android
{
+static hw_module_t *gVibraModule = NULL;
+static vibrator_device_t *gVibraDevice = NULL;
+
+static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
+{
+ if (gVibraModule != NULL) {
+ return;
+ }
+
+ int err = hw_get_module(VIBRATOR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVibraModule);
+
+ if (err) {
+ ALOGE("Couldn't load %s module (%s)", VIBRATOR_HARDWARE_MODULE_ID, strerror(-err));
+ } else {
+ if (gVibraModule) {
+ vibrator_open(gVibraModule, &gVibraDevice);
+ }
+ }
+}
+
static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
{
- return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE;
+ if (gVibraModule && gVibraDevice) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
}
static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
- // ALOGI("vibratorOn\n");
- vibrator_on(timeout_ms);
+ if (gVibraDevice) {
+ int err = gVibraDevice->vibrator_on(gVibraDevice, timeout_ms);
+ if (err != 0) {
+ ALOGE("The hw module failed in vibrator_on: %s", strerror(-err));
+ }
+ } else {
+ ALOGW("Tried to vibrate but there is no vibrator device.");
+ }
}
static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
- // ALOGI("vibratorOff\n");
- vibrator_off();
+ if (gVibraDevice) {
+ int err = gVibraDevice->vibrator_off(gVibraDevice);
+ if (err != 0) {
+ ALOGE("The hw module failed in vibrator_off(): %s", strerror(-err));
+ }
+ } else {
+ ALOGW("Tried to stop vibrating but there is no vibrator device.");
+ }
}
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
+ { "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
{ "vibratorOff", "()V", (void*)vibratorOff }
};
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4146c1c0..c518f77a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -218,7 +218,7 @@
synchronized (this) {
mScreenOnTime = readScreenOnTimeLocked();
}
- mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
synchronized (this) {
updateDisplayLocked();
}
diff --git a/tests/NetworkSecurityConfigTest/Android.mk b/tests/NetworkSecurityConfigTest/Android.mk
new file mode 100644
index 0000000..a63162d
--- /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 0000000..4c1fbd3
--- /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 0000000..235bd47
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
Binary files differ
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
new file mode 100644
index 0000000..413e3c0
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
new file mode 100644
index 0000000..81648d9
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIJAP/YiWztz/J7MA0GCSqGSIb3DQEBCwUAMCcxFjAUBgNV
+BAMMDVRlc3QgZGVidWcgQ0ExDTALBgNVBAoMBEFPU1AwHhcNMTUxMTA5MjEyNjQ2
+WhcNMTgwODI5MjEyNjQ2WjAnMRYwFAYDVQQDDA1UZXN0IGRlYnVnIENBMQ0wCwYD
+VQQKDARBT1NQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPFmkOJj
+ehjfvdDr2qTcBWNqNATrW1SuM88Vj00ubUFQ4tZElozj8YnQOw1FeC79c1k88b8R
+6jcqYYp/mw2JYoD6yWcFPHo5BplIpk0EhIUARH/aeoclHvsUN2GGDyTO0vf0CfJn
+9Wp6lSLjyq7V/6tYdk+0cL632t56MHp8TCO+AaveYP1T8JZqx0/50xNcsK7lIqNa
+ctWyRGFxR4ifdVsgkw9WhAB/Ow2uOwN9uLGqzsCd+yXW2weX52EIivoTGZfJo+U8
+Fi0ygnCHBv2jsJA7yWLhHmZ4ijsVtfutIKmN0w+DHkl6S25girXhy0zJp/1QvHGm
+jaF60V1gw471jQIDAQABo1AwTjAdBgNVHQ4EFgQUoq66jncy83L5eeyW1g78s/uq
+iyQwHwYDVR0jBBgwFoAUoq66jncy83L5eeyW1g78s/uqiyQwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAohytuH4CdX0gO8EGVDRVurRH7LO69lwd/6Iw
+hJ1lIK/mzj5RM2itVGTkintyHCLu5giVkHn4FHg4X9qzZaTPOcXv9ntQNS2nacZe
+bY8nfhsAhstJT4nIOWHE3FrZkMDOK6nZHIzfscX3V/VVq5MeA+WzXwmKp6MBNr+E
+oUegXCGjd26Bl6SFz3rD7Qh+dzSTtyf/ECzXaMjpZu3k6fb4EgRz6vdBCHKKtpv6
+Mxcr0nLwdI6LnAGXvJLV4sj+l6Ngg00EeyorG8ATgtmsUrXXOR1e+yDCQv6fjQfs
+CWYztECAUE9hfCXJwb0TBrq9YeJAvcO7iE6S0Pq+X3xNtetE1A==
+-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/xml/attributes.xml b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
new file mode 100644
index 0000000..eff13c8
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config cleartextTrafficPermitted="false" hstsEnforced="true">
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
new file mode 100644
index 0000000..6af855d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Bad pin digest -->
+ <pin digest="I am probably not an algorithm">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
new file mode 100644
index 0000000..d683b74a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Unknown pin digest -->
+ <pin>1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
new file mode 100644
index 0000000..6f3f8b4
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- empty digest -->
+ <pin digest="SHA-256"></pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
new file mode 100644
index 0000000..fb2126c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <!-- Same domain name used in two configs -->
+ <domain>android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
new file mode 100644
index 0000000..95972ce
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- domains are not allowed in base-config -->
+ <domain>android.com</domain>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
new file mode 100644
index 0000000..8b6b721
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- pins are not allowed in base-config -->
+ <pin-set>
+ </pin-set>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
new file mode 100644
index 0000000..62a7b88
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
new file mode 100644
index 0000000..8da9317
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <debug-overrides>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
new file mode 100644
index 0000000..24eed7a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+ <debug-overrides>
+ <trust-anchors>
+ <certificates src="@raw/test_debug_ca" />
+ </trust-anchors>
+ </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml
new file mode 100644
index 0000000..ce0cbc8
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <debug-overrides>
+ <trust-anchors>
+ <certificates src="@raw/test_debug_ca" />
+ </trust-anchors>
+ </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain1.xml b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
new file mode 100644
index 0000000..6d8565c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
new file mode 100644
index 0000000..1bd94b6
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
new file mode 100644
index 0000000..8093b9d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
new file mode 100644
index 0000000..f9f8465
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <!-- Invalid pin that has expired -->
+ <pin-set expiration="2015-01-01">
+ <pin digest="SHA-256">aaaaaaaaaaa2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
new file mode 100644
index 0000000..df08467
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
new file mode 100644
index 0000000..9743c5f
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
new file mode 100644
index 0000000..d45fd77
--- /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 0000000..84e06e3
--- /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 0000000..785714a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ <trust-anchors>
+ <certificates src="system" overridePins="true" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
new file mode 100644
index 0000000..1773d280
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
new file mode 100644
index 0000000..dfd6fd9
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_der" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
new file mode 100644
index 0000000..894f29b
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
new file mode 100644
index 0000000..482b26c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain includeSubdomains="true">android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
new file mode 100644
index 0000000..9f48d56
--- /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 0000000..92eadc0
--- /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 0000000..609f481
--- /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 0000000..f7590fd
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -0,0 +1,80 @@
+/*
+ * 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 javax.net.ssl.TrustManagerFactory;
+
+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);
+ TrustManagerFactory tmf =
+ TrustManagerFactory.getInstance("PKIX", new NetworkSecurityConfigProvider());
+ tmf.init(new RootTrustManagerFactorySpi.ApplicationConfigParameters(config));
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, tmf.getTrustManagers(), null);
+ return context;
+ }
+}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
new file mode 100644
index 0000000..c6f3680
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -0,0 +1,405 @@
+/*
+ * 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.security.KeyStore;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+public class XmlConfigTests extends AndroidTestCase {
+
+ private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
+
+ public void testEmptyConfigFile() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ 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());
+ }
+
+ public void testDebugOverridesDisabled() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ Set<TrustAnchor> anchors = config.getTrustAnchors();
+ MoreAsserts.assertEmpty(anchors);
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ }
+
+ public void testBasicDebugOverrides() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ Set<TrustAnchor> anchors = config.getTrustAnchors();
+ MoreAsserts.assertNotEmpty(anchors);
+ for (TrustAnchor anchor : anchors) {
+ assertTrue(anchor.overridesPins);
+ }
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ }
+
+ public void testDebugOverridesWithDomain() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ Set<TrustAnchor> anchors = config.getTrustAnchors();
+ boolean foundDebugCA = false;
+ for (TrustAnchor anchor : anchors) {
+ if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
+ foundDebugCA = true;
+ assertTrue(anchor.overridesPins);
+ }
+ }
+ assertTrue(foundDebugCA);
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ }
+
+ public void testDebugInherit() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ Set<TrustAnchor> anchors = config.getTrustAnchors();
+ boolean foundDebugCA = false;
+ for (TrustAnchor anchor : anchors) {
+ if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
+ foundDebugCA = true;
+ assertTrue(anchor.overridesPins);
+ }
+ }
+ assertTrue(foundDebugCA);
+ assertTrue(anchors.size() > 1);
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ }
+
+ private void testBadConfig(int configId) throws Exception {
+ try {
+ XmlConfigSource source = new XmlConfigSource(getContext(), configId);
+ 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);
+ }
+
+ public void testTrustManagerKeystore() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ Provider provider = new NetworkSecurityConfigProvider();
+ TrustManagerFactory tmf =
+ TrustManagerFactory.getInstance("PKIX", provider);
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keystore.load(null);
+ int i = 0;
+ for (X509Certificate cert : SystemCertificateSource.getInstance().getCertificates()) {
+ keystore.setEntry(String.valueOf(i),
+ new KeyStore.TrustedCertificateEntry(cert),
+ null);
+ i++;
+ }
+ tmf.init(keystore);
+ TrustManager[] tms = tmf.getTrustManagers();
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, tms, null);
+ TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
+ }
+}