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