Home | History | Annotate | Download | only in jsse
      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 
     17 package org.apache.harmony.xnet.provider.jsse;
     18 
     19 import java.io.File;
     20 import java.io.FileWriter;
     21 import java.security.cert.CertificateException;
     22 import java.security.cert.X509Certificate;
     23 import java.security.KeyStore;
     24 import java.security.MessageDigest;
     25 import java.util.ArrayList;
     26 import java.util.Arrays;
     27 import java.util.List;
     28 import javax.net.ssl.TrustManager;
     29 import javax.net.ssl.TrustManagerFactory;
     30 import javax.net.ssl.X509TrustManager;
     31 import junit.framework.TestCase;
     32 import libcore.java.security.TestKeyStore;
     33 
     34 public 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