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 isCertificateTransparencyVerificationRequired()115 public boolean isCertificateTransparencyVerificationRequired() { 116 return mCertificateTransparencyVerificationRequired; 117 } 118 getPins()119 public PinSet getPins() { 120 return mPins; 121 } 122 getTrustManager()123 public NetworkSecurityTrustManager getTrustManager() { 124 synchronized(mTrustManagerLock) { 125 if (mTrustManager == null) { 126 mTrustManager = new NetworkSecurityTrustManager(this); 127 } 128 return mTrustManager; 129 } 130 } 131 132 /** @hide */ findTrustAnchorBySubjectAndPublicKey(X509Certificate cert)133 public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { 134 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 135 TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); 136 if (anchor != null) { 137 return anchor; 138 } 139 } 140 return null; 141 } 142 143 /** @hide */ findTrustAnchorByIssuerAndSignature(X509Certificate cert)144 public TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate cert) { 145 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 146 TrustAnchor anchor = ref.findByIssuerAndSignature(cert); 147 if (anchor != null) { 148 return anchor; 149 } 150 } 151 return null; 152 } 153 154 /** @hide */ findAllCertificatesByIssuerAndSignature(X509Certificate cert)155 public Set<X509Certificate> findAllCertificatesByIssuerAndSignature(X509Certificate cert) { 156 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 157 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 158 certs.addAll(ref.findAllCertificatesByIssuerAndSignature(cert)); 159 } 160 return certs; 161 } 162 handleTrustStorageUpdate()163 public void handleTrustStorageUpdate() { 164 synchronized (mAnchorsLock) { 165 mAnchors = null; 166 for (CertificatesEntryRef ref : mCertificatesEntryRefs) { 167 ref.handleTrustStorageUpdate(); 168 } 169 } 170 getTrustManager().handleTrustStorageUpdate(); 171 } 172 173 /** 174 * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. 175 * 176 * <p> 177 * The default configuration has the following properties: 178 * <ol> 179 * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic 180 * is allowed by default.</li> 181 * <li>Cleartext traffic is not permitted for ephemeral apps.</li> 182 * <li>HSTS is not enforced.</li> 183 * <li>No certificate pinning is used.</li> 184 * <li>The system certificate store is trusted for connections.</li> 185 * <li>If the application targets API level 23 (Android M) or lower then the user certificate 186 * store is trusted by default as well for non-privileged applications.</li> 187 * <li>Privileged applications do not trust the user certificate store on Android P and higher. 188 * </li> 189 * </ol> 190 * 191 * @hide 192 */ getDefaultBuilder(ApplicationInfo info)193 public static Builder getDefaultBuilder(ApplicationInfo info) { 194 // System certificate store, does not bypass static pins, does not disable CT. 195 CertificatesEntryRef systemRef = new CertificatesEntryRef( 196 SystemCertificateSource.getInstance(), false, false); 197 Builder builder = new Builder() 198 .setHstsEnforced(DEFAULT_HSTS_ENFORCED) 199 .addCertificatesEntryRef(systemRef); 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. CT is disabled. 207 builder.addCertificatesEntryRef( 208 new CertificatesEntryRef(UserCertificateSource.getInstance(), false, true)); 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 // CT verification has not been set explicitly. Before deferring to 343 // the parent, check if any of the CertificatesEntryRef requires it 344 // to be disabled (i.e., user store or inline certificate). 345 if (hasCertificatesEntryRefs()) { 346 for (CertificatesEntryRef ref : getCertificatesEntryRefs()) { 347 if (ref.disableCT()) { 348 return false; 349 } 350 } 351 } 352 if (mParentBuilder != null) { 353 return mParentBuilder.getCertificateTransparencyVerificationRequired(); 354 } 355 return DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; 356 } 357 358 public NetworkSecurityConfig build() { 359 boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); 360 boolean hstsEnforced = getEffectiveHstsEnforced(); 361 boolean certificateTransparencyVerificationRequired = 362 getCertificateTransparencyVerificationRequired(); 363 PinSet pinSet = getEffectivePinSet(); 364 List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs(); 365 return new NetworkSecurityConfig( 366 cleartextPermitted, 367 hstsEnforced, 368 certificateTransparencyVerificationRequired, 369 pinSet, 370 entryRefs); 371 } 372 } 373 } 374