1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.net.config; 18 19 import android.os.Build; 20 import android.util.ArrayMap; 21 import android.util.ArraySet; 22 import java.security.cert.X509Certificate; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.Collections; 26 import java.util.Comparator; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Set; 30 31 import javax.net.ssl.X509TrustManager; 32 33 /** 34 * @hide 35 */ 36 public final class NetworkSecurityConfig { 37 /** @hide */ 38 public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true; 39 /** @hide */ 40 public static final boolean DEFAULT_HSTS_ENFORCED = false; 41 42 private final boolean mCleartextTrafficPermitted; 43 private final boolean mHstsEnforced; 44 private final PinSet mPins; 45 private final List<CertificatesEntryRef> mCertificatesEntryRefs; 46 private Set<TrustAnchor> mAnchors; 47 private final Object mAnchorsLock = new Object(); 48 private NetworkSecurityTrustManager mTrustManager; 49 private final Object mTrustManagerLock = new Object(); 50 NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced, PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs)51 private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced, 52 PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) { 53 mCleartextTrafficPermitted = cleartextTrafficPermitted; 54 mHstsEnforced = hstsEnforced; 55 mPins = pins; 56 mCertificatesEntryRefs = certificatesEntryRefs; 57 // Sort the certificates entry refs so that all entries that override pins come before 58 // non-override pin entries. This allows us to handle the case where a certificate is in 59 // multiple entry refs by returning the certificate from the first entry ref. 60 Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() { 61 @Override 62 public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) { 63 if (lhs.overridesPins()) { 64 return rhs.overridesPins() ? 0 : -1; 65 } else { 66 return rhs.overridesPins() ? 1 : 0; 67 } 68 } 69 }); 70 } 71 getTrustAnchors()72 public Set<TrustAnchor> getTrustAnchors() { 73 synchronized (mAnchorsLock) { 74 if (mAnchors != null) { 75 return mAnchors; 76 } 77 // Merge trust anchors based on the X509Certificate. 78 // If we see the same certificate in two TrustAnchors, one with overridesPins and one 79 // without, the one with overridesPins wins. 80 // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first 81 // this can be simplified to just using the first occurrence of a certificate. 82 Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>(); 83 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 84 Set<TrustAnchor> anchors = ref.getTrustAnchors(); 85 for (TrustAnchor anchor : anchors) { 86 X509Certificate cert = anchor.certificate; 87 if (!anchorMap.containsKey(cert)) { 88 anchorMap.put(cert, anchor); 89 } 90 } 91 } 92 ArraySet<TrustAnchor> anchors = new ArraySet<TrustAnchor>(anchorMap.size()); 93 anchors.addAll(anchorMap.values()); 94 mAnchors = anchors; 95 return mAnchors; 96 } 97 } 98 isCleartextTrafficPermitted()99 public boolean isCleartextTrafficPermitted() { 100 return mCleartextTrafficPermitted; 101 } 102 isHstsEnforced()103 public boolean isHstsEnforced() { 104 return mHstsEnforced; 105 } 106 getPins()107 public PinSet getPins() { 108 return mPins; 109 } 110 getTrustManager()111 public NetworkSecurityTrustManager getTrustManager() { 112 synchronized(mTrustManagerLock) { 113 if (mTrustManager == null) { 114 mTrustManager = new NetworkSecurityTrustManager(this); 115 } 116 return mTrustManager; 117 } 118 } 119 120 /** @hide */ findTrustAnchorBySubjectAndPublicKey(X509Certificate cert)121 public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 122 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 123 TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); 124 if (anchor != null) { 125 return anchor; 126 } 127 } 128 return null; 129 } 130 131 /** @hide */ findTrustAnchorByIssuerAndSignature(X509Certificate cert)132 public TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate cert) { 133 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 134 TrustAnchor anchor = ref.findByIssuerAndSignature(cert); 135 if (anchor != null) { 136 return anchor; 137 } 138 } 139 return null; 140 } 141 142 /** @hide */ findAllCertificatesByIssuerAndSignature(X509Certificate cert)143 public Set<X509Certificate> findAllCertificatesByIssuerAndSignature(X509Certificate cert) { 144 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 145 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 146 certs.addAll(ref.findAllCertificatesByIssuerAndSignature(cert)); 147 } 148 return certs; 149 } 150 handleTrustStorageUpdate()151 public void handleTrustStorageUpdate() { 152 synchronized (mAnchorsLock) { 153 mAnchors = null; 154 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 155 ref.handleTrustStorageUpdate(); 156 } 157 } 158 getTrustManager().handleTrustStorageUpdate(); 159 } 160 161 /** 162 * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. 163 * 164 * <p> 165 * The default configuration has the following properties: 166 * <ol> 167 * <li>Cleartext traffic is permitted for non-ephemeral apps.</li> 168 * <li>Cleartext traffic is not permitted for ephemeral apps.</li> 169 * <li>HSTS is not enforced.</li> 170 * <li>No certificate pinning is used.</li> 171 * <li>The system certificate store is trusted for connections.</li> 172 * <li>If the application targets API level 23 (Android M) or lower then the user certificate 173 * store is trusted by default as well.</li> 174 * </ol> 175 * 176 * @hide 177 */ getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion)178 public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) { 179 Builder builder = new Builder() 180 .setHstsEnforced(DEFAULT_HSTS_ENFORCED) 181 // System certificate store, does not bypass static pins. 182 .addCertificatesEntryRef( 183 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); 184 final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2; 185 builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); 186 // Applications targeting N and above must opt in into trusting the user added certificate 187 // store. 188 if (targetSdkVersion <= Build.VERSION_CODES.M) { 189 // User certificate store, does not bypass static pins. 190 builder.addCertificatesEntryRef( 191 new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); 192 } 193 return builder; 194 } 195 196 /** 197 * Builder for creating {@code NetworkSecurityConfig} objects. 198 * @hide 199 */ 200 public static final class Builder { 201 private List<CertificatesEntryRef> mCertificatesEntryRefs; 202 private PinSet mPinSet; 203 private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 204 private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED; 205 private boolean mCleartextTrafficPermittedSet = false; 206 private boolean mHstsEnforcedSet = false; 207 private Builder mParentBuilder; 208 209 /** 210 * Sets the parent {@code Builder} for this {@code Builder}. 211 * The parent will be used to determine values not configured in this {@code Builder} 212 * in {@link Builder#build()}, recursively if needed. 213 */ 214 public Builder setParent(Builder parent) { 215 // Sanity check to avoid adding loops. 216 Builder current = parent; 217 while (current != null) { 218 if (current == this) { 219 throw new IllegalArgumentException("Loops are not allowed in Builder parents"); 220 } 221 current = current.getParent(); 222 } 223 mParentBuilder = parent; 224 return this; 225 } 226 227 public Builder getParent() { 228 return mParentBuilder; 229 } 230 231 public Builder setPinSet(PinSet pinSet) { 232 mPinSet = pinSet; 233 return this; 234 } 235 236 private PinSet getEffectivePinSet() { 237 if (mPinSet != null) { 238 return mPinSet; 239 } 240 if (mParentBuilder != null) { 241 return mParentBuilder.getEffectivePinSet(); 242 } 243 return PinSet.EMPTY_PINSET; 244 } 245 246 public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) { 247 mCleartextTrafficPermitted = cleartextTrafficPermitted; 248 mCleartextTrafficPermittedSet = true; 249 return this; 250 } 251 252 private boolean getEffectiveCleartextTrafficPermitted() { 253 if (mCleartextTrafficPermittedSet) { 254 return mCleartextTrafficPermitted; 255 } 256 if (mParentBuilder != null) { 257 return mParentBuilder.getEffectiveCleartextTrafficPermitted(); 258 } 259 return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 260 } 261 262 public Builder setHstsEnforced(boolean hstsEnforced) { 263 mHstsEnforced = hstsEnforced; 264 mHstsEnforcedSet = true; 265 return this; 266 } 267 268 private boolean getEffectiveHstsEnforced() { 269 if (mHstsEnforcedSet) { 270 return mHstsEnforced; 271 } 272 if (mParentBuilder != null) { 273 return mParentBuilder.getEffectiveHstsEnforced(); 274 } 275 return DEFAULT_HSTS_ENFORCED; 276 } 277 278 public Builder addCertificatesEntryRef(CertificatesEntryRef ref) { 279 if (mCertificatesEntryRefs == null) { 280 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 281 } 282 mCertificatesEntryRefs.add(ref); 283 return this; 284 } 285 286 public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) { 287 if (mCertificatesEntryRefs == null) { 288 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 289 } 290 mCertificatesEntryRefs.addAll(refs); 291 return this; 292 } 293 294 private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() { 295 if (mCertificatesEntryRefs != null) { 296 return mCertificatesEntryRefs; 297 } 298 if (mParentBuilder != null) { 299 return mParentBuilder.getEffectiveCertificatesEntryRefs(); 300 } 301 return Collections.<CertificatesEntryRef>emptyList(); 302 } 303 304 public boolean hasCertificatesEntryRefs() { 305 return mCertificatesEntryRefs != null; 306 } 307 308 List<CertificatesEntryRef> getCertificatesEntryRefs() { 309 return mCertificatesEntryRefs; 310 } 311 312 public NetworkSecurityConfig build() { 313 boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); 314 boolean hstsEnforced = getEffectiveHstsEnforced(); 315 PinSet pinSet = getEffectivePinSet(); 316 List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs(); 317 return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs); 318 } 319 } 320 } 321