1 /*
2  * Copyright (C) 2020 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 com.android.keychain.tests;
18 
19 import static com.android.keychain.KeyChainActivity.CertificateParametersFilter;
20 import static com.google.common.truth.Truth.assertThat;
21 import static org.junit.Assert.fail;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.when;
24 
25 import android.platform.test.annotations.LargeTest;
26 import android.security.Credentials;
27 import android.security.KeyStore;
28 import android.util.Base64;
29 import androidx.test.runner.AndroidJUnit4;
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStreamWriter;
35 import java.io.Writer;
36 import java.nio.charset.StandardCharsets;
37 import java.security.cert.Certificate;
38 import java.security.cert.CertificateEncodingException;
39 import java.security.cert.CertificateException;
40 import java.security.cert.CertificateFactory;
41 import java.security.cert.X509Certificate;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.concurrent.CancellationException;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.TimeoutException;
47 import javax.security.auth.x500.X500Principal;
48 import org.bouncycastle.util.io.pem.PemObject;
49 import org.bouncycastle.util.io.pem.PemWriter;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 @LargeTest
55 @RunWith(AndroidJUnit4.class)
56 public final class KeyChainActivityTest {
57     // Generated with:
58     // openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650
59     //   -out root_ca_certificate.pem
60     private static final String ROOT_CA_CERT_RSA =
61             "MIIDazCCAlOgAwIBAgIUWQjj+9olDNtdjcSLzK2RpxI9j6UwDQYJKoZIhvcNAQELBQAwRTELMAkG" +
62             "A1UEBhMCVUsxCzAJBgNVBAgMAk5BMQ8wDQYDVQQHDAZMb25kb24xGDAWBgNVBAoMD0FuZHJvaWQg" +
63             "VGVzdCBDQTAeFw0yMDAzMTIxMTUxNDJaFw0zMDAzMTAxMTUxNDJaMEUxCzAJBgNVBAYTAlVLMQsw" +
64             "CQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMRgwFgYDVQQKDA9BbmRyb2lkIFRlc3QgQ0EwggEi" +
65             "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGbsQO+LqPMr5Nr4Lq0B7C0th93phohSY6hb2w" +
66             "MmZs3MRwamlw8FS64KEgmszX5lnyLNqRs91FOFNuq4f2A+TYQhawi9D2bHB7z2ishDM3SxNAqwQl" +
67             "LzVNBJw7DAtimajy3VvXoprescFbsOZx8wPGGb2xMKqAXg4Yw9F6te4Y4BSIiwCWtammiSR8Ev0B" +
68             "lcnMBrWmSZ4yYF+UgNgNiD/TVrTtRmzQlRhBo5n4F61SGeAxb5p0NRRGmAXKtx358HiLANzZSCiM" +
69             "UE5IrgDvW8AKPn5InuYS1G1K2wG5ar1eanQahimtaIEugQxhqG0+/OiKKq2LGRiBpwV1OomXHNFr" +
70             "AgMBAAGjUzBRMB0GA1UdDgQWBBRrxYWzKZpCHDi/NK4keXIU5iGukzAfBgNVHSMEGDAWgBRrxYWz" +
71             "KZpCHDi/NK4keXIU5iGukzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBJTjUY" +
72             "rfi3adJFpF2IFkSnavPRxi+NX6wxfKgQvcScO7sV18gAMG7r2NhjsVeScZ48mxsNkj99lHaVoNm6" +
73             "c+sWmcb3LO7WCqmAgfJcHeQ5VuluwNoJWo+SGuKbh6/yRejNeQFf++uaEwXP3yNydwKJyQDyDwoG" +
74             "vx0jvy8glkVl3fr6u0lGQqmubGU5Q1X6QyA0zJ/sSWBVorLCgk6KvPABQJhjoij+g/GOB1h4g6fb" +
75             "bQ3xnek6TGwjQ1bB7rQlqBF7iP/9iUtuuDf0cR8LwMr1Z2OUEMDjRHQCZQJ3APc0kW1ewJ8nqQ+m" +
76             "NsUKFRuThYtE/OFsV/TfXwMXbc2rMug+";
77 
78     // Generated with:
79     // openssl genrsa  -out intermediate_key.pem 2048
80     // openssl req -new -key intermediate_key.pem -out intermediate_ca.csr
81     // openssl x509 -req -days 3650 -in intermediate_ca.csr -CA root_ca_certificate.pem
82     //   -CAkey key.pem -CAcreateserial -out intermediate_ca.pem
83     private static final String INTERMEDIATE_CA_CERT_RSA =
84             "MIIDHjCCAgYCFHVrA0CEKcoc/C8BqKap3a1m0x8CMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYT" +
85             "AlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMRgwFgYDVQQKDA9BbmRyb2lkIFRlc3Qg" +
86             "Q0EwHhcNMjAwMzEyMTE1NzUwWhcNMzAwMzEwMTE1NzUwWjBSMQswCQYDVQQGEwJVSzELMAkGA1UE" +
87             "CAwCTkExDzANBgNVBAcMBkxvbmRvbjElMCMGA1UECgwcQW5kcm9pZCBJbnRlcm1lZGlhdGUgVGVz" +
88             "dCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPlEIzApeTyKzQWTvv25z/KVEbsr" +
89             "alrcto7mX56HV1VQ53cGqi4I7dso3cvpg0CYfcZ+mZh6Evkd+njkcc7Dh/nI0KBJIzGZuo2LB+0r" +
90             "qT0RfvI/Xv7CqLO9KOjWJ3+HK3EhSXGvnLvSTsQD1LnE9HXKVdhdOUgLFjbcZrzH62mvTRAO6nhg" +
91             "agWTzprTXOX8okaMJtJl9QGMG63Z/m5DONPPrgASW6X6wksGjyorEaakQTUGuPimapP5mk+Y31Se" +
92             "pLDDumqRavLT2CpfjHfFq0iDmnnJjG5nz6oKlirhg9JjxHwuKm5jIdsO4dIgi1fJ8Goz2ODG6R6E" +
93             "6CjitsQjoMMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdud6PahLSmDLOTr9t0Jqq1HKpeRsjSqn" +
94             "JTpd/GkNUrxcctKLTzpAruMq6en8OfcSEa2s3HUMJ1LVMoO9pp4aQaadH7626Q4uqHbNGHWngVNX" +
95             "lfBioxrH2QJ2wuKBjUipEGWaM3LY0wqNuBFd5qAVuBwQtZ1x/XH7/Y4l38Y5EGGEi4jSw8eCqiiQ" +
96             "2UKItmK8byl3/T5SVgAMbFYz1WJN37EgETEcEgPlosSQ4pha6fVB3Oz6mSfzjGXqHKpHBPUn/N2d" +
97             "Z2kxJG8IuwhhhyemhqJdCfOxT2WpemgLQQCCgqtM9O89peWL8AJUVT9cF9KySOvh/P9lTtSf5bJf" +
98             "sAzfSg==";
99 
100     // Generated with:
101     // openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr
102     // openssl x509 -req -days 3650 -in server.csr -CA intermediate_ca.pem
103     //   -CAkey intermediate_key.pem -CAcreateserial -out server.pem
104     private static final String LEAF_SERVER_CERT_RSA =
105             "MIIDIjCCAgoCFGNstHCN7uzPtFlxnbu2FQ3+sc7rMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYT" +
106             "AlVLMQswCQYDVQQIDAJOQTEPMA0GA1UEBwwGTG9uZG9uMSUwIwYDVQQKDBxBbmRyb2lkIEludGVy" +
107             "bWVkaWF0ZSBUZXN0IENBMB4XDTIwMDMxMjEyMTQwMloXDTMwMDMxMDEyMTQwMlowSTELMAkGA1UE" +
108             "BhMCVUsxCzAJBgNVBAgMAk5BMQ8wDQYDVQQHDAZMb25kb24xHDAaBgNVBAoME0FuZHJvaWQgU2Vy" +
109             "dmVyIFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCBdt0uV01nG2ULMThfnEx" +
110             "CDoVvOSJJLcqKSMsoRUcEYvvqA70x7O2BR6+FZLilr09qAyK1ygyTh+Q6NcBxiB137khrygd/R3S" +
111             "U9nNWI6Xj882EospCiPaewR3j49F6F45PvviAacx7v0mZpU1dVMP7ZvhJWb1AB675/379EFztYyU" +
112             "ghM5ub8zuAgBuvH/O/dJpnmKUmO43n/qSHDM+Q+oDmLAkWD4gd3SOBZKcLgTyO/pD0pghT8m2t0t" +
113             "9X/3KX+tQhif1pAemCOqvxr/HHjlLWxY+QWmcRIAMzTg6+h7sNSKAwwfulMomzMwzkV5sJAYUJOm" +
114             "suhhyAXCNX5rWuqXAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACdFD/uV4iT4jg3/rEtszPHkyP4b" +
115             "zHaYJYpExoWNbJIz7djhycptdM7wjSoesWQMfpJz95aNWoVHrW85DSdGT+7HwZEsW1zUx3KkXURA" +
116             "CdlbVBn1CS4vm0Xk7Rr3LayfhqdFALQVItvBr+LkJPiG/R1jQySp1qaw+NrwukFoepukeZxHH1bF" +
117             "9zjGCLwOcfuRB9g8Gm45wRgoSUTDKbD1SMSLuPyllKeHLKE+chhYjm51Evy2xR0DLm0yaFmeyPPM" +
118             "KQaZJmtkeAzk2uYYb1HsVDlnEoDoXpTKKN1j39qckpCHxF0X0KqbN7D0grDWIDIee5mnSrg3+Xq5" +
119             "aCEDTUn6uU4=";
120 
121     private static final X500Principal LEAF_SUBJECT =
122             new X500Principal("O=Android Server Test, L=London, ST=NA, C=UK");
123 
124     private static final X500Principal INTERMEDIATE_SUBJECT =
125             new X500Principal("O=Android Intermediate Test CA, L=London, ST=NA, C=UK");
126 
127     private static final X500Principal ROOT_SUBJECT =
128             new X500Principal("O=Android Test CA, L=London, ST=NA, C=UK");
129 
130     private byte[] mLeafRsaCertificate;
131     private byte[] mIntermediateRsaCertificate;
132     private byte[] mRootRsaCertificate;
133 
134     @Before
setUp()135     public void setUp() {
136         mLeafRsaCertificate = Base64.decode(LEAF_SERVER_CERT_RSA, Base64.DEFAULT);
137         mIntermediateRsaCertificate = Base64.decode(INTERMEDIATE_CA_CERT_RSA, Base64.DEFAULT);
138         mRootRsaCertificate = Base64.decode(ROOT_CA_CERT_RSA, Base64.DEFAULT);
139     }
140 
141     @Test
testCertificateParametersFilter_filtersByIntermediateIssuer()142     public void testCertificateParametersFilter_filtersByIntermediateIssuer()
143             throws InterruptedException, ExecutionException, CancellationException,
144             TimeoutException, IOException, CertificateEncodingException {
145         KeyStore keyStore = prepareKeyStoreWithLongChainCertificates();
146 
147         assertThat(createCheckerForIssuer(keyStore, ROOT_SUBJECT)
148                 .shouldPresentCertificate("client")).isTrue();
149 
150         assertThat(createCheckerForIssuer(keyStore, INTERMEDIATE_SUBJECT)
151                 .shouldPresentCertificate("client")).isTrue();
152 
153         assertThat(createCheckerForIssuer(keyStore, LEAF_SUBJECT)
154                 .shouldPresentCertificate("client")).isFalse();
155     }
156 
157     // Return a KeyStore instance that has both a client certificate as well as a certificate
158     // chain associated with it.
prepareKeyStoreWithLongChainCertificates()159     private KeyStore prepareKeyStoreWithLongChainCertificates()
160             throws IOException, CertificateEncodingException {
161         KeyStore keyStore = mock(KeyStore.class);
162         when(keyStore.get(Credentials.USER_CERTIFICATE + "client")).thenReturn(mLeafRsaCertificate);
163         Certificate[] intermediates = new Certificate[] {
164             parseCertificate(mRootRsaCertificate), parseCertificate(mIntermediateRsaCertificate)};
165         byte[] intermediatesPem = convertToPem(intermediates);
166         assertThat(intermediatesPem).isNotNull();
167         when(keyStore.get(Credentials.CA_CERTIFICATE + "client")).thenReturn(intermediatesPem);
168 
169         return keyStore;
170     }
171 
172     // Create a CertificateParametersFilter instance that has the specified issuer as a requested
173     // issuer.
createCheckerForIssuer( KeyStore keyStore, X500Principal issuer)174     private static CertificateParametersFilter createCheckerForIssuer(
175             KeyStore keyStore, X500Principal issuer) {
176         return new CertificateParametersFilter(
177                 keyStore, new String[] {},
178                 new ArrayList<byte[]>(Arrays.asList(issuer.getEncoded())));
179     }
180 
parseCertificate(byte[] certificateBytes)181     private static X509Certificate parseCertificate(byte[] certificateBytes) {
182         InputStream in = new ByteArrayInputStream(certificateBytes);
183         try {
184             CertificateFactory cf = CertificateFactory.getInstance("X.509");
185             return (X509Certificate)cf.generateCertificate(in);
186         } catch (CertificateException e) {
187             fail(String.format("Could not parse certificate: %s", e));
188             return null;
189         }
190     }
191 
192     // Copied from android.security.Credentials, as that is a framework class.
convertToPem(Certificate... objects)193     public static byte[] convertToPem(Certificate... objects)
194             throws IOException, CertificateEncodingException {
195         ByteArrayOutputStream bao = new ByteArrayOutputStream();
196         Writer writer = new OutputStreamWriter(bao, StandardCharsets.US_ASCII);
197         PemWriter pw = new PemWriter(writer);
198         for (Certificate o : objects) {
199             pw.writeObject(new PemObject("CERTIFICATE", o.getEncoded()));
200         }
201         pw.close();
202         return bao.toByteArray();
203     }
204 }
205