1/*
2 * Copyright (C) 2011 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 org.conscrypt;
18
19import java.io.File;
20import java.io.FileWriter;
21import java.security.cert.Certificate;
22import java.security.cert.CertificateException;
23import java.security.cert.X509Certificate;
24import java.security.KeyStore;
25import java.security.MessageDigest;
26import java.security.Principal;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
30import javax.net.ssl.SSLPeerUnverifiedException;
31import javax.net.ssl.SSLSession;
32import javax.net.ssl.SSLSessionContext;
33import javax.net.ssl.TrustManagerFactory;
34import javax.net.ssl.X509TrustManager;
35import junit.framework.TestCase;
36import libcore.java.security.TestKeyStore;
37
38public class TrustManagerImplTest extends TestCase {
39
40    private List<File> tmpFiles = new ArrayList<File>();
41
42    private String getFingerprint(X509Certificate cert) throws Exception {
43        MessageDigest dgst = MessageDigest.getInstance("SHA512");
44        byte[] encoded = cert.getPublicKey().getEncoded();
45        byte[] fingerprint = dgst.digest(encoded);
46        return Hex.bytesToHexString(fingerprint);
47    }
48
49    private String writeTmpPinFile(String text) throws Exception {
50        File tmp = File.createTempFile("pins", null);
51        FileWriter fstream = new FileWriter(tmp);
52        fstream.write(text);
53        fstream.close();
54        tmpFiles.add(tmp);
55        return tmp.getPath();
56    }
57
58    @Override
59    public void tearDown() throws Exception {
60        try {
61            for (File f : tmpFiles) {
62                f.delete();
63            }
64            tmpFiles.clear();
65        } finally {
66            super.tearDown();
67        }
68    }
69
70    /**
71     * Ensure that our non-standard behavior of learning to trust new
72     * intermediate CAs does not regress. http://b/3404902
73     */
74    public void testLearnIntermediate() throws Exception {
75        // chain3 should be server/intermediate/root
76        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
77        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
78        X509Certificate root = chain3[2];
79        X509Certificate intermediate = chain3[1];
80        X509Certificate server = chain3[0];
81        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
82        X509Certificate[] chain1 =  new X509Certificate[] { server };
83
84        // Normal behavior
85        assertValid(chain3,   trustManager(root));
86        assertValid(chain2,   trustManager(root));
87        assertInvalid(chain1, trustManager(root));
88        assertValid(chain3,   trustManager(intermediate));
89        assertValid(chain2,   trustManager(intermediate));
90        assertValid(chain1,   trustManager(intermediate));
91        assertValid(chain3,   trustManager(server));
92        assertValid(chain2,   trustManager(server));
93        assertValid(chain1,   trustManager(server));
94
95        // non-standard behavior
96        X509TrustManager tm = trustManager(root);
97        // fail on short chain with only root trusted
98        assertInvalid(chain1, tm);
99        // succeed on longer chain, learn intermediate
100        assertValid(chain2, tm);
101        // now we can validate the short chain
102        assertValid(chain1, tm);
103    }
104
105    // We should ignore duplicate cruft in the certificate chain
106    // See https://code.google.com/p/android/issues/detail?id=52295 http://b/8313312
107    public void testDuplicateInChain() throws Exception {
108        // chain3 should be server/intermediate/root
109        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
110        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
111        X509Certificate root = chain3[2];
112        X509Certificate intermediate = chain3[1];
113        X509Certificate server = chain3[0];
114
115        X509Certificate[] chain4 = new X509Certificate[] { server, intermediate,
116                                                           server, intermediate
117        };
118        assertValid(chain4, trustManager(root));
119    }
120
121    public void testGetFullChain() throws Exception {
122        // build the trust manager
123        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
124        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
125        X509Certificate root = chain3[2];
126        X509TrustManager tm = trustManager(root);
127
128        // build the chains we'll use for testing
129        X509Certificate intermediate = chain3[1];
130        X509Certificate server = chain3[0];
131        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
132        X509Certificate[] chain1 =  new X509Certificate[] { server };
133
134        assertTrue(tm instanceof TrustManagerImpl);
135        TrustManagerImpl tmi = (TrustManagerImpl) tm;
136        List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", new MySSLSession(
137                "purple.com"));
138        assertEquals(Arrays.asList(chain3), certs);
139        certs = tmi.checkServerTrusted(chain1, "RSA", new MySSLSession("purple.com"));
140        assertEquals(Arrays.asList(chain3), certs);
141    }
142
143    public void testCertPinning() throws Exception {
144        // chain3 should be server/intermediate/root
145        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
146        X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain();
147        X509Certificate root = chain3[2];
148        X509Certificate intermediate = chain3[1];
149        X509Certificate server = chain3[0];
150        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
151        X509Certificate[] chain1 =  new X509Certificate[] { server };
152
153        // test without a hostname, expecting failure
154        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null);
155        // test without a hostname, expecting success
156        assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3);
157        // test an unpinned hostname that should fail
158        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com");
159        // test an unpinned hostname that should succeed
160        assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3);
161        // test a pinned hostname that should fail
162        assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com");
163        // test a pinned hostname that should succeed
164        assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com",
165                          chain2);
166        // test a pinned hostname that chains to user installed that should succeed
167        assertValidPinned(chain2, trustManagerUserInstalled(
168            (X509Certificate)TestKeyStore.getIntermediateCa2().getPrivateKey("RSA", "RSA")
169                .getCertificateChain()[1], intermediate, "gugle.com", server), "gugle.com",
170                chain2, true);
171    }
172
173    private X509TrustManager trustManager(X509Certificate ca) throws Exception {
174        KeyStore keyStore = TestKeyStore.createKeyStore();
175        keyStore.setCertificateEntry("alias", ca);
176
177        return new TrustManagerImpl(keyStore);
178    }
179
180    private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin)
181                                          throws Exception {
182        // build the cert pin manager
183        CertPinManager cm = certManager(hostname, pin);
184        // insert it into the trust manager
185        KeyStore keyStore = TestKeyStore.createKeyStore();
186        keyStore.setCertificateEntry("alias", ca);
187        return new TrustManagerImpl(keyStore, cm);
188    }
189
190    private TrustManagerImpl trustManagerUserInstalled(
191        X509Certificate caKeyStore, X509Certificate caUserStore, String hostname,
192        X509Certificate pin) throws Exception {
193        // build the cert pin manager
194        CertPinManager cm = certManager(hostname, pin);
195
196        // install at least one cert in the store (requirement)
197        KeyStore keyStore = TestKeyStore.createKeyStore();
198        keyStore.setCertificateEntry("alias", caKeyStore);
199
200        // install a cert into the user installed store
201        final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
202        final File DIR_TEST = new File(DIR_TEMP, "test");
203        final File system = new File(DIR_TEST, "system-test");
204        final File added = new File(DIR_TEST, "added-test");
205        final File deleted = new File(DIR_TEST, "deleted-test");
206
207        TrustedCertificateStore tcs = new TrustedCertificateStore(system, added, deleted);
208        added.mkdirs();
209        tcs.installCertificate(caUserStore);
210        return new TrustManagerImpl(keyStore, cm, tcs);
211    }
212
213    private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception {
214        String pinString = "";
215        if (pin != null) {
216            pinString = hostname + "=true|" + getFingerprint(pin);
217        }
218        // write it to a pinfile
219        String path = writeTmpPinFile(pinString);
220        // build the certpinmanager
221        return new CertPinManager(path, new TrustedCertificateStore());
222    }
223
224    private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception {
225        if (tm instanceof TrustManagerImpl) {
226            TrustManagerImpl tmi = (TrustManagerImpl) tm;
227            tmi.checkServerTrusted(chain, "RSA");
228        }
229        tm.checkServerTrusted(chain, "RSA");
230    }
231
232    private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
233                                   X509Certificate[] fullChain) throws Exception {
234        assertValidPinned(chain, tm, hostname, fullChain, false);
235    }
236
237    private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
238                                   X509Certificate[] fullChain, boolean expectUserInstalled)
239                                   throws Exception {
240        if (tm instanceof TrustManagerImpl) {
241            TrustManagerImpl tmi = (TrustManagerImpl) tm;
242            List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA",
243                    new MySSLSession(hostname));
244            assertEquals(checkedChain, Arrays.asList(fullChain));
245            boolean chainContainsUserInstalled = false;
246            for (X509Certificate cert : checkedChain) {
247                if (tmi.isUserAddedCertificate(cert)) {
248                    chainContainsUserInstalled = true;
249                }
250            }
251            assertEquals(expectUserInstalled, chainContainsUserInstalled);
252        }
253        tm.checkServerTrusted(chain, "RSA");
254    }
255
256    private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) {
257        try {
258            tm.checkClientTrusted(chain, "RSA");
259            fail();
260        } catch (CertificateException expected) {
261        }
262        try {
263            tm.checkServerTrusted(chain, "RSA");
264            fail();
265        } catch (CertificateException expected) {
266        }
267    }
268
269    private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname)
270                                     throws Exception {
271        assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl);
272        try {
273            TrustManagerImpl tmi = (TrustManagerImpl) tm;
274            tmi.checkServerTrusted(chain, "RSA", new MySSLSession(hostname));
275            fail();
276        } catch (CertificateException expected) {
277        }
278    }
279
280    private class MySSLSession implements SSLSession {
281        private final String hostname;
282
283        public MySSLSession(String hostname) {
284            this.hostname = hostname;
285        }
286
287        @Override
288        public int getApplicationBufferSize() {
289            throw new UnsupportedOperationException();
290        }
291
292        @Override
293        public String getCipherSuite() {
294            throw new UnsupportedOperationException();
295        }
296
297        @Override
298        public long getCreationTime() {
299            throw new UnsupportedOperationException();
300        }
301
302        @Override
303        public byte[] getId() {
304            throw new UnsupportedOperationException();
305        }
306
307        @Override
308        public long getLastAccessedTime() {
309            throw new UnsupportedOperationException();
310        }
311
312        @Override
313        public Certificate[] getLocalCertificates() {
314            throw new UnsupportedOperationException();
315        }
316
317        @Override
318        public Principal getLocalPrincipal() {
319            throw new UnsupportedOperationException();
320        }
321
322        @Override
323        public int getPacketBufferSize() {
324            throw new UnsupportedOperationException();
325        }
326
327        @Override
328        public javax.security.cert.X509Certificate[] getPeerCertificateChain()
329                throws SSLPeerUnverifiedException {
330            throw new UnsupportedOperationException();
331        }
332
333        @Override
334        public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
335            throw new UnsupportedOperationException();
336        }
337
338        @Override
339        public String getPeerHost() {
340            return hostname;
341        }
342
343        @Override
344        public int getPeerPort() {
345            throw new UnsupportedOperationException();
346        }
347
348        @Override
349        public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
350            throw new UnsupportedOperationException();
351        }
352
353        @Override
354        public String getProtocol() {
355            throw new UnsupportedOperationException();
356        }
357
358        @Override
359        public SSLSessionContext getSessionContext() {
360            throw new UnsupportedOperationException();
361        }
362
363        @Override
364        public Object getValue(String name) {
365            throw new UnsupportedOperationException();
366        }
367
368        @Override
369        public String[] getValueNames() {
370            throw new UnsupportedOperationException();
371        }
372
373        @Override
374        public void invalidate() {
375            throw new UnsupportedOperationException();
376        }
377
378        @Override
379        public boolean isValid() {
380            throw new UnsupportedOperationException();
381        }
382
383        @Override
384        public void putValue(String name, Object value) {
385            throw new UnsupportedOperationException();
386        }
387
388        @Override
389        public void removeValue(String name) {
390            throw new UnsupportedOperationException();
391        }
392    }
393}
394