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