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