diff options
author | 2024-12-04 09:12:49 +0000 | |
---|---|---|
committer | 2024-12-04 09:12:49 +0000 | |
commit | dd9e9d2058c1033682d0fac4b6684ae24b71b40f (patch) | |
tree | 667a81d43b02b35047c1fe05f599f2bb4fdb49f0 | |
parent | 56099f595324d718e6c570caa7223141065dc2b0 (diff) | |
parent | ab328e51c77835a03057d20cb524d26f73c7ee74 (diff) |
Merge "Disable CT verification for inline certificate and user store" into main
8 files changed, 137 insertions, 15 deletions
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java index 45cd0f011299..a46049fb2f6d 100644 --- a/core/java/android/security/net/config/CertificatesEntryRef.java +++ b/core/java/android/security/net/config/CertificatesEntryRef.java @@ -17,6 +17,7 @@ package android.security.net.config; import android.util.ArraySet; + import java.security.cert.X509Certificate; import java.util.Set; @@ -24,16 +25,23 @@ import java.util.Set; public final class CertificatesEntryRef { private final CertificateSource mSource; private final boolean mOverridesPins; + private final boolean mDisableCT; - public CertificatesEntryRef(CertificateSource source, boolean overridesPins) { + public CertificatesEntryRef(CertificateSource source, boolean overridesPins, + boolean disableCT) { mSource = source; mOverridesPins = overridesPins; + mDisableCT = disableCT; } boolean overridesPins() { return mOverridesPins; } + boolean disableCT() { + return mDisableCT; + } + public Set<TrustAnchor> getTrustAnchors() { // TODO: cache this [but handle mutable sources] Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>(); diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java index 8d4f098bcb37..a54d8d0499cb 100644 --- a/core/java/android/security/net/config/KeyStoreConfigSource.java +++ b/core/java/android/security/net/config/KeyStoreConfigSource.java @@ -17,8 +17,8 @@ package android.security.net.config; import android.util.Pair; + import java.security.KeyStore; -import java.security.KeyStoreException; import java.util.Set; /** @@ -32,7 +32,7 @@ class KeyStoreConfigSource implements ConfigSource { mConfig = new NetworkSecurityConfig.Builder() .addCertificatesEntryRef( // Use the KeyStore and do not override pins (of which there are none). - new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false)) + new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false, false)) .build(); } diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java index 129ae63ec9c0..410c68b8d04d 100644 --- a/core/java/android/security/net/config/NetworkSecurityConfig.java +++ b/core/java/android/security/net/config/NetworkSecurityConfig.java @@ -112,7 +112,6 @@ public final class NetworkSecurityConfig { return mHstsEnforced; } - // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides. public boolean isCertificateTransparencyVerificationRequired() { return mCertificateTransparencyVerificationRequired; } @@ -192,20 +191,21 @@ public final class NetworkSecurityConfig { * @hide */ public static Builder getDefaultBuilder(ApplicationInfo info) { + // System certificate store, does not bypass static pins, does not disable CT. + CertificatesEntryRef systemRef = new CertificatesEntryRef( + SystemCertificateSource.getInstance(), false, false); Builder builder = new Builder() .setHstsEnforced(DEFAULT_HSTS_ENFORCED) - // System certificate store, does not bypass static pins. - .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); + .addCertificatesEntryRef(systemRef); final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P && !info.isInstantApp(); builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); // Applications targeting N and above must opt in into trusting the user added certificate // store. if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) { - // User certificate store, does not bypass static pins. + // User certificate store, does not bypass static pins. CT is disabled. builder.addCertificatesEntryRef( - new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); + new CertificatesEntryRef(UserCertificateSource.getInstance(), false, true)); } return builder; } @@ -339,6 +339,16 @@ public final class NetworkSecurityConfig { if (mCertificateTransparencyVerificationRequiredSet) { return mCertificateTransparencyVerificationRequired; } + // CT verification has not been set explicitly. Before deferring to + // the parent, check if any of the CertificatesEntryRef requires it + // to be disabled (i.e., user store or inline certificate). + if (hasCertificatesEntryRefs()) { + for (CertificatesEntryRef ref : getCertificatesEntryRefs()) { + if (ref.disableCT()) { + return false; + } + } + } if (mParentBuilder != null) { return mParentBuilder.getCertificateTransparencyVerificationRequired(); } diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java index b1c14793bbbd..95e579fc538b 100644 --- a/core/java/android/security/net/config/XmlConfigSource.java +++ b/core/java/android/security/net/config/XmlConfigSource.java @@ -182,6 +182,7 @@ public class XmlConfigSource implements ConfigSource { boolean overridePins = parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins); int sourceId = parser.getAttributeResourceValue(null, "src", -1); + boolean disableCT = false; String sourceString = parser.getAttributeValue(null, "src"); CertificateSource source = null; if (sourceString == null) { @@ -190,10 +191,12 @@ public class XmlConfigSource implements ConfigSource { if (sourceId != -1) { // TODO: Cache ResourceCertificateSources by sourceId source = new ResourceCertificateSource(sourceId, mContext); + disableCT = true; } else if ("system".equals(sourceString)) { source = SystemCertificateSource.getInstance(); } else if ("user".equals(sourceString)) { source = UserCertificateSource.getInstance(); + disableCT = true; } else if ("wfa".equals(sourceString)) { source = WfaCertificateSource.getInstance(); } else { @@ -201,7 +204,7 @@ public class XmlConfigSource implements ConfigSource { + "Should be one of system|user|@resourceVal"); } XmlUtils.skipCurrentTag(parser); - return new CertificatesEntryRef(source, overridePins); + return new CertificatesEntryRef(source, overridePins, disableCT); } private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser, diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml new file mode 100644 index 000000000000..67d4397afe7d --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <certificateTransparency enabled="true" /> + </base-config> + <domain-config> + <domain>android.com</domain> + <trust-anchors> + <certificates src="system" /> + </trust-anchors> + </domain-config> + <domain-config> + <domain>subdomain_user.android.com</domain> + <trust-anchors> + <certificates src="user" /> + </trust-anchors> + </domain-config> + <domain-config> + <certificateTransparency enabled="true" /> + <domain>subdomain_user_ct.android.com</domain> + <trust-anchors> + <certificates src="user" /> + </trust-anchors> + </domain-config> + <domain-config> + <domain>subdomain_inline.android.com</domain> + <trust-anchors> + <certificates src="@raw/ca_certs_pem" /> + </trust-anchors> + </domain-config> + <domain-config> + <certificateTransparency enabled="true" /> + <domain>subdomain_inline_ct.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/ct_users.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml new file mode 100644 index 000000000000..c35fd71c3178 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config> + <base-config> + <trust-anchors> + <certificates src="user" /> + </trust-anchors> + </base-config> + <domain-config> + <domain>android.com</domain> + </domain-config> + <domain-config> + <certificateTransparency enabled="true" /> + <domain>subdomain.android.com</domain> + </domain-config> +</network-security-config> diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java index 0494f174f191..c6fe06858e3f 100644 --- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java @@ -111,7 +111,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { private NetworkSecurityConfig getSystemStoreConfig() { return new NetworkSecurityConfig.Builder() .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + new CertificatesEntryRef( + SystemCertificateSource.getInstance(), false, false)) .build(); } @@ -141,7 +142,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() .setPinSet(new PinSet(pins, Long.MAX_VALUE)) .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + new CertificatesEntryRef( + SystemCertificateSource.getInstance(), false, false)) .build(); ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); @@ -159,7 +161,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() .setPinSet(new PinSet(pins, Long.MAX_VALUE)) .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + new CertificatesEntryRef( + SystemCertificateSource.getInstance(), false, false)) .build(); ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); @@ -178,7 +181,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() .setPinSet(new PinSet(pins, Long.MAX_VALUE)) .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), true)) + new CertificatesEntryRef( + SystemCertificateSource.getInstance(), true, false)) .build(); ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); @@ -245,7 +249,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> { NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder() .setPinSet(new PinSet(pins, Long.MAX_VALUE)) .addCertificatesEntryRef( - new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)) + new CertificatesEntryRef( + SystemCertificateSource.getInstance(), false, false)) .build(); ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>(); diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java index 81e05c1d4e42..542465d62a66 100644 --- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java +++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java @@ -502,4 +502,47 @@ public class XmlConfigTests extends AndroidTestCase { TestUtils.assertConnectionSucceeds(context, "android.com", 443); TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443); } + + public void testCertificateTransparencyDomainConfig() throws Exception { + XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_domains, + TestUtils.makeApplicationInfo()); + ApplicationConfig appConfig = new ApplicationConfig(source); + assertTrue(appConfig.hasPerDomainConfigs()); + NetworkSecurityConfig config = appConfig.getConfigForHostname(""); + assertNotNull(config); + // Check defaults. + assertTrue(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("android.com"); + assertTrue(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("subdomain_user.android.com"); + assertFalse(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("subdomain_user_ct.android.com"); + assertTrue(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("subdomain_inline.android.com"); + assertFalse(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("subdomain_inline_ct.android.com"); + assertTrue(config.isCertificateTransparencyVerificationRequired()); + } + + public void testCertificateTransparencyUserConfig() throws Exception { + XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_users, + TestUtils.makeApplicationInfo()); + ApplicationConfig appConfig = new ApplicationConfig(source); + assertTrue(appConfig.hasPerDomainConfigs()); + NetworkSecurityConfig config = appConfig.getConfigForHostname(""); + assertNotNull(config); + // Check defaults. + assertFalse(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("android.com"); + assertFalse(config.isCertificateTransparencyVerificationRequired()); + + config = appConfig.getConfigForHostname("subdomain.android.com"); + assertTrue(config.isCertificateTransparencyVerificationRequired()); + } } |