diff options
78 files changed, 3311 insertions, 195 deletions
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp index 6b2f46099b80..c13d318f7449 100644 --- a/cmds/idmap/create.cpp +++ b/cmds/idmap/create.cpp @@ -1,6 +1,6 @@ #include "idmap.h" -#include <UniquePtr.h> +#include <memory> #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> #include <androidfw/ZipFileRO.h> @@ -15,7 +15,7 @@ using namespace android; namespace { int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc) { - UniquePtr<ZipFileRO> zip(ZipFileRO::open(zip_path)); + std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(zip_path)); if (zip.get() == NULL) { return -1; } diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index f1b2f9e72ca0..6d30f0d0ce34 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -4,7 +4,7 @@ #include "idmap.h" -#include <UniquePtr.h> +#include <memory> #include <androidfw/ResourceTypes.h> #include <androidfw/StreamingZipInflater.h> #include <androidfw/ZipFileRO.h> @@ -120,7 +120,7 @@ namespace { int parse_apk(const char *path, const char *target_package_name) { - UniquePtr<ZipFileRO> zip(ZipFileRO::open(path)); + std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(path)); if (zip.get() == NULL) { ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path); return -1; diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 4a0a49b4e777..005153178298 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -764,8 +764,7 @@ public final class Pm { String optionData = nextOptionData(); if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); - showUsage(); - return 1; + return showUsage(); } else { userId = Integer.parseInt(optionData); } @@ -868,8 +867,7 @@ public final class Pm { } } else { System.err.println("Error: unknown option: " + opt); - showUsage(); - return 1; + return showUsage(); } } @@ -877,16 +875,14 @@ public final class Pm { final String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified."); - showUsage(); - return 1; + return showUsage(); } // State to apply; {always|ask|never|undefined}, required final String modeString = nextArg(); if (modeString == null) { System.err.println("Error: no app link state specified."); - showUsage(); - return 1; + return showUsage(); } final int newMode; @@ -955,8 +951,7 @@ public final class Pm { } } else { System.err.println("Error: unknown option: " + opt); - showUsage(); - return 1; + return showUsage(); } } @@ -964,8 +959,7 @@ public final class Pm { final String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified."); - showUsage(); - return 1; + return showUsage(); } try { @@ -1370,8 +1364,7 @@ public final class Pm { String optionData = nextOptionData(); if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); - showUsage(); - return 1; + return showUsage(); } else { userId = Integer.parseInt(optionData); } @@ -1379,8 +1372,7 @@ public final class Pm { flags |= UserInfo.FLAG_MANAGED_PROFILE; } else { System.err.println("Error: unknown option " + opt); - showUsage(); - return 1; + return showUsage(); } } String arg = nextArg(); @@ -1557,8 +1549,7 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - showUsage(); - return 1; + return showUsage(); } if (userId == UserHandle.USER_ALL) { @@ -1626,8 +1617,7 @@ public final class Pm { String optionData = nextOptionData(); if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); - showUsage(); - return 1; + return showUsage(); } else { userId = Integer.parseInt(optionData); } @@ -1636,8 +1626,7 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - showUsage(); - return 1; + return showUsage(); } ClearDataObserver obs = new ClearDataObserver(); @@ -1698,8 +1687,7 @@ public final class Pm { String optionData = nextOptionData(); if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); - showUsage(); - return 1; + return showUsage(); } else { userId = Integer.parseInt(optionData); } @@ -1708,8 +1696,7 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package or component specified"); - showUsage(); - return 1; + return showUsage(); } ComponentName cn = ComponentName.unflattenFromString(pkg); if (cn == null) { @@ -1747,8 +1734,7 @@ public final class Pm { String optionData = nextOptionData(); if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); - showUsage(); - return 1; + return showUsage(); } else { userId = Integer.parseInt(optionData); } @@ -1757,8 +1743,7 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package or component specified"); - showUsage(); - return 1; + return showUsage(); } try { mPm.setApplicationHiddenSettingAsUser(pkg, state, userId); @@ -1785,14 +1770,12 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - showUsage(); - return 1; + return showUsage(); } String perm = nextArg(); if (perm == null) { System.err.println("Error: no permission specified"); - showUsage(); - return 1; + return showUsage(); } try { @@ -1808,8 +1791,7 @@ public final class Pm { return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); - showUsage(); - return 1; + return showUsage(); } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); return 1; @@ -1826,8 +1808,7 @@ public final class Pm { return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); - showUsage(); - return 1; + return showUsage(); } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); return 1; @@ -1838,14 +1819,12 @@ public final class Pm { final String permission = nextArg(); if (permission == null) { System.err.println("Error: no permission specified"); - showUsage(); - return 1; + return showUsage(); } final String enforcedRaw = nextArg(); if (enforcedRaw == null) { System.err.println("Error: no enforcement specified"); - showUsage(); - return 1; + return showUsage(); } final boolean enforced = Boolean.parseBoolean(enforcedRaw); try { @@ -1857,8 +1836,7 @@ public final class Pm { return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); - showUsage(); - return 1; + return showUsage(); } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); return 1; @@ -1884,8 +1862,7 @@ public final class Pm { String size = nextArg(); if (size == null) { System.err.println("Error: no size specified"); - showUsage(); - return 1; + return showUsage(); } int len = size.length(); long multiplier = 1; @@ -1899,8 +1876,7 @@ public final class Pm { multiplier = 1024L*1024L*1024L; } else { System.err.println("Invalid suffix: " + c); - showUsage(); - return 1; + return showUsage(); } size = size.substring(0, len-1); } @@ -1909,8 +1885,7 @@ public final class Pm { sizeVal = Long.parseLong(size) * multiplier; } catch (NumberFormatException e) { System.err.println("Error: expected number at: " + size); - showUsage(); - return 1; + return showUsage(); } String volumeUuid = nextArg(); if ("internal".equals(volumeUuid)) { @@ -1934,8 +1909,7 @@ public final class Pm { return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); - showUsage(); - return 1; + return showUsage(); } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); return 1; diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 0a1ba4d19c5c..1ee60b068694 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -42,6 +42,7 @@ public final class Sm { } catch (Exception e) { if (e instanceof IllegalArgumentException) { showUsage(); + System.exit(1); } Log.e(TAG, "Error", e); System.err.println("Error: " + e); diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index eb4cedab83b7..7c23c2319f4d 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -18,6 +18,9 @@ package android.net.http; 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 @@ import javax.net.ssl.X509TrustManager; */ 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 class X509TrustManagerExtensions { 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 class X509TrustManagerExtensions { */ 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 @@ public class X509TrustManagerExtensions { * 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 1273772bc4ec..1e879f2edca1 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -603,6 +603,32 @@ public final class Parcel { } /** + * {@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 @@ public final class Parcel { 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 000000000000..48359d47f091 --- /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 000000000000..386354dc4d57 --- /dev/null +++ b/core/java/android/security/net/config/CertificateSource.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import java.util.Set; +import java.security.cert.X509Certificate; + +/** @hide */ +public interface CertificateSource { + Set<X509Certificate> getCertificates(); +} diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java new file mode 100644 index 000000000000..2ba38c21c330 --- /dev/null +++ b/core/java/android/security/net/config/CertificatesEntryRef.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.util.ArraySet; +import java.util.Set; +import java.security.cert.X509Certificate; + +/** @hide */ +public final class CertificatesEntryRef { + private final CertificateSource mSource; + private final boolean mOverridesPins; + + public CertificatesEntryRef(CertificateSource source, boolean overridesPins) { + mSource = source; + mOverridesPins = overridesPins; + } + + public Set<TrustAnchor> getTrustAnchors() { + // TODO: cache this [but handle mutable sources] + Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>(); + for (X509Certificate cert : mSource.getCertificates()) { + anchors.add(new TrustAnchor(cert, mOverridesPins)); + } + return anchors; + } +} diff --git a/core/java/android/security/net/config/ConfigSource.java b/core/java/android/security/net/config/ConfigSource.java new file mode 100644 index 000000000000..4adf265c678c --- /dev/null +++ b/core/java/android/security/net/config/ConfigSource.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.util.Pair; +import java.util.Set; + +/** @hide */ +public interface ConfigSource { + Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs(); + NetworkSecurityConfig getDefaultConfig(); +} diff --git a/core/java/android/security/net/config/Domain.java b/core/java/android/security/net/config/Domain.java new file mode 100644 index 000000000000..5bb727a38033 --- /dev/null +++ b/core/java/android/security/net/config/Domain.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import java.util.Locale; +/** @hide */ +public final class Domain { + /** + * Lower case hostname for this domain rule. + */ + public final String hostname; + + /** + * Whether this domain includes subdomains. + */ + public final boolean subdomainsIncluded; + + public Domain(String hostname, boolean subdomainsIncluded) { + if (hostname == null) { + throw new NullPointerException("Hostname must not be null"); + } + this.hostname = hostname.toLowerCase(Locale.US); + this.subdomainsIncluded = subdomainsIncluded; + } + + @Override + public int hashCode() { + return hostname.hashCode() ^ (subdomainsIncluded ? 1231 : 1237); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Domain)) { + return false; + } + Domain otherDomain = (Domain) other; + return otherDomain.subdomainsIncluded == this.subdomainsIncluded && + otherDomain.hostname.equals(this.hostname); + } +} diff --git a/core/java/android/security/net/config/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java new file mode 100644 index 000000000000..1973ef1825e9 --- /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 000000000000..8d4f098bcb37 --- /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 000000000000..9eab80ca0771 --- /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 000000000000..ac762efe85d2 --- /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 000000000000..7f5b3ca27bf4 --- /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 000000000000..94520e22bd02 --- /dev/null +++ b/core/java/android/security/net/config/Pin.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import java.util.Arrays; + +/** @hide */ +public final class Pin { + public final String digestAlgorithm; + public final byte[] digest; + + private final int mHashCode; + + public Pin(String digestAlgorithm, byte[] digest) { + this.digestAlgorithm = digestAlgorithm; + this.digest = digest; + mHashCode = Arrays.hashCode(digest) ^ digestAlgorithm.hashCode(); + } + + /** + * @hide + */ + public static boolean isSupportedDigestAlgorithm(String algorithm) { + // Currently only SHA-256 is supported. SHA-512 if/once Chromium networking stack + // supports it. + return "SHA-256".equalsIgnoreCase(algorithm); + } + + /** + * @hide + */ + public static int getDigestLength(String algorithm) { + if ("SHA-256".equalsIgnoreCase(algorithm)) { + return 32; + } + throw new IllegalArgumentException("Unsupported digest algorithm: " + algorithm); + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Pin)) { + return false; + } + Pin other = (Pin) obj; + if (other.hashCode() != mHashCode) { + return false; + } + if (!Arrays.equals(digest, other.digest)) { + return false; + } + if (!digestAlgorithm.equals(other.digestAlgorithm)) { + return false; + } + return true; + } +} diff --git a/core/java/android/security/net/config/PinSet.java b/core/java/android/security/net/config/PinSet.java new file mode 100644 index 000000000000..d3c975eb3101 --- /dev/null +++ b/core/java/android/security/net/config/PinSet.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.util.ArraySet; +import java.util.Collections; +import java.util.Set; + +/** @hide */ +public final class PinSet { + public static final PinSet EMPTY_PINSET = + new PinSet(Collections.<Pin>emptySet(), Long.MAX_VALUE); + public final long expirationTime; + public final Set<Pin> pins; + + public PinSet(Set<Pin> pins, long expirationTime) { + if (pins == null) { + throw new NullPointerException("pins must not be null"); + } + this.pins = pins; + this.expirationTime = expirationTime; + } + + Set<String> getPinAlgorithms() { + // TODO: Cache this. + Set<String> algorithms = new ArraySet<String>(); + for (Pin pin : pins) { + algorithms.add(pin.digestAlgorithm); + } + return algorithms; + } +} diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java new file mode 100644 index 000000000000..06dd9d42e364 --- /dev/null +++ b/core/java/android/security/net/config/ResourceCertificateSource.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.content.Context; +import android.util.ArraySet; +import libcore.io.IoUtils; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Set; + +/** + * {@link CertificateSource} based on certificates contained in an application resource file. + * @hide + */ +public class ResourceCertificateSource implements CertificateSource { + private Set<X509Certificate> mCertificates; + private final int mResourceId; + private Context mContext; + private final Object mLock = new Object(); + + public ResourceCertificateSource(int resourceId, Context context) { + mResourceId = resourceId; + mContext = context.getApplicationContext(); + } + + @Override + public Set<X509Certificate> getCertificates() { + synchronized (mLock) { + if (mCertificates != null) { + return mCertificates; + } + Set<X509Certificate> certificates = new ArraySet<X509Certificate>(); + Collection<? extends Certificate> certs; + InputStream in = null; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + in = mContext.getResources().openRawResource(mResourceId); + certs = factory.generateCertificates(in); + } catch (CertificateException e) { + throw new RuntimeException("Failed to load trust anchors from id " + mResourceId, + e); + } finally { + IoUtils.closeQuietly(in); + } + for (Certificate cert : certs) { + certificates.add((X509Certificate) cert); + } + mCertificates = certificates; + mContext = null; + return mCertificates; + } + } +} diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java new file mode 100644 index 000000000000..b87bf1fe0695 --- /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 000000000000..0a1fe881cb17 --- /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 000000000000..7649a977ff5c --- /dev/null +++ b/core/java/android/security/net/config/SystemCertificateSource.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.os.Environment; +import android.os.UserHandle; +import android.util.ArraySet; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Set; +import libcore.io.IoUtils; + +/** + * {@link CertificateSource} based on the system trusted CA store. + * @hide + */ +public class SystemCertificateSource implements CertificateSource { + private static final SystemCertificateSource INSTANCE = new SystemCertificateSource(); + private Set<X509Certificate> mSystemCerts = null; + private final Object mLock = new Object(); + + private SystemCertificateSource() { + } + + public static SystemCertificateSource getInstance() { + return INSTANCE; + } + + @Override + public Set<X509Certificate> getCertificates() { + // TODO: loading all of these is wasteful, we should instead use a keystore style API. + synchronized (mLock) { + if (mSystemCerts != null) { + return mSystemCerts; + } + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + + final String ANDROID_ROOT = System.getenv("ANDROID_ROOT"); + final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts"); + final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); + final File userRemovedCaDir = new File(configDir, "cacerts-removed"); + // Sanity check + if (!systemCaDir.isDirectory()) { + throw new AssertionError(systemCaDir + " is not a directory"); + } + + Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>(); + for (String caFile : systemCaDir.list()) { + // Skip any CAs in the user's deleted directory. + if (new File(userRemovedCaDir, caFile).exists()) { + continue; + } + InputStream is = null; + try { + is = new BufferedInputStream( + new FileInputStream(new File(systemCaDir, caFile))); + systemCerts.add((X509Certificate) certFactory.generateCertificate(is)); + } catch (CertificateException | IOException e) { + // Don't rethrow to be consistent with conscrypt's cert loading code. + continue; + } finally { + IoUtils.closeQuietly(is); + } + } + mSystemCerts = systemCerts; + return mSystemCerts; + } + } + + public void onCertificateStorageChange() { + synchronized (mLock) { + mSystemCerts = null; + } + } +} diff --git a/core/java/android/security/net/config/TrustAnchor.java b/core/java/android/security/net/config/TrustAnchor.java new file mode 100644 index 000000000000..b62d85fa37ae --- /dev/null +++ b/core/java/android/security/net/config/TrustAnchor.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import java.security.cert.X509Certificate; + +/** @hide */ +public final class TrustAnchor { + public final X509Certificate certificate; + public final boolean overridesPins; + + public TrustAnchor(X509Certificate certificate, boolean overridesPins) { + if (certificate == null) { + throw new NullPointerException("certificate"); + } + this.certificate = certificate; + this.overridesPins = overridesPins; + } +} diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java new file mode 100644 index 000000000000..e9d5aa1e2f34 --- /dev/null +++ b/core/java/android/security/net/config/UserCertificateSource.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.os.Environment; +import android.os.UserHandle; +import android.util.ArraySet; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Set; +import libcore.io.IoUtils; + +/** + * {@link CertificateSource} based on the user-installed trusted CA store. + * @hide + */ +public class UserCertificateSource implements CertificateSource { + private static final UserCertificateSource INSTANCE = new UserCertificateSource(); + private Set<X509Certificate> mUserCerts = null; + private final Object mLock = new Object(); + + private UserCertificateSource() { + } + + public static UserCertificateSource getInstance() { + return INSTANCE; + } + + @Override + public Set<X509Certificate> getCertificates() { + // TODO: loading all of these is wasteful, we should instead use a keystore style API. + synchronized (mLock) { + if (mUserCerts != null) { + return mUserCerts; + } + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); + final File userCaDir = new File(configDir, "cacerts-added"); + Set<X509Certificate> userCerts = new ArraySet<X509Certificate>(); + // If the user hasn't added any certificates the directory may not exist. + if (userCaDir.isDirectory()) { + for (String caFile : userCaDir.list()) { + InputStream is = null; + try { + is = new BufferedInputStream( + new FileInputStream(new File(userCaDir, caFile))); + userCerts.add((X509Certificate) certFactory.generateCertificate(is)); + } catch (CertificateException | IOException e) { + // Don't rethrow to be consistent with conscrypt's cert loading code. + continue; + } finally { + IoUtils.closeQuietly(is); + } + } + } + mUserCerts = userCerts; + return mUserCerts; + } + } + + public void onCertificateStorageChange() { + synchronized (mLock) { + mUserCerts = null; + } + } +} diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java new file mode 100644 index 000000000000..1706e95513ff --- /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 42402ebdc746..1d1edaaf6d75 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1591,7 +1591,7 @@ public final class ViewRootImpl implements ViewParent, 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 77f1efaefafe..4bcfa4caf158 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -519,7 +519,7 @@ public class ZygoteInit { String args[] = { "--setuid=1000", "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007,3009", "--capabilities=" + capabilities + "," + capabilities, "--nice-name=system_server", "--runtime-args", diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 47b2f3b0f6ab..4c68e015e531 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -273,7 +273,7 @@ LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunre # -Wno-c++11-extensions: Clang warns about Skia using the C++11 override keyword, but this project # is not being compiled with that level. Remove once this has changed. -LOCAL_CFLAGS += -Wno-c++11-extensions +LOCAL_CLANG_CFLAGS += -Wno-c++11-extensions # b/22414716: thread_local (android/graphics/Paint.cpp) and Clang don't like each other at the # moment. diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index cdce77c775c2..734c50e78e2b 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -608,15 +608,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) 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 ba39ba70f4d3..85cb4dfb095a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -97,20 +97,21 @@ public class AndroidKeyStoreProvider extends Provider { */ 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 2292ef415cfc..9621b545fada 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2325,12 +2325,15 @@ void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) { 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 3af640f76365..3236f6f7c24d 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -400,6 +400,13 @@ PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { 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 70148631db34..c529915988d9 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -201,6 +201,7 @@ public: 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 09d125839a68..595c10c88554 100644 --- a/libs/hwui/ShadowTessellator.cpp +++ b/libs/hwui/ShadowTessellator.cpp @@ -80,6 +80,11 @@ void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque, ALOGD("light center %f %f %f", adjustedLightCenter.x, adjustedLightCenter.y, adjustedLightCenter.z); #endif + if (isnan(adjustedLightCenter.x) + || isnan(adjustedLightCenter.y) + || isnan(adjustedLightCenter.z)) { + return; + } // light position (because it's in local space) needs to compensate for receiver transform // TODO: should apply to light orientation, not just position diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index b8c98041a6bf..3186a8e9735e 100644 --- a/libs/hwui/SpotShadow.cpp +++ b/libs/hwui/SpotShadow.cpp @@ -742,7 +742,7 @@ inline void genNewPenumbraAndPairWithUmbra(const Vector2* penumbra, int penumbra // 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 6bf5721d55c3..445ee6f91508 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 @@ public class ExifInterface { // 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 class ExifInterface { */ 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 class ExifInterface { 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 e8a5e43ce874..14bac4f03ccb 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 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } } - 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 54180f447c36..9bbacbc0d84c 100644 --- a/rs/java/android/renderscript/ScriptGroup.java +++ b/rs/java/android/renderscript/ScriptGroup.java @@ -278,6 +278,8 @@ public final class ScriptGroup extends BaseObj { 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 @@ public final class ScriptGroup extends BaseObj { 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 be7071e05044..113241da4572 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -393,7 +393,6 @@ nClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong kernelID, size_t numValues, numDependencies; RsScriptFieldID* fieldIDs; - uintptr_t* values; RsClosure* depClosures; RsScriptFieldID* depFieldIDs; @@ -430,15 +429,6 @@ nClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong kernelID, 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 @@ nClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong kernelID, 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 @@ nInvokeClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong invokeID, 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 @@ nInvokeClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong invokeID, 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 @@ exit: 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 b4613d6d811a..a8e27a809e73 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @Override public void onDestroy() { + if (mMagnificationController != null) { + mMagnificationController.cancelAnimation(); + } mScreenStateObserver.destroy(); mWindowManager.setMagnificationCallbacks(null); } @@ -988,10 +991,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 c2284224502d..9eb66dd281e1 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -88,6 +88,7 @@ public class VibratorService extends IVibratorService.Stub 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 @@ public class VibratorService extends IVibratorService.Stub } 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 6fbe8e448476..a5c6180396bf 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -20,6 +20,7 @@ import android.Manifest; import android.app.DownloadManager; import android.app.admin.DevicePolicyManager; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal.PackagesProvider; @@ -673,7 +674,12 @@ final class DefaultPermissionGrantPolicy { Intent intent, int userId) { ResolveInfo handler = mService.resolveIntent(intent, intent.resolveType(mService.mContext.getContentResolver()), 0, userId); - if (handler == null) { + if (handler == null || handler.activityInfo == null) { + return null; + } + ActivityInfo activityInfo = handler.activityInfo; + if (activityInfo.packageName.equals(mService.mResolveActivity.packageName) + && activityInfo.name.equals(mService.mResolveActivity.name)) { return null; } return getSystemPackageLPr(handler.activityInfo.packageName); @@ -842,7 +848,7 @@ final class DefaultPermissionGrantPolicy { 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 64278ed4499b..03fbd19094ba 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 4146c1c0111d..c518f77a0716 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 @@ public class UsageStatsService extends SystemService implements 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 000000000000..a63162d9ba09 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests +LOCAL_CERTIFICATE := platform + +LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := NetworkSecurityConfigTests + +include $(BUILD_PACKAGE) diff --git a/tests/NetworkSecurityConfigTest/AndroidManifest.xml b/tests/NetworkSecurityConfigTest/AndroidManifest.xml new file mode 100644 index 000000000000..4c1fbd375e1e --- /dev/null +++ b/tests/NetworkSecurityConfigTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.net.config" + android:sharedUserId="android.uid.system"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="android.security.net.config" + android:label="ANSC Tests"> + </instrumentation> +</manifest> diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der Binary files differnew file mode 100644 index 000000000000..235bd4797b78 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem new file mode 100644 index 000000000000..413e3c07d2ab --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT +MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0 +aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw +WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE +AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m +OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu +T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c +JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR +Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz +PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm +aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM +TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g +LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO +BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv +dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB +AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL +NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W +b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i +2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ +2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ +-----END CERTIFICATE----- diff --git a/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem new file mode 100644 index 000000000000..81648d984d64 --- /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 000000000000..eff13c80c343 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config cleartextTrafficPermitted="false" hstsEnforced="true"> + </base-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml new file mode 100644 index 000000000000..6af855d12d5f --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <!-- Bad pin digest --> + <pin digest="I am probably not an algorithm">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml new file mode 100644 index 000000000000..d683b74ae197 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <!-- Unknown pin digest --> + <pin>1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml new file mode 100644 index 000000000000..6f3f8b43d653 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <!-- empty digest --> + <pin digest="SHA-256"></pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml new file mode 100644 index 000000000000..fb2126c0f778 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + </domain-config> + <domain-config> + <!-- Same domain name used in two configs --> + <domain>android.com</domain> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml new file mode 100644 index 000000000000..95972cedfc90 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <!-- domains are not allowed in base-config --> + <domain>android.com</domain> + </base-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml new file mode 100644 index 000000000000..8b6b72151269 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <!-- pins are not allowed in base-config --> + <pin-set> + </pin-set> + </base-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml new file mode 100644 index 000000000000..62a7b8819d87 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <pin digest="SHA-256">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml new file mode 100644 index 000000000000..8da93173e6ec --- /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 000000000000..24eed7a4e943 --- /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 000000000000..ce0cbc874ca6 --- /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 000000000000..6d8565c35717 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="system" /> + <certificates src="user" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml new file mode 100644 index 000000000000..1bd94b648d22 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml new file mode 100644 index 000000000000..8093b9d05153 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml new file mode 100644 index 000000000000..f9f846526a92 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <!-- Invalid pin that has expired --> + <pin-set expiration="2015-01-01"> + <pin digest="SHA-256">aaaaaaaaaaa2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml new file mode 100644 index 000000000000..df08467af744 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="system" /> + <certificates src="user" /> + </trust-anchors> + </domain-config> + <domain-config> + <domain>google.com</domain> + <trust-anchors> + <certificates src="system" /> + <certificates src="user" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml new file mode 100644 index 000000000000..9743c5f0c4a0 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + <domain>google.com</domain> + <trust-anchors> + <certificates src="system" /> + <certificates src="user" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml new file mode 100644 index 000000000000..d45fd77a5f0f --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain includeSubdomains="true">android.com</domain> + <trust-anchors> + <certificates src="system" /> + </trust-anchors> + <!-- nested config that adds pins --> + <domain-config> + <domain>developer.android.com</domain> + <pin-set> + <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> + </domain-config> + <base-config cleartextTrafficPermitted="false"> + </base-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml new file mode 100644 index 000000000000..84e06e324513 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config cleartextTrafficPermitted="false"> + </base-config> + <!-- Nested config that overrides parent --> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">android.com</domain> + <domain-config cleartextTrafficPermitted="false"> + <domain>developer.android.com</domain> + </domain-config> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml new file mode 100644 index 000000000000..785714a8f19e --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + <trust-anchors> + <certificates src="system" overridePins="true" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml new file mode 100644 index 000000000000..1773d28094a3 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <domain-config> + <domain>android.com</domain> + <pin-set> + <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> + </pin-set> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml new file mode 100644 index 000000000000..dfd6fd9cc373 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="@raw/ca_certs_der" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml new file mode 100644 index 000000000000..894f29b4027c --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="@raw/ca_certs_pem" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml new file mode 100644 index 000000000000..482b26c18716 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + </trust-anchors> + </base-config> + <domain-config> + <domain includeSubdomains="true">android.com</domain> + <trust-anchors> + <certificates src="system" /> + <certificates src="user" /> + </trust-anchors> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java new file mode 100644 index 000000000000..9f48d56cb56b --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.app.Activity; +import android.test.ActivityUnitTestCase; +import android.util.ArraySet; +import android.util.Pair; +import java.io.IOException; +import java.net.Socket; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; + +public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { + + public NetworkSecurityConfigTests() { + super(Activity.class); + } + + // SHA-256 of the G2 intermediate CA for android.com (as of 10/2015). + private static final byte[] G2_SPKI_SHA256 + = hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776"); + + private static byte[] hexToBytes(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit( + s.charAt(i + 1), 16)); + } + return data; + } + + + + /** + * Return a NetworkSecurityConfig that has an empty TrustAnchor set. This should always cause a + * SSLHandshakeException when used for a connection. + */ + private NetworkSecurityConfig getEmptyConfig() { + return new NetworkSecurityConfig.Builder().build(); + } + + private NetworkSecurityConfig getSystemStoreConfig() { + return new NetworkSecurityConfig.Builder() + .addCertificatesEntryRef( + new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + .build(); + } + + public void testEmptyConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + ConfigSource testSource = + new TestConfigSource(domainMap, getEmptyConfig()); + SSLContext context = TestUtils.getSSLContext(testSource); + TestUtils.assertConnectionFails(context, "android.com", 443); + } + + public void testEmptyPerNetworkSecurityConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), getEmptyConfig())); + NetworkSecurityConfig defaultConfig = getSystemStoreConfig(); + SSLContext context = TestUtils.getSSLContext(new TestConfigSource(domainMap, defaultConfig)); + TestUtils.assertConnectionFails(context, "android.com", 443); + TestUtils.assertConnectionSucceeds(context, "google.com", 443); + } + + public void testBadPin() throws Exception { + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", new byte[0])); + NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() + .setPinSet(new PinSet(pins, Long.MAX_VALUE)) + .addCertificatesEntryRef( + new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + .build(); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), domain)); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig())); + TestUtils.assertConnectionFails(context, "android.com", 443); + TestUtils.assertConnectionSucceeds(context, "google.com", 443); + } + + public void testGoodPin() throws Exception { + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); + NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() + .setPinSet(new PinSet(pins, Long.MAX_VALUE)) + .addCertificatesEntryRef( + new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + .build(); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), domain)); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertConnectionSucceeds(context, "android.com", 443); + TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); + } + + public void testOverridePins() throws Exception { + // Use a bad pin + granting the system CA store the ability to override pins. + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", new byte[0])); + NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() + .setPinSet(new PinSet(pins, Long.MAX_VALUE)) + .addCertificatesEntryRef( + new CertificatesEntryRef(SystemCertificateSource.getInstance(), true)) + .build(); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), domain)); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertConnectionSucceeds(context, "android.com", 443); + } + + public void testMostSpecificNetworkSecurityConfig() throws Exception { + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), getEmptyConfig())); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("developer.android.com", false), getSystemStoreConfig())); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertConnectionFails(context, "android.com", 443); + TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); + } + + public void testSubdomainIncluded() throws Exception { + // First try connecting to a subdomain of a domain entry that includes subdomains. + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), getSystemStoreConfig())); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); + // Now try without including subdomains. + domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", false), getSystemStoreConfig())); + context = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertConnectionFails(context, "developer.android.com", 443); + } + + public void testConfigBuilderUsesParents() throws Exception { + // Check that a builder with a parent uses the parent's values when non is set. + NetworkSecurityConfig config = new NetworkSecurityConfig.Builder() + .setParent(NetworkSecurityConfig.getDefaultBuilder()) + .build(); + assert(!config.getTrustAnchors().isEmpty()); + } + + public void testConfigBuilderParentLoop() throws Exception { + NetworkSecurityConfig.Builder config1 = new NetworkSecurityConfig.Builder(); + NetworkSecurityConfig.Builder config2 = new NetworkSecurityConfig.Builder(); + config1.setParent(config2); + try { + config2.setParent(config1); + fail("Loop in NetworkSecurityConfig parents"); + } catch (IllegalArgumentException expected) { + } + } + + public void testWithUrlConnection() throws Exception { + ArraySet<Pin> pins = new ArraySet<Pin>(); + pins.add(new Pin("SHA-256", G2_SPKI_SHA256)); + NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() + .setPinSet(new PinSet(pins, Long.MAX_VALUE)) + .addCertificatesEntryRef( + new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + .build(); + ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap + = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); + domainMap.add(new Pair<Domain, NetworkSecurityConfig>( + new Domain("android.com", true), domain)); + SSLContext context + = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig())); + TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443); + TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443); + TestUtils.assertUrlConnectionFails(context, "google.com", 443); + } +} diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java new file mode 100644 index 000000000000..92eadc06cd49 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import java.util.Set; +import java.security.cert.X509Certificate; + +/** @hide */ +public class TestCertificateSource implements CertificateSource { + + private final Set<X509Certificate> mCertificates; + public TestCertificateSource(Set<X509Certificate> certificates) { + mCertificates = certificates; + } + + public Set<X509Certificate> getCertificates() { + return mCertificates; + } +} diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java new file mode 100644 index 000000000000..609f481a312c --- /dev/null +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestConfigSource.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.net.config; + +import android.util.Pair; +import java.util.Set; + +/** @hide */ +public class TestConfigSource implements ConfigSource { + private final Set<Pair<Domain, NetworkSecurityConfig>> mConfigs; + private final NetworkSecurityConfig mDefaultConfig; + public TestConfigSource(Set<Pair<Domain, NetworkSecurityConfig>> configs, + NetworkSecurityConfig defaultConfig) { + mConfigs = configs; + mDefaultConfig = defaultConfig; + } + + public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { + return mConfigs; + } + + public NetworkSecurityConfig getDefaultConfig() { + return mDefaultConfig; + } +} diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java new file mode 100644 index 000000000000..f7590fd6ff12 --- /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 000000000000..c6f3680f455c --- /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); + } +} |