/*
 * 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.pm.ApplicationInfo;
import android.os.Build;
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.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

@hide
/** * @hide */
public final class NetworkSecurityConfig {
@hide
/** @hide */
public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
@hide
/** @hide */
public static final boolean DEFAULT_HSTS_ENFORCED = false; 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; // Sort the certificates entry refs so that all entries that override pins come before // non-override pin entries. This allows us to handle the case where a certificate is in // multiple entry refs by returning the certificate from the first entry ref. Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() { @Override public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) { if (lhs.overridesPins()) { return rhs.overridesPins() ? 0 : -1; } else { return rhs.overridesPins() ? 1 : 0; } } }); } 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. // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first // this can be simplified to just using the first occurrence of a certificate. Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>(); for (CertificatesEntryRef ref : mCertificatesEntryRefs) { Set<TrustAnchor> anchors = ref.getTrustAnchors(); for (TrustAnchor anchor : anchors) { X509Certificate cert = anchor.certificate; if (!anchorMap.containsKey(cert)) { anchorMap.put(cert, 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; } }
@hide
/** @hide */
public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { for (CertificatesEntryRef ref : mCertificatesEntryRefs) { TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); if (anchor != null) { return anchor; } } return null; }
@hide
/** @hide */
public TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate cert) { for (CertificatesEntryRef ref : mCertificatesEntryRefs) { TrustAnchor anchor = ref.findByIssuerAndSignature(cert); if (anchor != null) { return anchor; } } return null; }
@hide
/** @hide */
public Set<X509Certificate> findAllCertificatesByIssuerAndSignature(X509Certificate cert) { Set<X509Certificate> certs = new ArraySet<X509Certificate>(); for (CertificatesEntryRef ref : mCertificatesEntryRefs) { certs.addAll(ref.findAllCertificatesByIssuerAndSignature(cert)); } return certs; } public void handleTrustStorageUpdate() { synchronized (mAnchorsLock) { mAnchors = null; for (CertificatesEntryRef ref : mCertificatesEntryRefs) { ref.handleTrustStorageUpdate(); } } getTrustManager().handleTrustStorageUpdate(); }
Return a Builder for the default NetworkSecurityConfig.

The default configuration has the following properties:

  1. If the application targets API level 27 (Android O MR1) or lower then cleartext traffic is allowed by default.
  2. Cleartext traffic is not permitted for ephemeral apps.
  3. HSTS is not enforced.
  4. No certificate pinning is used.
  5. The system certificate store is trusted for connections.
  6. If the application targets API level 23 (Android M) or lower then the user certificate store is trusted by default as well for non-privileged applications.
  7. Privileged applications do not trust the user certificate store on Android P and higher.
@hide
/** * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. * * <p> * The default configuration has the following properties: * <ol> * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic * is allowed by default.</li> * <li>Cleartext traffic is not permitted for ephemeral apps.</li> * <li>HSTS is not enforced.</li> * <li>No certificate pinning is used.</li> * <li>The system certificate store is trusted for connections.</li> * <li>If the application targets API level 23 (Android M) or lower then the user certificate * store is trusted by default as well for non-privileged applications.</li> * <li>Privileged applications do not trust the user certificate store on Android P and higher. * </li> * </ol> * * @hide */
public static Builder getDefaultBuilder(ApplicationInfo info) { Builder builder = new Builder() .setHstsEnforced(DEFAULT_HSTS_ENFORCED) // System certificate store, does not bypass static pins. .addCertificatesEntryRef( new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P && info.targetSandboxVersion < 2; 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. builder.addCertificatesEntryRef( new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); } return builder; }
Builder for creating NetworkSecurityConfig objects.
@hide
/** * 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 Builder for this Builder. The parent will be used to determine values not configured in this Builder in build(), recursively if needed.
/** * 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); } } }