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.apache.harmony.xnet.provider.jsse;
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        // write a pinfile with two entries, one longer than the other
90        String shortEntry = "*.google.com=true|" + shortPin;
91        String longEntry = "*.clients.google.com=true|" + longPin;
92
93        // create the pinFile
94        String path = writeTmpPinFile(shortEntry + "\n" + longEntry);
95        CertPinManager pf = new CertPinManager(path, new TrustedCertificateStore());
96
97        // verify that the shorter chain doesn't work for a name matching the longer
98        assertTrue("short chain long uri failed",
99                   pf.chainIsNotPinned("android.clients.google.com", shortChain));
100        // verify that the longer chain doesn't work for a name matching the shorter
101        assertTrue("long chain short uri failed",
102                   pf.chainIsNotPinned("android.google.com", longChain));
103        // verify that the shorter chain works for the shorter domain
104        assertTrue("short chain short uri failed",
105                   !pf.chainIsNotPinned("android.google.com", shortChain));
106        // and the same for the longer
107        assertTrue("long chain long uri failed",
108                   !pf.chainIsNotPinned("android.clients.google.com", longChain));
109    }
110
111    public void testPinEntryMalformedEntry() throws Exception {
112        // set up the pinEntry with a bogus entry
113        String entry = "*.google.com=";
114        try {
115            new PinListEntry(entry, new TrustedCertificateStore());
116            fail("Accepted an empty pin list entry.");
117        } catch (PinEntryException expected) {
118        }
119    }
120
121    public void testPinEntryNull() throws Exception {
122        // set up the pinEntry with a bogus entry
123        String entry = null;
124        try {
125            new PinListEntry(entry, new TrustedCertificateStore());
126            fail("Accepted a basically wholly bogus entry.");
127        } catch (NullPointerException expected) {
128        }
129    }
130
131    public void testPinEntryEmpty() throws Exception {
132        // set up the pinEntry with a bogus entry
133        try {
134            new PinListEntry("", new TrustedCertificateStore());
135            fail("Accepted an empty entry.");
136        } catch (PinEntryException expected) {
137        }
138    }
139
140    public void testPinEntryPinFailure() throws Exception {
141        // write a pinfile with two entries, one longer than the other
142        String shortEntry = "*.google.com=true|" + shortPin;
143
144        // set up the pinEntry with a pinlist that doesn't match what we'll give it
145        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
146        assertTrue("Not enforcing!", e.getEnforcing());
147        // verify that it doesn't accept
148        boolean retval = e.chainIsNotPinned(longChain);
149        assertTrue("Accepted an incorrect pinning, this is very bad", retval);
150    }
151
152    public void testPinEntryPinSuccess() throws Exception {
153        // write a pinfile with two entries, one longer than the other
154        String shortEntry = "*.google.com=true|" + shortPin;
155
156        // set up the pinEntry with a pinlist that matches what we'll give it
157        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
158        assertTrue("Not enforcing!", e.getEnforcing());
159        // verify that it accepts
160        boolean retval = e.chainIsNotPinned(shortChain);
161        assertTrue("Failed on a correct pinning, this is very bad", !retval);
162    }
163
164    public void testPinEntryNonEnforcing() throws Exception {
165        // write a pinfile with two entries, one longer than the other
166        String shortEntry = "*.google.com=false|" + shortPin;
167
168        // set up the pinEntry with a pinlist that matches what we'll give it
169        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
170        assertFalse("Enforcing!", e.getEnforcing());
171        // verify that it accepts
172        boolean retval = e.chainIsNotPinned(shortChain);
173        assertTrue("Failed on an unenforced pinning, this is bad-ish", !retval);
174    }
175}
176