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