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