Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2016 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 android.security.cts;
     18 
     19 import android.content.res.AssetManager;
     20 import android.net.http.X509TrustManagerExtensions;
     21 import android.platform.test.annotations.SecurityTest;
     22 import android.test.AndroidTestCase;
     23 
     24 import java.io.File;
     25 import java.io.InputStream;
     26 import java.util.Arrays;
     27 import java.util.List;
     28 import java.security.KeyStore;
     29 import java.security.Provider;
     30 import java.security.Security;
     31 import java.security.cert.CertificateException;
     32 import java.security.cert.CertificateFactory;
     33 import java.security.cert.X509Certificate;
     34 
     35 import javax.net.ssl.TrustManager;
     36 import javax.net.ssl.TrustManagerFactory;
     37 import javax.net.ssl.X509TrustManager;
     38 
     39 /**
     40  * Test that all {@link X509TrustManager} build the correct certificate chain during
     41  * {@link X509TrustManagerExtensions#checkServerTrusted(X509Certificate[], String, String)} when
     42  * multiple possible certificate paths exist.
     43  */
     44 @SecurityTest
     45 public class X509CertChainBuildingTest extends AndroidTestCase {
     46     private static final String CERT_ASSET_DIR = "path_building";
     47 
     48     /* Certificates for tests. These are initialized in setUp.
     49      * All certificates use 2048 bit RSA keys and SHA-256 digests unless otherwise specified.
     50      * First certificate graph:
     51      *
     52      * rootA: A root CA
     53      * rootASha1: rootA but with SHA-1 as the digest algorithm.
     54      * rootB: Another root CA
     55      * leaf1: Certificate issued by rootA
     56      * rootAtoB: rootA cross signed by rootB
     57      * rootBtoA: rootB cross signed by rootA
     58      *
     59      *   [A] <-------> [B]
     60      *    |
     61      *    v
     62      * [leaf1]
     63      * Second certificate graph:
     64      *
     65      * intermediateA: Intermediate I issued by rootA
     66      * intermediateB: Intermediate I issued by rootB
     67      * leaf2: Leaf issued by I
     68      *
     69      * [A]   [B]
     70      *    \ /
     71      *    [I]
     72      *     |
     73      *     v
     74      *  [leaf2]
     75      *
     76      *  These can be generated by running cts/tools/utils/certificates.py
     77      */
     78     private X509Certificate rootA;
     79     private X509Certificate rootASha1;
     80     private X509Certificate rootB;
     81     private X509Certificate rootAtoB;
     82     private X509Certificate rootBtoA;
     83     private X509Certificate leaf1;
     84     private X509Certificate leaf2;
     85     private X509Certificate intermediateA;
     86     private X509Certificate intermediateB;
     87 
     88     @Override
     89     public void setUp() throws Exception {
     90         super.setUp();
     91         rootA = loadCertificate("a.pem");
     92         rootASha1 = loadCertificate("a_sha1.pem");
     93         rootB = loadCertificate("b.pem");
     94         leaf1 = loadCertificate("leaf1.pem");
     95         leaf2 = loadCertificate("leaf2.pem");
     96         rootAtoB = loadCertificate("a_to_b.pem");
     97         rootBtoA = loadCertificate("b_to_a.pem");
     98         intermediateA = loadCertificate("intermediate_a.pem");
     99         intermediateB = loadCertificate("intermediate_b.pem");
    100     }
    101 
    102     public void testBasicChain() throws Exception {
    103         assertExactPath(new X509Certificate[] {leaf1, rootA},
    104                 new X509Certificate[] {leaf1},
    105                 new X509Certificate[] {rootA});
    106     }
    107     public void testCrossSign() throws Exception {
    108         // First try a path that doesn't have the cross signed A to B certificate.
    109         assertNoPath(new X509Certificate[] {leaf1, rootA}, new X509Certificate[] {rootB});
    110         // Now try with one valid chain (leaf1 -> rootAtoB -> rootB).
    111         assertExactPath(new X509Certificate[] {leaf1, rootAtoB, rootB},
    112                 new X509Certificate[] {leaf1, rootAtoB},
    113                 new X509Certificate[] {rootB});
    114         // Now try with two possible chains present only one of which chains to a trusted root.
    115         assertExactPath(new X509Certificate[] {leaf1, rootAtoB, rootB},
    116                 new X509Certificate[] {leaf1, rootA, rootAtoB},
    117                 new X509Certificate[] {rootB});
    118     }
    119 
    120     public void testUntrustedLoop() throws Exception {
    121         // Verify that providing all the certificates doesn't cause the path building to get stuck
    122         // in the loop caused by the cross signed certificates.
    123         assertNoPath(new X509Certificate[] {leaf1, rootAtoB, rootBtoA, rootA, rootB},
    124                 new X509Certificate[] {});
    125     }
    126 
    127     public void testAvoidCrossSigned() throws Exception {
    128         // Check that leaf1 -> rootA is preferred over using the cross signed cert when both rootA
    129         // and rootB are trusted.
    130         assertExactPath(new X509Certificate[] {leaf1, rootA},
    131                 new X509Certificate[] {leaf1, rootAtoB},
    132                 new X509Certificate[] {rootA, rootB});
    133     }
    134 
    135     public void testSelfIssuedPreferred() throws Exception {
    136         // Check that when there are multiple possible trusted issuers that we prefer self-issued
    137         // certificates over bridge versions of the same certificate.
    138         assertExactPath(new X509Certificate[] {leaf1, rootA},
    139                 new X509Certificate[] {leaf1, rootAtoB},
    140                 new X509Certificate[] {rootA, rootAtoB, rootB});
    141     }
    142 
    143     public void testBridgeCrossing() throws Exception {
    144         // Check that when provided with leaf2, intermediateA, intermediateB, rootA that it builds
    145         // the leaf2 -> intermediateB -> B path.
    146         assertExactPath(new X509Certificate[] {leaf2, intermediateB, rootB},
    147                 new X509Certificate[] {leaf2, intermediateA, rootA, intermediateB},
    148                 new X509Certificate[] {rootB});
    149     }
    150 
    151     public void testDigestOrdering() throws Exception {
    152         // Check that leaf1 -> rootASha1 is valid
    153         assertExactPath(new X509Certificate[] {leaf1, rootASha1},
    154                 new X509Certificate[] {leaf1},
    155                 new X509Certificate[] {rootASha1});
    156         // Check that when a SHA-256 and SHA-1 are available the SHA-256 cert is used
    157         assertExactPath(new X509Certificate[] {leaf1, rootA},
    158                 new X509Certificate[] {leaf1},
    159                 new X509Certificate[] {rootASha1, rootA});
    160     }
    161 
    162     private X509Certificate loadCertificate(String file) throws Exception {
    163         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    164         AssetManager assetManager = getContext().getAssets();
    165         InputStream in = null;
    166         try {
    167             in = assetManager.open(new File(CERT_ASSET_DIR, file).toString());
    168             return (X509Certificate) certFactory.generateCertificate(in);
    169         } finally {
    170             if (in != null) {
    171                 in.close();
    172             }
    173         }
    174     }
    175 
    176     private static X509TrustManager getTrustManager(KeyStore ks, Provider p) throws Exception {
    177         TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", p);
    178         tmf.init(ks);
    179         for (TrustManager tm : tmf.getTrustManagers()) {
    180             if (tm instanceof X509TrustManager) {
    181                 return (X509TrustManager) tm;
    182             }
    183         }
    184         fail("Unable to find X509TrustManager");
    185         return null;
    186     }
    187 
    188     /**
    189      * Asserts that all PKIX TrustManagerFactory providers build the expected certificate path or
    190      * throw a {@code CertificateException} if {@code expected} is {@code null}.
    191      */
    192     private static void assertExactPath(X509Certificate[] expected, X509Certificate[] bagOfCerts,
    193             X509Certificate[] roots) throws Exception {
    194         KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    195         ks.load(null);
    196         int i = 0;
    197         for (X509Certificate root : roots) {
    198             ks.setEntry(String.valueOf(i++), new KeyStore.TrustedCertificateEntry(root), null);
    199         }
    200         Provider[] providers = Security.getProviders("TrustManagerFactory.PKIX");
    201         assertNotNull(providers);
    202         assertTrue("No providers found", providers.length != 0);
    203         for (Provider p : providers) {
    204             try {
    205                 X509TrustManager tm = getTrustManager(ks, p);
    206                 X509TrustManagerExtensions xtm = new X509TrustManagerExtensions(tm);
    207                 List<X509Certificate> result;
    208                 try {
    209                     result = xtm.checkServerTrusted(bagOfCerts, "RSA", null);
    210                 } catch (CertificateException e) {
    211                     if (expected == null) {
    212                         // Exception expected.
    213                         continue;
    214                     }
    215                     throw e;
    216                 }
    217                 if (expected == null) {
    218                     fail("checkServerTrusted expected to fail, instead returned: " + result);
    219                 }
    220                 assertEquals(Arrays.asList(expected), result);
    221             } catch (Exception e) {
    222                 throw new Exception("Failed with provider " + p, e);
    223             }
    224         }
    225     }
    226 
    227     private static void assertNoPath(X509Certificate[] bagOfCerts, X509Certificate[] roots)
    228             throws Exception {
    229         assertExactPath(null, bagOfCerts, roots);
    230     }
    231 
    232 }
    233