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 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>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic 168 * is allowed by default.</li> 169 * <li>Cleartext traffic is not permitted for ephemeral apps.</li> 170 * <li>HSTS is not enforced.</li> 171 * <li>No certificate pinning is used.</li> 172 * <li>The system certificate store is trusted for connections.</li> 173 * <li>If the application targets API level 23 (Android M) or lower then the user certificate 174 * store is trusted by default as well for non-privileged applications.</li> 175 * <li>Privileged applications do not trust the user certificate store on Android P and higher. 176 * </li> 177 * </ol> 178 * 179 * @hide 180 */ getDefaultBuilder(ApplicationInfo info)181 public static Builder getDefaultBuilder(ApplicationInfo info) { 182 Builder builder = new Builder() 183 .setHstsEnforced(DEFAULT_HSTS_ENFORCED) 184 // System certificate store, does not bypass static pins. 185 .addCertificatesEntryRef( 186 new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); 187 final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P 188 && !info.isInstantApp(); 189 builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); 190 // Applications targeting N and above must opt in into trusting the user added certificate 191 // store. 192 if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) { 193 // User certificate store, does not bypass static pins. 194 builder.addCertificatesEntryRef( 195 new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); 196 } 197 return builder; 198 } 199 200 /** 201 * Builder for creating {@code NetworkSecurityConfig} objects. 202 * @hide 203 */ 204 public static final class Builder { 205 private List<CertificatesEntryRef> mCertificatesEntryRefs; 206 private PinSet mPinSet; 207 private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 208 private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED; 209 private boolean mCleartextTrafficPermittedSet = false; 210 private boolean mHstsEnforcedSet = false; 211 private Builder mParentBuilder; 212 213 /** 214 * Sets the parent {@code Builder} for this {@code Builder}. 215 * The parent will be used to determine values not configured in this {@code Builder} 216 * in {@link Builder#build()}, recursively if needed. 217 */ 218 public Builder setParent(Builder parent) { 219 // Quick check to avoid adding loops. 220 Builder current = parent; 221 while (current != null) { 222 if (current == this) { 223 throw new IllegalArgumentException("Loops are not allowed in Builder parents"); 224 } 225 current = current.getParent(); 226 } 227 mParentBuilder = parent; 228 return this; 229 } 230 231 public Builder getParent() { 232 return mParentBuilder; 233 } 234 235 public Builder setPinSet(PinSet pinSet) { 236 mPinSet = pinSet; 237 return this; 238 } 239 240 private PinSet getEffectivePinSet() { 241 if (mPinSet != null) { 242 return mPinSet; 243 } 244 if (mParentBuilder != null) { 245 return mParentBuilder.getEffectivePinSet(); 246 } 247 return PinSet.EMPTY_PINSET; 248 } 249 250 public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) { 251 mCleartextTrafficPermitted = cleartextTrafficPermitted; 252 mCleartextTrafficPermittedSet = true; 253 return this; 254 } 255 256 private boolean getEffectiveCleartextTrafficPermitted() { 257 if (mCleartextTrafficPermittedSet) { 258 return mCleartextTrafficPermitted; 259 } 260 if (mParentBuilder != null) { 261 return mParentBuilder.getEffectiveCleartextTrafficPermitted(); 262 } 263 return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED; 264 } 265 266 public Builder setHstsEnforced(boolean hstsEnforced) { 267 mHstsEnforced = hstsEnforced; 268 mHstsEnforcedSet = true; 269 return this; 270 } 271 272 private boolean getEffectiveHstsEnforced() { 273 if (mHstsEnforcedSet) { 274 return mHstsEnforced; 275 } 276 if (mParentBuilder != null) { 277 return mParentBuilder.getEffectiveHstsEnforced(); 278 } 279 return DEFAULT_HSTS_ENFORCED; 280 } 281 282 public Builder addCertificatesEntryRef(CertificatesEntryRef ref) { 283 if (mCertificatesEntryRefs == null) { 284 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 285 } 286 mCertificatesEntryRefs.add(ref); 287 return this; 288 } 289 290 public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) { 291 if (mCertificatesEntryRefs == null) { 292 mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>(); 293 } 294 mCertificatesEntryRefs.addAll(refs); 295 return this; 296 } 297 298 private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() { 299 if (mCertificatesEntryRefs != null) { 300 return mCertificatesEntryRefs; 301 } 302 if (mParentBuilder != null) { 303 return mParentBuilder.getEffectiveCertificatesEntryRefs(); 304 } 305 return Collections.<CertificatesEntryRef>emptyList(); 306 } 307 308 public boolean hasCertificatesEntryRefs() { 309 return mCertificatesEntryRefs != null; 310 } 311 312 List<CertificatesEntryRef> getCertificatesEntryRefs() { 313 return mCertificatesEntryRefs; 314 } 315 316 public NetworkSecurityConfig build() { 317 boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); 318 boolean hstsEnforced = getEffectiveHstsEnforced(); 319 PinSet pinSet = getEffectivePinSet(); 320 List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs(); 321 return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs); 322 } 323 } 324 } 325