1/*
2 * Copyright (C) 2012 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.X509Certificate;
22import java.security.KeyStore;
23import java.security.MessageDigest;
24import java.security.NoSuchAlgorithmException;
25import java.util.ArrayList;
26import java.util.List;
27import junit.framework.TestCase;
28import libcore.java.security.TestKeyStore;
29
30public class CertPinManagerTest extends TestCase {
31
32    private X509Certificate[] chain;
33    private List<X509Certificate> shortChain;
34    private List<X509Certificate> longChain;
35    private String shortPin;
36    private String longPin;
37    private List<File> tmpFiles = new ArrayList<File>();
38
39    private String writeTmpPinFile(String text) throws Exception {
40        File tmp = File.createTempFile("pins", null);
41        FileWriter fstream = new FileWriter(tmp);
42        fstream.write(text);
43        fstream.close();
44        tmpFiles.add(tmp);
45        return tmp.getPath();
46    }
47
48    private static String getFingerprint(X509Certificate cert) throws NoSuchAlgorithmException {
49        MessageDigest dgst = MessageDigest.getInstance("SHA512");
50        byte[] encoded = cert.getPublicKey().getEncoded();
51        byte[] fingerprint = dgst.digest(encoded);
52        return IntegralToString.bytesToHexString(fingerprint, false);
53    }
54
55    @Override
56    public void setUp() throws Exception {
57        super.setUp();
58        // build some valid chains
59        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
60        chain = (X509Certificate[]) pke.getCertificateChain();
61        X509Certificate root = chain[2];
62        X509Certificate server = chain[0];
63
64        // build the short and long chains
65        shortChain = new ArrayList<X509Certificate>();
66        shortChain.add(root);
67        longChain = new ArrayList<X509Certificate>();
68        longChain.add(server);
69
70        // we'll use the root as the pin for the short entry and the server as the pin for the long
71        shortPin = getFingerprint(root);
72        longPin = getFingerprint(server);
73    }
74
75    @Override
76    public void tearDown() throws Exception {
77        try {
78            for (File f : tmpFiles) {
79                f.delete();
80            }
81            tmpFiles.clear();
82        } finally {
83            super.tearDown();
84        }
85    }
86
87    public void testPinFileMaximumLookup() throws Exception {
88
89        // Hostnames to match
90        String longHostname = "android.clients.google.com";
91        String shortHostname = "android.google.com";
92
93        // Write a pinfile with two entries, one longer than the other.
94        // NOTE: "shortChain", "longChain", "shortPin", and "longPin"
95        // does not have any bearing on the test. It's simply used to
96        // distinguish the following pin entries.
97        String shortHostnameEntry = "*.google.com=true|" + shortPin;
98        String longHostnameEntry = "*.clients.google.com=true|" + longPin;
99
100        // create the pinFile
101        String path = writeTmpPinFile(shortHostnameEntry + "\n" + longHostnameEntry);
102        CertPinManager pf = new CertPinManager(path, new TrustedCertificateStore());
103
104        assertFalse("Short entry should NOT match longer hostname",
105                pf.isChainValid(longHostname, shortChain));
106        assertFalse("Long entry should NOT match shorter hostname",
107                pf.isChainValid(shortHostname, longChain));
108        assertTrue("Short entry should match short hostname",
109                pf.isChainValid(shortHostname, shortChain));
110        assertTrue("Long entry should match long name",
111                pf.isChainValid(longHostname, longChain));
112    }
113
114    public void testPinEntryMalformedEntry() throws Exception {
115        // set up the pinEntry with a bogus entry
116        String entry = "*.google.com=";
117        try {
118            new PinListEntry(entry, new TrustedCertificateStore());
119            fail("Accepted an empty pin list entry.");
120        } catch (PinEntryException expected) {
121        }
122    }
123
124    public void testPinEntryNull() throws Exception {
125        // set up the pinEntry with a bogus entry
126        String entry = null;
127        try {
128            new PinListEntry(entry, new TrustedCertificateStore());
129            fail("Accepted a basically wholly bogus entry.");
130        } catch (NullPointerException expected) {
131        }
132    }
133
134    public void testPinEntryEmpty() throws Exception {
135        // set up the pinEntry with a bogus entry
136        try {
137            new PinListEntry("", new TrustedCertificateStore());
138            fail("Accepted an empty entry.");
139        } catch (PinEntryException expected) {
140        }
141    }
142
143    public void testPinEntryPinFailure() throws Exception {
144        // write a pinfile with two entries, one longer than the other
145        String shortEntry = "*.google.com=true|" + shortPin;
146
147        // set up the pinEntry with a pinlist that doesn't match what we'll give it
148        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
149        assertTrue("Not enforcing!", e.getEnforcing());
150        // verify that it doesn't accept
151        boolean retval = e.isChainValid(longChain);
152        assertFalse("Accepted an incorrect pinning, this is very bad", retval);
153    }
154
155    public void testPinEntryPinSuccess() throws Exception {
156        // write a pinfile with two entries, one longer than the other
157        String shortEntry = "*.google.com=true|" + shortPin;
158
159        // set up the pinEntry with a pinlist that matches what we'll give it
160        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
161        assertTrue("Not enforcing!", e.getEnforcing());
162        // verify that it accepts
163        boolean retval = e.isChainValid(shortChain);
164        assertTrue("Failed on a correct pinning, this is very bad", retval);
165    }
166
167    public void testPinEntryNonEnforcing() throws Exception {
168        // write a pinfile with two entries, one longer than the other
169        String shortEntry = "*.google.com=false|" + shortPin;
170
171        // set up the pinEntry with a pinlist that matches what we'll give it
172        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
173        assertFalse("Enforcing!", e.getEnforcing());
174        // verify that it accepts
175        boolean retval = e.isChainValid(shortChain);
176        assertTrue("Failed on an unenforced pinning, this is bad-ish", retval);
177    }
178}
179