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.CertificateException;
22import java.security.cert.X509Certificate;
23import java.security.KeyStore;
24import java.security.MessageDigest;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.List;
28import javax.net.ssl.TrustManager;
29import javax.net.ssl.TrustManagerFactory;
30import javax.net.ssl.X509TrustManager;
31import junit.framework.TestCase;
32import libcore.java.security.TestKeyStore;
33
34public class TrustManagerImplTest extends TestCase {
35
36    private List<File> tmpFiles = new ArrayList<File>();
37
38    private String getFingerprint(X509Certificate cert) throws Exception {
39        MessageDigest dgst = MessageDigest.getInstance("SHA512");
40        byte[] encoded = cert.getPublicKey().getEncoded();
41        byte[] fingerprint = dgst.digest(encoded);
42        return IntegralToString.bytesToHexString(fingerprint, false);
43    }
44
45    private String writeTmpPinFile(String text) throws Exception {
46        File tmp = File.createTempFile("pins", null);
47        FileWriter fstream = new FileWriter(tmp);
48        fstream.write(text);
49        fstream.close();
50        tmpFiles.add(tmp);
51        return tmp.getPath();
52    }
53
54    @Override
55    public void tearDown() throws Exception {
56        try {
57            for (File f : tmpFiles) {
58                f.delete();
59            }
60            tmpFiles.clear();
61        } finally {
62            super.tearDown();
63        }
64    }
65
66    /**
67     * Ensure that our non-standard behavior of learning to trust new
68     * intermediate CAs does not regress. http://b/3404902
69     */
70    public void testLearnIntermediate() throws Exception {
71        // chain3 should be server/intermediate/root
72        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
73        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
74        X509Certificate root = chain3[2];
75        X509Certificate intermediate = chain3[1];
76        X509Certificate server = chain3[0];
77        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
78        X509Certificate[] chain1 =  new X509Certificate[] { server };
79
80        // Normal behavior
81        assertValid(chain3,   trustManager(root));
82        assertValid(chain2,   trustManager(root));
83        assertInvalid(chain1, trustManager(root));
84        assertValid(chain3,   trustManager(intermediate));
85        assertValid(chain2,   trustManager(intermediate));
86        assertValid(chain1,   trustManager(intermediate));
87        assertValid(chain3,   trustManager(server));
88        assertValid(chain2,   trustManager(server));
89        assertValid(chain1,   trustManager(server));
90
91        // non-standard behavior
92        X509TrustManager tm = trustManager(root);
93        // fail on short chain with only root trusted
94        assertInvalid(chain1, tm);
95        // succeed on longer chain, learn intermediate
96        assertValid(chain2, tm);
97        // now we can validate the short chain
98        assertValid(chain1, tm);
99    }
100
101    // We should ignore duplicate cruft in the certificate chain
102    // See https://code.google.com/p/android/issues/detail?id=52295 http://b/8313312
103    public void testDuplicateInChain() throws Exception {
104        // chain3 should be server/intermediate/root
105        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
106        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
107        X509Certificate root = chain3[2];
108        X509Certificate intermediate = chain3[1];
109        X509Certificate server = chain3[0];
110
111        X509Certificate[] chain4 = new X509Certificate[] { server, intermediate,
112                                                           server, intermediate
113        };
114        assertValid(chain4, trustManager(root));
115    }
116
117    public void testGetFullChain() throws Exception {
118        // build the trust manager
119        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
120        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
121        X509Certificate root = chain3[2];
122        X509TrustManager tm = trustManager(root);
123
124        // build the chains we'll use for testing
125        X509Certificate intermediate = chain3[1];
126        X509Certificate server = chain3[0];
127        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
128        X509Certificate[] chain1 =  new X509Certificate[] { server };
129
130        assertTrue(tm instanceof TrustManagerImpl);
131        TrustManagerImpl tmi = (TrustManagerImpl) tm;
132        List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com");
133        assertEquals(Arrays.asList(chain3), certs);
134        certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com");
135        assertEquals(Arrays.asList(chain3), certs);
136    }
137
138    public void testCertPinning() throws Exception {
139        // chain3 should be server/intermediate/root
140        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
141        X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain();
142        X509Certificate root = chain3[2];
143        X509Certificate intermediate = chain3[1];
144        X509Certificate server = chain3[0];
145        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
146        X509Certificate[] chain1 =  new X509Certificate[] { server };
147
148        // test without a hostname, expecting failure
149        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null);
150        // test without a hostname, expecting success
151        assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3);
152        // test an unpinned hostname that should fail
153        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com");
154        // test an unpinned hostname that should succeed
155        assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3);
156        // test a pinned hostname that should fail
157        assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com");
158        // test a pinned hostname that should succeed
159        assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com",
160                                                                                            chain2);
161    }
162
163    private X509TrustManager trustManager(X509Certificate ca) throws Exception {
164        KeyStore keyStore = TestKeyStore.createKeyStore();
165        keyStore.setCertificateEntry("alias", ca);
166
167        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
168        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
169        tmf.init(keyStore);
170        return (X509TrustManager) tmf.getTrustManagers()[0];
171    }
172
173    private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin)
174                                          throws Exception {
175        // build the cert pin manager
176        CertPinManager cm = certManager(hostname, pin);
177        // insert it into the trust manager
178        KeyStore keyStore = TestKeyStore.createKeyStore();
179        keyStore.setCertificateEntry("alias", ca);
180        return new TrustManagerImpl(keyStore, cm);
181    }
182
183    private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception {
184        String pinString = "";
185        if (pin != null) {
186            pinString = hostname + "=true|" + getFingerprint(pin);
187        }
188        // write it to a pinfile
189        String path = writeTmpPinFile(pinString);
190        // build the certpinmanager
191        return new CertPinManager(path, new TrustedCertificateStore());
192    }
193
194    private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception {
195        if (tm instanceof TrustManagerImpl) {
196            TrustManagerImpl tmi = (TrustManagerImpl) tm;
197            tmi.checkServerTrusted(chain, "RSA");
198        }
199        tm.checkServerTrusted(chain, "RSA");
200    }
201
202    private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
203                                   X509Certificate[] fullChain) throws Exception {
204        if (tm instanceof TrustManagerImpl) {
205            TrustManagerImpl tmi = (TrustManagerImpl) tm;
206            List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", hostname);
207            assertEquals(checkedChain, Arrays.asList(fullChain));
208        }
209        tm.checkServerTrusted(chain, "RSA");
210    }
211
212    private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) {
213        try {
214            tm.checkClientTrusted(chain, "RSA");
215            fail();
216        } catch (CertificateException expected) {
217        }
218        try {
219            tm.checkServerTrusted(chain, "RSA");
220            fail();
221        } catch (CertificateException expected) {
222        }
223    }
224
225    private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname)
226                                     throws Exception {
227        assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl);
228        try {
229            TrustManagerImpl tmi = (TrustManagerImpl) tm;
230            tmi.checkServerTrusted(chain, "RSA", hostname);
231            fail();
232        } catch (CertificateException expected) {
233        }
234    }
235}
236