XmlConfigTests.java revision 5a1078f40dd511901c33ccf78be6e2d5081d6637
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.Context;
20import android.test.AndroidTestCase;
21import android.test.MoreAsserts;
22import android.util.ArraySet;
23import android.util.Pair;
24import java.io.IOException;
25import java.net.Socket;
26import java.net.URL;
27import java.security.KeyStore;
28import java.security.Provider;
29import java.security.Security;
30import java.security.cert.X509Certificate;
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.Set;
34import javax.net.ssl.HttpsURLConnection;
35import javax.net.ssl.SSLContext;
36import javax.net.ssl.SSLHandshakeException;
37import javax.net.ssl.TrustManager;
38import javax.net.ssl.TrustManagerFactory;
39
40public class XmlConfigTests extends AndroidTestCase {
41
42    private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
43
44    public void testEmptyConfigFile() throws Exception {
45        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
46        ApplicationConfig appConfig = new ApplicationConfig(source);
47        assertFalse(appConfig.hasPerDomainConfigs());
48        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
49        assertNotNull(config);
50        // Check defaults.
51        assertTrue(config.isCleartextTrafficPermitted());
52        assertFalse(config.isHstsEnforced());
53        assertFalse(config.getTrustAnchors().isEmpty());
54        PinSet pinSet = config.getPins();
55        assertTrue(pinSet.pins.isEmpty());
56        // Try some connections.
57        SSLContext context = TestUtils.getSSLContext(source);
58        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
59        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
60        TestUtils.assertUrlConnectionSucceeds(context, "google.com", 443);
61    }
62
63    public void testEmptyAnchors() throws Exception {
64        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
65        ApplicationConfig appConfig = new ApplicationConfig(source);
66        assertFalse(appConfig.hasPerDomainConfigs());
67        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
68        assertNotNull(config);
69        // Check defaults.
70        assertTrue(config.isCleartextTrafficPermitted());
71        assertFalse(config.isHstsEnforced());
72        assertTrue(config.getTrustAnchors().isEmpty());
73        PinSet pinSet = config.getPins();
74        assertTrue(pinSet.pins.isEmpty());
75        SSLContext context = TestUtils.getSSLContext(source);
76        TestUtils.assertConnectionFails(context, "android.com", 443);
77        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
78        TestUtils.assertUrlConnectionFails(context, "google.com", 443);
79    }
80
81    public void testBasicDomainConfig() throws Exception {
82        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
83        ApplicationConfig appConfig = new ApplicationConfig(source);
84        assertTrue(appConfig.hasPerDomainConfigs());
85        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
86        assertNotNull(config);
87        // Check defaults.
88        assertTrue(config.isCleartextTrafficPermitted());
89        assertFalse(config.isHstsEnforced());
90        assertTrue(config.getTrustAnchors().isEmpty());
91        PinSet pinSet = config.getPins();
92        assertTrue(pinSet.pins.isEmpty());
93        // Check android.com.
94        config = appConfig.getConfigForHostname("android.com");
95        assertTrue(config.isCleartextTrafficPermitted());
96        assertFalse(config.isHstsEnforced());
97        assertFalse(config.getTrustAnchors().isEmpty());
98        pinSet = config.getPins();
99        assertTrue(pinSet.pins.isEmpty());
100        // Try connections.
101        SSLContext context = TestUtils.getSSLContext(source);
102        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
103        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
104        TestUtils.assertUrlConnectionFails(context, "google.com", 443);
105        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
106    }
107
108    public void testBasicPinning() throws Exception {
109        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
110        ApplicationConfig appConfig = new ApplicationConfig(source);
111        assertTrue(appConfig.hasPerDomainConfigs());
112        // Check android.com.
113        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
114        PinSet pinSet = config.getPins();
115        assertFalse(pinSet.pins.isEmpty());
116        // Try connections.
117        SSLContext context = TestUtils.getSSLContext(source);
118        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
119        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
120        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
121    }
122
123    public void testExpiredPin() throws Exception {
124        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
125        ApplicationConfig appConfig = new ApplicationConfig(source);
126        assertTrue(appConfig.hasPerDomainConfigs());
127        // Check android.com.
128        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
129        PinSet pinSet = config.getPins();
130        assertFalse(pinSet.pins.isEmpty());
131        // Try connections.
132        SSLContext context = TestUtils.getSSLContext(source);
133        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
134        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
135    }
136
137    public void testOverridesPins() throws Exception {
138        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
139        ApplicationConfig appConfig = new ApplicationConfig(source);
140        assertTrue(appConfig.hasPerDomainConfigs());
141        // Check android.com.
142        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
143        PinSet pinSet = config.getPins();
144        assertFalse(pinSet.pins.isEmpty());
145        // Try connections.
146        SSLContext context = TestUtils.getSSLContext(source);
147        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
148        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
149    }
150
151    public void testBadPin() throws Exception {
152        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
153        ApplicationConfig appConfig = new ApplicationConfig(source);
154        assertTrue(appConfig.hasPerDomainConfigs());
155        // Check android.com.
156        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
157        PinSet pinSet = config.getPins();
158        assertFalse(pinSet.pins.isEmpty());
159        // Try connections.
160        SSLContext context = TestUtils.getSSLContext(source);
161        TestUtils.assertConnectionFails(context, "android.com", 443);
162        TestUtils.assertUrlConnectionFails(context, "android.com", 443);
163        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
164    }
165
166    public void testMultipleDomains() throws Exception {
167        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
168        ApplicationConfig appConfig = new ApplicationConfig(source);
169        assertTrue(appConfig.hasPerDomainConfigs());
170        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
171        assertTrue(config.isCleartextTrafficPermitted());
172        assertFalse(config.isHstsEnforced());
173        assertFalse(config.getTrustAnchors().isEmpty());
174        PinSet pinSet = config.getPins();
175        assertTrue(pinSet.pins.isEmpty());
176        // Both android.com and google.com should use the same config
177        NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
178        assertEquals(config, other);
179        // Try connections.
180        SSLContext context = TestUtils.getSSLContext(source);
181        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
182        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
183        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
184        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
185    }
186
187    public void testMultipleDomainConfigs() throws Exception {
188        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
189        ApplicationConfig appConfig = new ApplicationConfig(source);
190        assertTrue(appConfig.hasPerDomainConfigs());
191        // Should be two different config objects
192        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
193        NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
194        MoreAsserts.assertNotEqual(config, other);
195        // Try connections.
196        SSLContext context = TestUtils.getSSLContext(source);
197        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
198        TestUtils.assertConnectionSucceeds(context, "google.com", 443);
199        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
200    }
201
202    public void testIncludeSubdomains() throws Exception {
203        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
204        ApplicationConfig appConfig = new ApplicationConfig(source);
205        assertTrue(appConfig.hasPerDomainConfigs());
206        // Try connections.
207        SSLContext context = TestUtils.getSSLContext(source);
208        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
209        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
210        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
211        TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
212        TestUtils.assertConnectionFails(context, "google.com", 443);
213    }
214
215    public void testAttributes() throws Exception {
216        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
217        ApplicationConfig appConfig = new ApplicationConfig(source);
218        assertFalse(appConfig.hasPerDomainConfigs());
219        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
220        assertTrue(config.isHstsEnforced());
221        assertFalse(config.isCleartextTrafficPermitted());
222    }
223
224    public void testResourcePemCertificateSource() throws Exception {
225        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
226        ApplicationConfig appConfig = new ApplicationConfig(source);
227        // Check android.com.
228        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
229        assertTrue(config.isCleartextTrafficPermitted());
230        assertFalse(config.isHstsEnforced());
231        assertEquals(2, config.getTrustAnchors().size());
232        // Try connections.
233        SSLContext context = TestUtils.getSSLContext(source);
234        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
235        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
236        TestUtils.assertUrlConnectionFails(context, "google.com", 443);
237        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
238    }
239
240    public void testResourceDerCertificateSource() throws Exception {
241        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
242        ApplicationConfig appConfig = new ApplicationConfig(source);
243        // Check android.com.
244        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
245        assertTrue(config.isCleartextTrafficPermitted());
246        assertFalse(config.isHstsEnforced());
247        assertEquals(2, config.getTrustAnchors().size());
248        // Try connections.
249        SSLContext context = TestUtils.getSSLContext(source);
250        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
251        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
252        TestUtils.assertUrlConnectionFails(context, "google.com", 443);
253        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
254    }
255
256    public void testNestedDomainConfigs() throws Exception {
257        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
258        ApplicationConfig appConfig = new ApplicationConfig(source);
259        assertTrue(appConfig.hasPerDomainConfigs());
260        NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
261        NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
262        MoreAsserts.assertNotEqual(parent, child);
263        MoreAsserts.assertEmpty(parent.getPins().pins);
264        MoreAsserts.assertNotEmpty(child.getPins().pins);
265        // Check that the child inherited the cleartext value and anchors.
266        assertFalse(child.isCleartextTrafficPermitted());
267        MoreAsserts.assertNotEmpty(child.getTrustAnchors());
268        // Test connections.
269        SSLContext context = TestUtils.getSSLContext(source);
270        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
271        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
272    }
273
274    public void testNestedDomainConfigsOverride() throws Exception {
275        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
276        ApplicationConfig appConfig = new ApplicationConfig(source);
277        assertTrue(appConfig.hasPerDomainConfigs());
278        NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
279        NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
280        MoreAsserts.assertNotEqual(parent, child);
281        assertTrue(parent.isCleartextTrafficPermitted());
282        assertFalse(child.isCleartextTrafficPermitted());
283    }
284
285    public void testDebugOverridesDisabled() throws Exception {
286        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
287        ApplicationConfig appConfig = new ApplicationConfig(source);
288        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
289        Set<TrustAnchor> anchors = config.getTrustAnchors();
290        MoreAsserts.assertEmpty(anchors);
291        SSLContext context = TestUtils.getSSLContext(source);
292        TestUtils.assertConnectionFails(context, "android.com", 443);
293        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
294    }
295
296    public void testBasicDebugOverrides() throws Exception {
297        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
298        ApplicationConfig appConfig = new ApplicationConfig(source);
299        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
300        Set<TrustAnchor> anchors = config.getTrustAnchors();
301        MoreAsserts.assertNotEmpty(anchors);
302        for (TrustAnchor anchor : anchors) {
303            assertTrue(anchor.overridesPins);
304        }
305        SSLContext context = TestUtils.getSSLContext(source);
306        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
307        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
308    }
309
310    public void testDebugOverridesWithDomain() throws Exception {
311        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
312        ApplicationConfig appConfig = new ApplicationConfig(source);
313        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
314        Set<TrustAnchor> anchors = config.getTrustAnchors();
315        boolean foundDebugCA = false;
316        for (TrustAnchor anchor : anchors) {
317            if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
318                foundDebugCA = true;
319                assertTrue(anchor.overridesPins);
320            }
321        }
322        assertTrue(foundDebugCA);
323        SSLContext context = TestUtils.getSSLContext(source);
324        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
325        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
326    }
327
328    public void testDebugInherit() throws Exception {
329        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
330        ApplicationConfig appConfig = new ApplicationConfig(source);
331        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
332        Set<TrustAnchor> anchors = config.getTrustAnchors();
333        boolean foundDebugCA = false;
334        for (TrustAnchor anchor : anchors) {
335            if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
336                foundDebugCA = true;
337                assertTrue(anchor.overridesPins);
338            }
339        }
340        assertTrue(foundDebugCA);
341        assertTrue(anchors.size() > 1);
342        SSLContext context = TestUtils.getSSLContext(source);
343        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
344        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
345    }
346
347    private void testBadConfig(int configId) throws Exception {
348        try {
349            XmlConfigSource source = new XmlConfigSource(getContext(), configId);
350            ApplicationConfig appConfig = new ApplicationConfig(source);
351            appConfig.getConfigForHostname("android.com");
352            fail("Bad config " + getContext().getResources().getResourceName(configId)
353                    + " did not fail to parse");
354        } catch (RuntimeException e) {
355            MoreAsserts.assertAssignableFrom(XmlConfigSource.ParserException.class,
356                    e.getCause());
357        }
358    }
359
360    public void testBadConfig0() throws Exception {
361        testBadConfig(R.xml.bad_config0);
362    }
363
364    public void testBadConfig1() throws Exception {
365        testBadConfig(R.xml.bad_config1);
366    }
367
368    public void testBadConfig2() throws Exception {
369        testBadConfig(R.xml.bad_config2);
370    }
371
372    public void testBadConfig3() throws Exception {
373        testBadConfig(R.xml.bad_config3);
374    }
375
376    public void testBadConfig4() throws Exception {
377        testBadConfig(R.xml.bad_config4);
378    }
379
380    public void testBadConfig5() throws Exception {
381        testBadConfig(R.xml.bad_config4);
382    }
383
384    public void testTrustManagerKeystore() throws Exception {
385        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
386        ApplicationConfig appConfig = new ApplicationConfig(source);
387        Provider provider = new NetworkSecurityConfigProvider();
388        TrustManagerFactory tmf =
389                TrustManagerFactory.getInstance("PKIX", provider);
390        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
391        keystore.load(null);
392        int i = 0;
393        for (X509Certificate cert : SystemCertificateSource.getInstance().getCertificates()) {
394            keystore.setEntry(String.valueOf(i),
395                    new KeyStore.TrustedCertificateEntry(cert),
396                    null);
397            i++;
398        }
399        tmf.init(keystore);
400        TrustManager[] tms = tmf.getTrustManagers();
401        SSLContext context = SSLContext.getInstance("TLS");
402        context.init(null, tms, null);
403        TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
404    }
405}
406