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.app.Activity;
20import android.os.Build;
21import android.test.ActivityUnitTestCase;
22import android.util.ArraySet;
23import android.util.Pair;
24import java.io.ByteArrayInputStream;
25import java.io.IOException;
26import java.net.Socket;
27import java.net.URL;
28import java.security.cert.Certificate;
29import java.security.cert.CertificateFactory;
30import java.security.cert.X509Certificate;
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.HashSet;
34import java.util.Set;
35import javax.net.ssl.HttpsURLConnection;
36import javax.net.ssl.SSLContext;
37import javax.net.ssl.SSLHandshakeException;
38import javax.net.ssl.TrustManager;
39
40import com.android.org.conscrypt.TrustedCertificateStore;
41
42public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
43
44    public NetworkSecurityConfigTests() {
45        super(Activity.class);
46    }
47
48    // SHA-256 of the G2 intermediate CA for android.com (as of 10/2015).
49    private static final byte[] G2_SPKI_SHA256
50            = hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776");
51
52    private static final byte[] TEST_CA_BYTES
53            = hexToBytes(
54                    "3082036130820249a003020102020900bd54597d6750ea62300d06092a86"
55                    + "4886f70d01010b05003047310b3009060355040613025553310b30090603"
56                    + "5504080c0243413110300e060355040a0c07416e64726f69643119301706"
57                    + "035504030c104e53436f6e6669672054657374204341301e170d31363032"
58                    + "32343030313130325a170d3136303332353030313130325a3047310b3009"
59                    + "060355040613025553310b300906035504080c0243413110300e06035504"
60                    + "0a0c07416e64726f69643119301706035504030c104e53436f6e66696720"
61                    + "5465737420434130820122300d06092a864886f70d01010105000382010f"
62                    + "003082010a0282010100e15ce8fd5794029841e760d68d6e0159c9c67630"
63                    + "089775bc728d83dae7e29e23fe5f6e113b789f4c5b22f052300ec6d5faa5"
64                    + "724432e7bac96682792ef6e9617c939c4329dce8788cbdf3a11b621fac9e"
65                    + "2edbec2d7e5e07296bbb544b89263137a6a31573a2362e05ca8ff9c886bf"
66                    + "52df4ff93c45475145a40a83f2670e23669220a5a4bf2c6860edb78d3022"
67                    + "192fb5dc5e8c118f70870f89da292dfe522751462f020ed556653c8b07f8"
68                    + "89712a6e8196c457a637439e3073d7d917ab55aa51a146826367f7b5922a"
69                    + "64fb2f95099de21eb98341fa76faa79ffbda123fe5b8adc614b16174e8b0"
70                    + "dfdac2bbc4d526d2487ad2b009d53996ec23ffbd732112efa66b02030100"
71                    + "01a350304e301d0603551d0e04160414f66e1a95486c879edd60a5756bc2"
72                    + "f1f4677e128e301f0603551d23041830168014f66e1a95486c879edd60a5"
73                    + "756bc2f1f4677e128e300c0603551d13040530030101ff300d06092a8648"
74                    + "86f70d01010b05000382010100d2856130dccae24e5f8901900d94bc642f"
75                    + "85466ab7cfa1066399077a168cd4b56603a9e2af9d2e58aec13101e338a4"
76                    + "8e95e9c7a84d7991f0d381d4965eaada1b80fbbd8277445f449babe64f53"
77                    + "ba625387460b592a1a97b14b8251115e6610350021a6e716ae22b905f8d4"
78                    + "eae24e668e71b12ab51fd2f2bb600e074487dec720c3db14dbca504844b6"
79                    + "933bb0248283ea95464747689c37d706d4839c7d0e9bd86abf98ddce5d36"
80                    + "8b38bfe5062353e28d5be378827fade1caa6bba3df9cd9ebf83d839eae52"
81                    + "780181f31973f15f982686ba6d899f7b644fd1f26c8ebb99f4c986faaf4c"
82                    + "1b9e3d9d391943ce3fb9fa2e631bd66b8ef3d47fd85acf09ea3a30f15f");
83
84    private static final X509Certificate TEST_CA_CERT;
85
86    static {
87        try {
88            CertificateFactory factory = CertificateFactory.getInstance("X.509");
89            Certificate cert = factory.generateCertificate(new ByteArrayInputStream(TEST_CA_BYTES));
90            TEST_CA_CERT = (X509Certificate) cert;
91        } catch (Exception e) {
92            throw new RuntimeException(e);
93        }
94    }
95
96
97    private static byte[] hexToBytes(String s) {
98        int len = s.length();
99        byte[] data = new byte[len / 2];
100        for (int i = 0; i < len; i += 2) {
101            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
102                    s.charAt(i + 1), 16));
103        }
104        return data;
105    }
106
107
108    /**
109     * Return a NetworkSecurityConfig that has an empty TrustAnchor set. This should always cause a
110     * SSLHandshakeException when used for a connection.
111     */
112    private NetworkSecurityConfig getEmptyConfig() {
113        return new NetworkSecurityConfig.Builder().build();
114    }
115
116    private NetworkSecurityConfig getSystemStoreConfig() {
117        return new NetworkSecurityConfig.Builder()
118                .addCertificatesEntryRef(
119                        new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
120                .build();
121    }
122
123    public void testEmptyConfig() throws Exception {
124        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
125                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
126        ConfigSource testSource =
127                new TestConfigSource(domainMap, getEmptyConfig());
128        SSLContext context = TestUtils.getSSLContext(testSource);
129        TestUtils.assertConnectionFails(context, "android.com", 443);
130    }
131
132    public void testEmptyPerNetworkSecurityConfig() throws Exception {
133        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
134                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
135        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
136                new Domain("android.com", true), getEmptyConfig()));
137        NetworkSecurityConfig defaultConfig = getSystemStoreConfig();
138        SSLContext context = TestUtils.getSSLContext(new TestConfigSource(domainMap, defaultConfig));
139        TestUtils.assertConnectionFails(context, "android.com", 443);
140        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
141    }
142
143    public void testBadPin() throws Exception {
144        ArraySet<Pin> pins = new ArraySet<Pin>();
145        pins.add(new Pin("SHA-256", new byte[0]));
146        NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
147                .setPinSet(new PinSet(pins, Long.MAX_VALUE))
148                .addCertificatesEntryRef(
149                        new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
150                .build();
151        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
152                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
153        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
154                new Domain("android.com", true), domain));
155        SSLContext context
156                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
157        TestUtils.assertConnectionFails(context, "android.com", 443);
158        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
159    }
160
161    public void testGoodPin() throws Exception {
162        ArraySet<Pin> pins = new ArraySet<Pin>();
163        pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
164        NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
165                .setPinSet(new PinSet(pins, Long.MAX_VALUE))
166                .addCertificatesEntryRef(
167                        new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
168                .build();
169        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
170                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
171        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
172                new Domain("android.com", true), domain));
173        SSLContext context
174                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
175        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
176        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
177    }
178
179    public void testOverridePins() throws Exception {
180        // Use a bad pin + granting the system CA store the ability to override pins.
181        ArraySet<Pin> pins = new ArraySet<Pin>();
182        pins.add(new Pin("SHA-256", new byte[0]));
183        NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
184                .setPinSet(new PinSet(pins, Long.MAX_VALUE))
185                .addCertificatesEntryRef(
186                        new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
187                .build();
188        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
189                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
190        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
191                new Domain("android.com", true), domain));
192        SSLContext context
193                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
194        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
195    }
196
197    public void testMostSpecificNetworkSecurityConfig() throws Exception {
198        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
199                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
200        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
201                new Domain("android.com", true), getEmptyConfig()));
202        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
203                new Domain("developer.android.com", false), getSystemStoreConfig()));
204        SSLContext context
205                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
206        TestUtils.assertConnectionFails(context, "android.com", 443);
207        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
208    }
209
210    public void testSubdomainIncluded() throws Exception {
211        // First try connecting to a subdomain of a domain entry that includes subdomains.
212        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
213                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
214        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
215                new Domain("android.com", true), getSystemStoreConfig()));
216        SSLContext context
217                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
218        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
219        // Now try without including subdomains.
220        domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
221        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
222                new Domain("android.com", false), getSystemStoreConfig()));
223        context = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
224        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
225    }
226
227    public void testConfigBuilderUsesParents() throws Exception {
228        // Check that a builder with a parent uses the parent's values when non is set.
229        NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
230                .setParent(NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N))
231                .build();
232        assert(!config.getTrustAnchors().isEmpty());
233    }
234
235    public void testConfigBuilderParentLoop() throws Exception {
236        NetworkSecurityConfig.Builder config1 = new NetworkSecurityConfig.Builder();
237        NetworkSecurityConfig.Builder config2 = new NetworkSecurityConfig.Builder();
238        config1.setParent(config2);
239        try {
240            config2.setParent(config1);
241            fail("Loop in NetworkSecurityConfig parents");
242        } catch (IllegalArgumentException expected) {
243        }
244    }
245
246    public void testWithUrlConnection() throws Exception {
247        ArraySet<Pin> pins = new ArraySet<Pin>();
248        pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
249        NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
250                .setPinSet(new PinSet(pins, Long.MAX_VALUE))
251                .addCertificatesEntryRef(
252                        new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
253                .build();
254        ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
255                = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
256        domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
257                new Domain("android.com", true), domain));
258        SSLContext context
259                = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
260        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
261        TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
262        TestUtils.assertUrlConnectionFails(context, "google.com", 443);
263    }
264
265    public void testUserAddedCaOptIn() throws Exception {
266        TrustedCertificateStore store = new TrustedCertificateStore();
267        try {
268            // Install the test CA.
269            store.installCertificate(TEST_CA_CERT);
270            NetworkSecurityConfig preNConfig =
271                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.M).build();
272            NetworkSecurityConfig nConfig =
273                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N).build();
274            Set<TrustAnchor> preNAnchors = preNConfig.getTrustAnchors();
275            Set<TrustAnchor> nAnchors = nConfig.getTrustAnchors();
276            Set<X509Certificate> preNCerts = new HashSet<X509Certificate>();
277            for (TrustAnchor anchor : preNAnchors) {
278                preNCerts.add(anchor.certificate);
279            }
280            Set<X509Certificate> nCerts = new HashSet<X509Certificate>();
281            for (TrustAnchor anchor : nAnchors) {
282                nCerts.add(anchor.certificate);
283            }
284            assertTrue(preNCerts.contains(TEST_CA_CERT));
285            assertFalse(nCerts.contains(TEST_CA_CERT));
286        } finally {
287            // Delete the user added CA. We don't know the alias so just delete them all.
288            for (String alias : store.aliases()) {
289                if (store.isUser(alias)) {
290                    try {
291                        store.deleteCertificateEntry(alias);
292                    } catch (Exception ignored) {
293                    }
294                }
295            }
296        }
297    }
298}
299