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.content.pm.ApplicationInfo; 20 import android.os.Build; 21 import android.util.ArrayMap; 22 import android.util.ArraySet; 23 24 import java.security.cert.X509Certificate; 25 import java.util.ArrayList; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.Comparator; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.Set; 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 /** @hide */ 42 public static final boolean DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED = false; 43 44 private final boolean mCleartextTrafficPermitted; 45 private final boolean mHstsEnforced; 46 private final boolean mCertificateTransparencyVerificationRequired; 47 private final PinSet mPins; 48 private final List<CertificatesEntryRef> mCertificatesEntryRefs; 49 private Set<TrustAnchor> mAnchors; 50 private final Object mAnchorsLock = new Object(); 51 private NetworkSecurityTrustManager mTrustManager; 52 private final Object mTrustManagerLock = new Object(); 53 NetworkSecurityConfig( boolean cleartextTrafficPermitted, boolean hstsEnforced, boolean certificateTransparencyVerificationRequired, PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs)54 private NetworkSecurityConfig( 55 boolean cleartextTrafficPermitted, 56 boolean hstsEnforced, 57 boolean certificateTransparencyVerificationRequired, 58 PinSet pins, 59 List<CertificatesEntryRef> certificatesEntryRefs) { 60 mCleartextTrafficPermitted = cleartextTrafficPermitted; 61 mHstsEnforced = hstsEnforced; 62 mCertificateTransparencyVerificationRequired = certificateTransparencyVerificationRequired; 63 mPins = pins; 64 mCertificatesEntryRefs = certificatesEntryRefs; 65 // Sort the certificates entry refs so that all entries that override pins come before 66 // non-override pin entries. This allows us to handle the case where a certificate is in 67 // multiple entry refs by returning the certificate from the first entry ref. 68 Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() { 69 @Override 70 public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) { 71 if (lhs.overridesPins()) { 72 return rhs.overridesPins() ? 0 : -1; 73 } else { 74 return rhs.overridesPins() ? 1 : 0; 75 } 76 } 77 }); 78 } 79 getTrustAnchors()80 public Set<TrustAnchor> getTrustAnchors() { 81 synchronized (mAnchorsLock) { 82 if (mAnchors != null) { 83 return mAnchors; 84 } 85 // Merge trust anchors based on the X509Certificate. 86 // If we see the same certificate in two TrustAnchors, one with overridesPins and one 87 // without, the one with overridesPins wins. 88 // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first 89 // this can be simplified to just using the first occurrence of a certificate. 90 Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>(); 91 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 92 Set<TrustAnchor> anchors = ref.getTrustAnchors(); 93 for (TrustAnchor anchor : anchors) { 94 X509Certificate cert = anchor.certificate; 95 if (!anchorMap.containsKey(cert)) { 96 anchorMap.put(cert, anchor); 97 } 98 } 99 } 100 ArraySet<TrustAnchor> anchors = new ArraySet<TrustAnchor>(anchorMap.size()); 101 anchors.addAll(anchorMap.values()); 102 mAnchors = anchors; 103 return mAnchors; 104 } 105 } 106 isCleartextTrafficPermitted()107 public boolean isCleartextTrafficPermitted() { 108 return mCleartextTrafficPermitted; 109 } 110 isHstsEnforced()111 public boolean isHstsEnforced() { 112 return mHstsEnforced; 113 } 114 115 // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides. isCertificateTransparencyVerificationRequired()116 public boolean isCertificateTransparencyVerificationRequired() { 117 return mCertificateTransparencyVerificationRequired; 118 } 119 getPins()120 public PinSet getPins() { 121 return mPins; 122 } 123 getTrustManager()124 public NetworkSecurityTrustManager getTrustManager() { 125 synchronized(mTrustManagerLock) { 126 if (mTrustManager == null) { 127 mTrustManager = new NetworkSecurityTrustManager(this); 128 } 129 return mTrustManager; 130 } 131 } 132 133 /** @hide */ findTrustAnchorBySubjectAndPublicKey(X509Certificate cert)134 public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 135 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 136 TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); 137 if (anchor != null) { 138 return anchor; 139 } 140 } 141 return null; 142 } 143 144 /** @hide */ findTrustAnchorByIssuerAndSignature(X509Certificate cert)145 public TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate cert) { 146 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 147 TrustAnchor anchor = ref.findByIssuerAndSignature(cert); 148 if (anchor != null) { 149 return anchor; 150 } 151 } 152 return null; 153 } 154 155 /** @hide */ findAllCertificatesByIssuerAndSignature(X509Certificate cert)156 public Set<X509Certificate> findAllCertificatesByIssuerAndSignature(X509Certificate cert) { 157 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 158 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 159 certs.addAll(ref.findAllCertificatesByIssuerAndSignature(cert)); 160 } 161 return certs; 162 } 163 handleTrustStorageUpdate()164 public void handleTrustStorageUpdate() { 165 synchronized (mAnchorsLock) { 166 mAnchors = null; 167 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 168 ref.handleTrustStorageUpdate(); 169 } 170 } 171 getTrustManager().handleTrustStorageUpdate(); 172 } 173 174 /** 175 * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. 176 * 177 * <p> 178 * The default configuration has the following properties: 179 * <ol> 180 * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic 181 * is allowed by default.</li> 182 * <li>Cleartext traffic is not permitted for ephemeral apps.</li> 183 * <li>HSTS is not enforced.</li> 184 * <li>No certificate pinning is used.</li> 185 * <li>The system certificate store is trusted for connections.</li> 186 * <li>If the application targets API level 23 (Android M) or lower then the user certificate 187 * store is trusted by default as well for non-privileged applications.</li> 188 * <li>Privileged applications do not trust the user certificate store on Android P and higher. 189 * </li> 190 * </ol> 191 * 192 * @hide 193 */ getDefaultBuilder(ApplicationInfo info)194 public static Builder getDefaultBuilder(ApplicationInfo info) { 195 Builder builder = new Builder() 196 .setHstsEnforced(DEFAULT_HSTS_ENFORCED) 197 // System certificate store, does not bypass static pins. 198 .addCertificatesEntryRef( 199 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); 200 final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P 201 && !info.isInstantApp(); 202 builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); 203 // Applications targeting N and above must opt in into trusting the user added certificate 204 // store. 205 if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) { 206 // User certificate store, does not bypass static pins. 207 builder.addCertificatesEntryRef( 208 new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); 209 } 210 return builder; 211 } 212 213 /** 214 * Builder for creating {@code NetworkSecurityConfig} objects. 215 * @hide 216 */ 217 public static final class Builder { 218 private List<CertificatesEntryRef> mCertificatesEntryRefs; 219 private PinSet mPinSet; 220 private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 221 private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED; 222 private boolean mCleartextTrafficPermittedSet = false; 223 private boolean mHstsEnforcedSet = false; 224 private boolean mCertificateTransparencyVerificationRequired = 225 DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; 226 private boolean mCertificateTransparencyVerificationRequiredSet = false; 227 private Builder mParentBuilder; 228 229 /** 230 * Sets the parent {@code Builder} for this {@code Builder}. 231 * The parent will be used to determine values not configured in this {@code Builder} 232 * in {@link Builder#build()}, recursively if needed. 233 */ 234 public Builder setParent(Builder parent) { 235 // Quick check to avoid adding loops. 236 Builder current = parent; 237 while (current != null) { 238 if (current == this) { 239 throw new IllegalArgumentException("Loops are not allowed in Builder parents"); 240 } 241 current = current.getParent(); 242 } 243 mParentBuilder = parent; 244 return this; 245 } 246 247 public Builder getParent() { 248 return mParentBuilder; 249 } 250 251 public Builder setPinSet(PinSet pinSet) { 252 mPinSet = pinSet; 253 return this; 254 } 255 256 private PinSet getEffectivePinSet() { 257 if (mPinSet != null) { 258 return mPinSet; 259 } 260 if (mParentBuilder != null) { 261 return mParentBuilder.getEffectivePinSet(); 262 } 263 return PinSet.EMPTY_PINSET; 264 } 265 266 public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) { 267 mCleartextTrafficPermitted = cleartextTrafficPermitted; 268 mCleartextTrafficPermittedSet = true; 269 return this; 270 } 271 272 private boolean getEffectiveCleartextTrafficPermitted() { 273 if (mCleartextTrafficPermittedSet) { 274 return mCleartextTrafficPermitted; 275 } 276 if (mParentBuilder != null) { 277 return mParentBuilder.getEffectiveCleartextTrafficPermitted(); 278 } 279 return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 280 } 281 282 public Builder setHstsEnforced(boolean hstsEnforced) { 283 mHstsEnforced = hstsEnforced; 284 mHstsEnforcedSet = true; 285 return this; 286 } 287 288 private boolean getEffectiveHstsEnforced() { 289 if (mHstsEnforcedSet) { 290 return mHstsEnforced; 291 } 292 if (mParentBuilder != null) { 293 return mParentBuilder.getEffectiveHstsEnforced(); 294 } 295 return DEFAULT_HSTS_ENFORCED; 296 } 297 298 public Builder addCertificatesEntryRef(CertificatesEntryRef ref) { 299 if (mCertificatesEntryRefs == null) { 300 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 301 } 302 mCertificatesEntryRefs.add(ref); 303 return this; 304 } 305 306 public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) { 307 if (mCertificatesEntryRefs == null) { 308 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 309 } 310 mCertificatesEntryRefs.addAll(refs); 311 return this; 312 } 313 314 private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() { 315 if (mCertificatesEntryRefs != null) { 316 return mCertificatesEntryRefs; 317 } 318 if (mParentBuilder != null) { 319 return mParentBuilder.getEffectiveCertificatesEntryRefs(); 320 } 321 return Collections.<CertificatesEntryRef>emptyList(); 322 } 323 324 public boolean hasCertificatesEntryRefs() { 325 return mCertificatesEntryRefs != null; 326 } 327 328 List<CertificatesEntryRef> getCertificatesEntryRefs() { 329 return mCertificatesEntryRefs; 330 } 331 332 Builder setCertificateTransparencyVerificationRequired(boolean required) { 333 mCertificateTransparencyVerificationRequired = required; 334 mCertificateTransparencyVerificationRequiredSet = true; 335 return this; 336 } 337 338 private boolean getCertificateTransparencyVerificationRequired() { 339 if (mCertificateTransparencyVerificationRequiredSet) { 340 return mCertificateTransparencyVerificationRequired; 341 } 342 if (mParentBuilder != null) { 343 return mParentBuilder.getCertificateTransparencyVerificationRequired(); 344 } 345 return DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; 346 } 347 348 public NetworkSecurityConfig build() { 349 boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); 350 boolean hstsEnforced = getEffectiveHstsEnforced(); 351 boolean certificateTransparencyVerificationRequired = 352 getCertificateTransparencyVerificationRequired(); 353 PinSet pinSet = getEffectivePinSet(); 354 List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs(); 355 return new NetworkSecurityConfig( 356 cleartextPermitted, 357 hstsEnforced, 358 certificateTransparencyVerificationRequired, 359 pinSet, 360 entryRefs); 361 } 362 } 363 } 364