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
17package android.security.net.config;
18
19import android.content.pm.ApplicationInfo;
20import android.os.Build;
21import android.util.ArrayMap;
22import android.util.ArraySet;
23
24import java.security.cert.X509Certificate;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Collections;
28import java.util.Comparator;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32
33/**
34 * @hide
35 */
36public 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
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
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
99    public boolean isCleartextTrafficPermitted() {
100        return mCleartextTrafficPermitted;
101    }
102
103    public boolean isHstsEnforced() {
104        return mHstsEnforced;
105    }
106
107    public PinSet getPins() {
108        return mPins;
109    }
110
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 */
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 */
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 */
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
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     */
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.targetSandboxVersion < 2;
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            // Sanity 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