Merge "Use a builder for NetworkSecurityConfig" am: 7e98f2e697

am: 478fad3cf5

* commit '478fad3cf53874f9cc96169cd25b29b9c031637a':
  Use a builder for NetworkSecurityConfig
This commit is contained in:
Chad Brubaker
2015-11-06 20:31:02 +00:00
committed by android-build-merger
5 changed files with 235 additions and 53 deletions

View File

@@ -17,6 +17,9 @@
package android.security.net.config;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -26,6 +29,12 @@ 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;
@@ -35,7 +44,7 @@ public final class NetworkSecurityConfig {
private X509TrustManager mTrustManager;
private final Object mTrustManagerLock = new Object();
public NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
mCleartextTrafficPermitted = cleartextTrafficPermitted;
mHstsEnforced = hstsEnforced;
@@ -83,4 +92,151 @@ public final class NetworkSecurityConfig {
mAnchors = null;
}
}
/**
* Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
*
* <p>
* The default configuration has the following properties:
* <ol>
* <li>Cleartext traffic is permitted.</li>
* <li>HSTS is not enforced.</li>
* <li>No certificate pinning is used.</li>
* <li>The system and user added trusted certificate stores are trusted for connections.</li>
* </ol>
*
* @hide
*/
public static final Builder getDefaultBuilder() {
return new Builder()
.setCleartextTrafficPermitted(DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
// User certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
}
/**
* Builder for creating {@code NetworkSecurityConfig} objects.
* @hide
*/
public static final class Builder {
private List<CertificatesEntryRef> mCertificatesEntryRefs;
private PinSet mPinSet;
private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED;
private boolean mCleartextTrafficPermittedSet = false;
private boolean mHstsEnforcedSet = false;
private Builder mParentBuilder;
/**
* Sets the parent {@code Builder} for this {@code Builder}.
* The parent will be used to determine values not configured in this {@code Builder}
* in {@link Builder#build()}, recursively if needed.
*/
public Builder setParent(Builder parent) {
// Sanity check to avoid adding loops.
Builder current = parent;
while (current != null) {
if (current == this) {
throw new IllegalArgumentException("Loops are not allowed in Builder parents");
}
current = current.getParent();
}
mParentBuilder = parent;
return this;
}
public Builder getParent() {
return mParentBuilder;
}
public Builder setPinSet(PinSet pinSet) {
mPinSet = pinSet;
return this;
}
private PinSet getEffectivePinSet() {
if (mPinSet != null) {
return mPinSet;
}
if (mParentBuilder != null) {
return mParentBuilder.getEffectivePinSet();
}
return PinSet.EMPTY_PINSET;
}
public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) {
mCleartextTrafficPermitted = cleartextTrafficPermitted;
mCleartextTrafficPermittedSet = true;
return this;
}
private boolean getEffectiveCleartextTrafficPermitted() {
if (mCleartextTrafficPermittedSet) {
return mCleartextTrafficPermitted;
}
if (mParentBuilder != null) {
return mParentBuilder.getEffectiveCleartextTrafficPermitted();
}
return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
}
public Builder setHstsEnforced(boolean hstsEnforced) {
mHstsEnforced = hstsEnforced;
mHstsEnforcedSet = true;
return this;
}
private boolean getEffectiveHstsEnforced() {
if (mHstsEnforcedSet) {
return mHstsEnforced;
}
if (mParentBuilder != null) {
return mParentBuilder.getEffectiveHstsEnforced();
}
return DEFAULT_HSTS_ENFORCED;
}
public Builder addCertificatesEntryRef(CertificatesEntryRef ref) {
if (mCertificatesEntryRefs == null) {
mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
}
mCertificatesEntryRefs.add(ref);
return this;
}
public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) {
if (mCertificatesEntryRefs == null) {
mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
}
mCertificatesEntryRefs.addAll(refs);
return this;
}
private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() {
if (mCertificatesEntryRefs != null) {
return mCertificatesEntryRefs;
}
if (mParentBuilder != null) {
return mParentBuilder.getEffectiveCertificatesEntryRefs();
}
return Collections.<CertificatesEntryRef>emptyList();
}
public boolean hasCertificateEntryRefs() {
return mCertificatesEntryRefs != null;
}
public NetworkSecurityConfig build() {
boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
boolean hstsEnforced = getEffectiveCleartextTrafficPermitted();
PinSet pinSet = getEffectivePinSet();
List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();
return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);
}
}
}

View File

@@ -17,10 +17,13 @@
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;

View File

@@ -36,18 +36,23 @@ import libcore.io.IoUtils;
* @hide
*/
public class SystemCertificateSource implements CertificateSource {
private static Set<X509Certificate> sSystemCerts = null;
private static final Object sLock = new Object();
private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
private Set<X509Certificate> mSystemCerts = null;
private final Object mLock = new Object();
public SystemCertificateSource() {
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 (sLock) {
if (sSystemCerts != null) {
return sSystemCerts;
synchronized (mLock) {
if (mSystemCerts != null) {
return mSystemCerts;
}
CertificateFactory certFactory;
try {
@@ -83,14 +88,14 @@ public class SystemCertificateSource implements CertificateSource {
IoUtils.closeQuietly(is);
}
}
sSystemCerts = systemCerts;
return sSystemCerts;
mSystemCerts = systemCerts;
return mSystemCerts;
}
}
public void onCertificateStorageChange() {
synchronized (sLock) {
sSystemCerts = null;
synchronized (mLock) {
mSystemCerts = null;
}
}
}

View File

@@ -36,18 +36,23 @@ import libcore.io.IoUtils;
* @hide
*/
public class UserCertificateSource implements CertificateSource {
private static Set<X509Certificate> sUserCerts = null;
private static final Object sLock = new Object();
private static final UserCertificateSource INSTANCE = new UserCertificateSource();
private Set<X509Certificate> mUserCerts = null;
private final Object mLock = new Object();
public UserCertificateSource() {
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 (sLock) {
if (sUserCerts != null) {
return sUserCerts;
synchronized (mLock) {
if (mUserCerts != null) {
return mUserCerts;
}
CertificateFactory certFactory;
try {
@@ -75,14 +80,14 @@ public class UserCertificateSource implements CertificateSource {
IoUtils.closeQuietly(is);
}
}
sUserCerts = userCerts;
return sUserCerts;
mUserCerts = userCerts;
return mUserCerts;
}
}
public void onCertificateStorageChange() {
synchronized (sLock) {
sUserCerts = null;
synchronized (mLock) {
mUserCerts = null;
}
}
}

View File

@@ -100,16 +100,14 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
* SSLHandshakeException when used for a connection.
*/
private NetworkSecurityConfig getEmptyConfig() {
return new NetworkSecurityConfig(true, false,
new PinSet(new ArraySet<Pin>(), -1),
new ArrayList<CertificatesEntryRef>());
return new NetworkSecurityConfig.Builder().build();
}
private NetworkSecurityConfig getSystemStoreConfig() {
ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
return new NetworkSecurityConfig(true, false, new PinSet(new ArraySet<Pin>(),
-1), defaultSource);
return new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
.build();
}
public void testEmptyConfig() throws Exception {
@@ -126,24 +124,20 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), getEmptyConfig()));
ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
NetworkSecurityConfig defaultConfig = new NetworkSecurityConfig(true, false,
new PinSet(new ArraySet<Pin>(), -1),
defaultSource);
NetworkSecurityConfig defaultConfig = getSystemStoreConfig();
SSLContext context = getSSLContext(new TestConfigSource(domainMap, defaultConfig));
assertConnectionFails(context, "android.com", 443);
assertConnectionSucceeds(context, "google.com", 443);
}
public void testBadPin() throws Exception {
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", new byte[0]));
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
new PinSet(pins, Long.MAX_VALUE),
systemSource);
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>(
@@ -155,13 +149,13 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
}
public void testGoodPin() throws Exception {
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
new PinSet(pins, Long.MAX_VALUE),
systemSource);
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>(
@@ -174,13 +168,13 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
public void testOverridePins() throws Exception {
// Use a bad pin + granting the system CA store the ability to override pins.
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), true));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", new byte[0]));
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
new PinSet(pins, Long.MAX_VALUE),
systemSource);
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>(
@@ -220,14 +214,33 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
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 {
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
new PinSet(pins, Long.MAX_VALUE),
systemSource);
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>(