blob: 40aad2c0ddb4d7549097ad34eec5dbfb6968cf22 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conscrypt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.security.auth.x500.X500Principal;
import junit.framework.TestCase;
import libcore.java.security.TestKeyStore;
public class TrustedCertificateStoreTest extends TestCase {
private static final Random tempFileRandom = new Random();
private final File dirTest = new File(System.getProperty("java.io.tmpdir", "."),
"cert-store-test" + tempFileRandom.nextInt());
private final File dirSystem = new File(dirTest, "system");
private final File dirAdded = new File(dirTest, "added");
private final File dirDeleted = new File(dirTest, "removed");
private static X509Certificate CA1;
private static X509Certificate CA2;
private static KeyStore.PrivateKeyEntry PRIVATE;
private static X509Certificate[] CHAIN;
private static X509Certificate CA3_WITH_CA1_SUBJECT;
private static String ALIAS_SYSTEM_CA1;
private static String ALIAS_SYSTEM_CA2;
private static String ALIAS_USER_CA1;
private static String ALIAS_USER_CA2;
private static String ALIAS_SYSTEM_CHAIN0;
private static String ALIAS_SYSTEM_CHAIN1;
private static String ALIAS_SYSTEM_CHAIN2;
private static String ALIAS_USER_CHAIN0;
private static String ALIAS_USER_CHAIN1;
private static String ALIAS_USER_CHAIN2;
private static String ALIAS_SYSTEM_CA3;
private static String ALIAS_SYSTEM_CA3_COLLISION;
private static String ALIAS_USER_CA3;
private static String ALIAS_USER_CA3_COLLISION;
private static X509Certificate CERTLOOP_EE;
private static X509Certificate CERTLOOP_CA1;
private static X509Certificate CERTLOOP_CA2;
private static String ALIAS_USER_CERTLOOP_EE;
private static String ALIAS_USER_CERTLOOP_CA1;
private static String ALIAS_USER_CERTLOOP_CA2;
private static X509Certificate MULTIPLE_ISSUERS_CA1;
private static X509Certificate MULTIPLE_ISSUERS_CA1_CROSS;
private static X509Certificate MULTIPLE_ISSUERS_CA2;
private static X509Certificate MULTIPLE_ISSUERS_EE;
private static String ALIAS_MULTIPLE_ISSUERS_CA1;
private static String ALIAS_MULTIPLE_ISSUERS_CA1_CROSS;
private static String ALIAS_MULTIPLE_ISSUERS_CA2;
private static String ALIAS_MULTIPLE_ISSUERS_EE;
private static X509Certificate getCa1() {
initCerts();
return CA1;
}
private static X509Certificate getCa2() {
initCerts();
return CA2;
}
private static KeyStore.PrivateKeyEntry getPrivate() {
initCerts();
return PRIVATE;
}
private static X509Certificate[] getChain() {
initCerts();
return CHAIN;
}
private static X509Certificate getCa3WithCa1Subject() {
initCerts();
return CA3_WITH_CA1_SUBJECT;
}
private static String getAliasSystemCa1() {
initCerts();
return ALIAS_SYSTEM_CA1;
}
private static String getAliasSystemCa2() {
initCerts();
return ALIAS_SYSTEM_CA2;
}
private static String getAliasUserCa1() {
initCerts();
return ALIAS_USER_CA1;
}
private static String getAliasUserCa2() {
initCerts();
return ALIAS_USER_CA2;
}
private static String getAliasSystemChain0() {
initCerts();
return ALIAS_SYSTEM_CHAIN0;
}
private static String getAliasSystemChain1() {
initCerts();
return ALIAS_SYSTEM_CHAIN1;
}
private static String getAliasSystemChain2() {
initCerts();
return ALIAS_SYSTEM_CHAIN2;
}
private static String getAliasUserChain0() {
initCerts();
return ALIAS_USER_CHAIN0;
}
private static String getAliasUserChain1() {
initCerts();
return ALIAS_USER_CHAIN1;
}
private static String getAliasUserChain2() {
initCerts();
return ALIAS_USER_CHAIN2;
}
private static String getAliasSystemCa3() {
initCerts();
return ALIAS_SYSTEM_CA3;
}
private static String getAliasSystemCa3Collision() {
initCerts();
return ALIAS_SYSTEM_CA3_COLLISION;
}
private static String getAliasUserCa3() {
initCerts();
return ALIAS_USER_CA3;
}
private static String getAliasUserCa3Collision() {
initCerts();
return ALIAS_USER_CA3_COLLISION;
}
private static X509Certificate getCertLoopEe() {
initCerts();
return CERTLOOP_EE;
}
private static X509Certificate getCertLoopCa1() {
initCerts();
return CERTLOOP_CA1;
}
private static X509Certificate getCertLoopCa2() {
initCerts();
return CERTLOOP_CA2;
}
private static String getAliasCertLoopEe() {
initCerts();
return ALIAS_USER_CERTLOOP_EE;
}
private static String getAliasCertLoopCa1() {
initCerts();
return ALIAS_USER_CERTLOOP_CA1;
}
private static String getAliasCertLoopCa2() {
initCerts();
return ALIAS_USER_CERTLOOP_CA2;
}
private static String getAliasMultipleIssuersCa1() {
initCerts();
return ALIAS_MULTIPLE_ISSUERS_CA1;
}
private static String getAliasMultipleIssuersCa2() {
initCerts();
return ALIAS_MULTIPLE_ISSUERS_CA2;
}
private static String getAliasMultipleIssuersCa1Cross() {
initCerts();
return ALIAS_MULTIPLE_ISSUERS_CA1_CROSS;
}
private static String getAliasMultipleIssuersEe() {
initCerts();
return ALIAS_MULTIPLE_ISSUERS_EE;
}
private static X509Certificate getMultipleIssuersCa1() {
initCerts();
return MULTIPLE_ISSUERS_CA1;
}
private static X509Certificate getMultipleIssuersCa2() {
initCerts();
return MULTIPLE_ISSUERS_CA2;
}
private static X509Certificate getMultipleIssuersCa1Cross() {
initCerts();
return MULTIPLE_ISSUERS_CA1_CROSS;
}
private static X509Certificate getMultipleIssuersEe() {
initCerts();
return MULTIPLE_ISSUERS_EE;
}
/**
* Lazily create shared test certificates.
*/
private static synchronized void initCerts() {
if (CA1 != null) {
return;
}
try {
CA1 = TestKeyStore.getClient().getRootCertificate("RSA");
CA2 = TestKeyStore.getClientCA2().getRootCertificate("RSA");
PRIVATE = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
CHAIN = (X509Certificate[]) PRIVATE.getCertificateChain();
CA3_WITH_CA1_SUBJECT = new TestKeyStore.Builder()
.aliasPrefix("unused")
.subject(CA1.getSubjectX500Principal())
.ca(true)
.build().getRootCertificate("RSA");
ALIAS_SYSTEM_CA1 = alias(false, CA1, 0);
ALIAS_SYSTEM_CA2 = alias(false, CA2, 0);
ALIAS_USER_CA1 = alias(true, CA1, 0);
ALIAS_USER_CA2 = alias(true, CA2, 0);
ALIAS_SYSTEM_CHAIN0 = alias(false, getChain()[0], 0);
ALIAS_SYSTEM_CHAIN1 = alias(false, getChain()[1], 0);
ALIAS_SYSTEM_CHAIN2 = alias(false, getChain()[2], 0);
ALIAS_USER_CHAIN0 = alias(true, getChain()[0], 0);
ALIAS_USER_CHAIN1 = alias(true, getChain()[1], 0);
ALIAS_USER_CHAIN2 = alias(true, getChain()[2], 0);
ALIAS_SYSTEM_CA3 = alias(false, CA3_WITH_CA1_SUBJECT, 0);
ALIAS_SYSTEM_CA3_COLLISION = alias(false, CA3_WITH_CA1_SUBJECT, 1);
ALIAS_USER_CA3 = alias(true, CA3_WITH_CA1_SUBJECT, 0);
ALIAS_USER_CA3_COLLISION = alias(true, CA3_WITH_CA1_SUBJECT, 1);
/*
* The construction below is to build a certificate chain that has a loop
* in it:
*
* EE ---> CA1 ---> CA2 ---+
* ^ |
* | |
* +--------------+
*/
TestKeyStore certLoopTempCa1 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("certloop-ca1")
.subject("CN=certloop-ca1")
.ca(true)
.build();
Certificate certLoopTempCaCert1 = ((TrustedCertificateEntry) certLoopTempCa1
.getEntryByAlias("certloop-ca1-public-RSA")).getTrustedCertificate();
PrivateKeyEntry certLoopCaKey1 = (PrivateKeyEntry) certLoopTempCa1
.getEntryByAlias("certloop-ca1-private-RSA");
TestKeyStore certLoopCa2 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("certloop-ca2")
.subject("CN=certloop-ca2")
.rootCa(certLoopTempCaCert1)
.signer(certLoopCaKey1)
.ca(true)
.build();
CERTLOOP_CA2 = (X509Certificate) ((TrustedCertificateEntry) certLoopCa2
.getEntryByAlias("certloop-ca2-public-RSA")).getTrustedCertificate();
ALIAS_USER_CERTLOOP_CA2 = alias(true, CERTLOOP_CA2, 0);
PrivateKeyEntry certLoopCaKey2 = (PrivateKeyEntry) certLoopCa2
.getEntryByAlias("certloop-ca2-private-RSA");
TestKeyStore certLoopCa1 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("certloop-ca1")
.subject("CN=certloop-ca1")
.privateEntry(certLoopCaKey1)
.rootCa(CERTLOOP_CA2)
.signer(certLoopCaKey2)
.ca(true)
.build();
CERTLOOP_CA1 = (X509Certificate) ((TrustedCertificateEntry) certLoopCa1
.getEntryByAlias("certloop-ca1-public-RSA")).getTrustedCertificate();
ALIAS_USER_CERTLOOP_CA1 = alias(true, CERTLOOP_CA1, 0);
TestKeyStore certLoopEe = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("certloop-ee")
.subject("CN=certloop-ee")
.rootCa(CERTLOOP_CA1)
.signer(certLoopCaKey1)
.build();
CERTLOOP_EE = (X509Certificate) ((TrustedCertificateEntry) certLoopEe
.getEntryByAlias("certloop-ee-public-RSA")).getTrustedCertificate();
ALIAS_USER_CERTLOOP_EE = alias(true, CERTLOOP_EE, 0);
/*
* The construction below creates a certificate with multiple possible issuer certs.
*
* EE ----> CA1 ---> CA2
*
* Where CA1 also exists in a self-issued form.
*/
TestKeyStore multipleIssuersCa1 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("multiple-issuers-ca1")
.subject("CN=multiple-issuers-ca1")
.ca(true)
.build();
MULTIPLE_ISSUERS_CA1 = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa1
.getEntryByAlias("multiple-issuers-ca1-public-RSA")).getTrustedCertificate();
ALIAS_MULTIPLE_ISSUERS_CA1 = alias(false, MULTIPLE_ISSUERS_CA1, 0);
PrivateKeyEntry multipleIssuersCa1Key = (PrivateKeyEntry) multipleIssuersCa1
.getEntryByAlias("multiple-issuers-ca1-private-RSA");
TestKeyStore multipleIssuersCa2 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("multiple-issuers-ca2")
.subject("CN=multiple-issuers-ca2")
.ca(true)
.build();
MULTIPLE_ISSUERS_CA2 = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa2
.getEntryByAlias("multiple-issuers-ca2-public-RSA")).getTrustedCertificate();
ALIAS_MULTIPLE_ISSUERS_CA2 = alias(false, MULTIPLE_ISSUERS_CA2, 0);
PrivateKeyEntry multipleIssuersCa2Key = (PrivateKeyEntry) multipleIssuersCa2
.getEntryByAlias("multiple-issuers-ca2-private-RSA");
TestKeyStore multipleIssuersCa1SignedByCa2 = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("multiple-issuers-ca1")
.subject("CN=multiple-issuers-ca1")
.privateEntry(multipleIssuersCa1Key)
.rootCa(MULTIPLE_ISSUERS_CA2)
.signer(multipleIssuersCa2Key)
.ca(true)
.build();
MULTIPLE_ISSUERS_CA1_CROSS =
(X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa1SignedByCa2
.getEntryByAlias("multiple-issuers-ca1-public-RSA"))
.getTrustedCertificate();
ALIAS_MULTIPLE_ISSUERS_CA1_CROSS = alias(false, MULTIPLE_ISSUERS_CA1_CROSS, 1);
TestKeyStore multipleIssuersEe = new TestKeyStore.Builder()
.keyAlgorithms("RSA")
.aliasPrefix("multiple-issuers-ee")
.subject("CN=multiple-issuers-ee")
.rootCa(MULTIPLE_ISSUERS_CA1)
.signer(multipleIssuersCa1Key)
.build();
MULTIPLE_ISSUERS_EE = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersEe
.getEntryByAlias("multiple-issuers-ee-public-RSA")).getTrustedCertificate();
ALIAS_MULTIPLE_ISSUERS_EE = alias(false, MULTIPLE_ISSUERS_EE, 0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private TrustedCertificateStore store;
@Override protected void setUp() {
setupStore();
}
private void setupStore() {
dirSystem.mkdirs();
cleanStore();
createStore();
}
private void createStore() {
store = new TrustedCertificateStore(dirSystem, dirAdded, dirDeleted);
}
@Override protected void tearDown() {
cleanStore();
}
private void cleanStore() {
for (File dir : new File[] { dirSystem, dirAdded, dirDeleted, dirTest }) {
File[] files = dir.listFiles();
if (files == null) {
continue;
}
for (File file : files) {
assertTrue("Should delete " + file.getPath(), file.delete());
}
}
store = null;
}
private void resetStore() {
cleanStore();
setupStore();
}
public void testEmptyDirectories() throws Exception {
assertEmpty();
}
public void testOneSystemOneDeleted() throws Exception {
install(getCa1(), getAliasSystemCa1());
store.deleteCertificateEntry(getAliasSystemCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
}
public void testTwoSystemTwoDeleted() throws Exception {
install(getCa1(), getAliasSystemCa1());
store.deleteCertificateEntry(getAliasSystemCa1());
install(getCa2(), getAliasSystemCa2());
store.deleteCertificateEntry(getAliasSystemCa2());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
assertDeleted(getCa2(), getAliasSystemCa2());
}
public void testPartialFileIsIgnored() throws Exception {
File file = file(getAliasSystemCa1());
file.getParentFile().mkdirs();
OutputStream os = new FileOutputStream(file);
os.write(0);
os.close();
assertTrue(file.exists());
assertEmpty();
assertTrue(file.exists());
}
private void assertEmpty() throws Exception {
try {
store.getCertificate(null);
fail();
} catch (NullPointerException expected) {
}
assertNull(store.getCertificate(""));
try {
store.getCreationDate(null);
fail();
} catch (NullPointerException expected) {
}
assertNull(store.getCreationDate(""));
Set<String> s = store.aliases();
assertNotNull(s);
assertTrue(s.isEmpty());
assertAliases();
Set<String> u = store.userAliases();
assertNotNull(u);
assertTrue(u.isEmpty());
try {
store.containsAlias(null);
fail();
} catch (NullPointerException expected) {
}
assertFalse(store.containsAlias(""));
assertNull(store.getCertificateAlias(null));
assertNull(store.getCertificateAlias(getCa1()));
try {
store.getTrustAnchor(null);
fail();
} catch (NullPointerException expected) {
}
assertNull(store.getTrustAnchor(getCa1()));
try {
store.findIssuer(null);
fail();
} catch (NullPointerException expected) {
}
assertNull(store.findIssuer(getCa1()));
try {
store.installCertificate(null);
fail();
} catch (NullPointerException expected) {
}
store.deleteCertificateEntry(null);
store.deleteCertificateEntry("");
String[] userFiles = dirAdded.list();
assertTrue(userFiles == null || userFiles.length == 0);
}
public void testTwoSystem() throws Exception {
testTwo(getCa1(), getAliasSystemCa1(),
getCa2(), getAliasSystemCa2());
}
public void testTwoUser() throws Exception {
testTwo(getCa1(), getAliasUserCa1(),
getCa2(), getAliasUserCa2());
}
public void testOneSystemOneUser() throws Exception {
testTwo(getCa1(), getAliasSystemCa1(),
getCa2(), getAliasUserCa2());
}
public void testTwoSystemSameSubject() throws Exception {
testTwo(getCa1(), getAliasSystemCa1(),
getCa3WithCa1Subject(), getAliasSystemCa3Collision());
}
public void testTwoUserSameSubject() throws Exception {
testTwo(getCa1(), getAliasUserCa1(),
getCa3WithCa1Subject(), getAliasUserCa3Collision());
store.deleteCertificateEntry(getAliasUserCa1());
assertDeleted(getCa1(), getAliasUserCa1());
assertTombstone(getAliasUserCa1());
assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3Collision());
assertAliases(getAliasUserCa3Collision());
store.deleteCertificateEntry(getAliasUserCa3Collision());
assertDeleted(getCa3WithCa1Subject(), getAliasUserCa3Collision());
assertNoTombstone(getAliasUserCa3Collision());
assertNoTombstone(getAliasUserCa1());
assertEmpty();
}
public void testOneSystemOneUserSameSubject() throws Exception {
testTwo(getCa1(), getAliasSystemCa1(),
getCa3WithCa1Subject(), getAliasUserCa3());
testTwo(getCa1(), getAliasUserCa1(),
getCa3WithCa1Subject(), getAliasSystemCa3());
}
private void testTwo(X509Certificate x1, String alias1,
X509Certificate x2, String alias2) {
install(x1, alias1);
install(x2, alias2);
assertRootCa(x1, alias1);
assertRootCa(x2, alias2);
assertAliases(alias1, alias2);
}
public void testOneSystemOneUserOneDeleted() throws Exception {
install(getCa1(), getAliasSystemCa1());
store.installCertificate(getCa2());
store.deleteCertificateEntry(getAliasSystemCa1());
assertDeleted(getCa1(), getAliasSystemCa1());
assertRootCa(getCa2(), getAliasUserCa2());
assertAliases(getAliasUserCa2());
}
public void testOneSystemOneUserOneDeletedSameSubject() throws Exception {
install(getCa1(), getAliasSystemCa1());
store.installCertificate(getCa3WithCa1Subject());
store.deleteCertificateEntry(getAliasSystemCa1());
assertDeleted(getCa1(), getAliasSystemCa1());
assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3());
assertAliases(getAliasUserCa3());
}
public void testUserMaskingSystem() throws Exception {
install(getCa1(), getAliasSystemCa1());
install(getCa1(), getAliasUserCa1());
assertMasked(getCa1(), getAliasSystemCa1());
assertRootCa(getCa1(), getAliasUserCa1());
assertAliases(getAliasSystemCa1(), getAliasUserCa1());
}
public void testChain() throws Exception {
testChain(getAliasSystemChain1(), getAliasSystemChain2());
testChain(getAliasSystemChain1(), getAliasUserChain2());
testChain(getAliasUserChain1(), getAliasSystemCa1());
testChain(getAliasUserChain1(), getAliasUserChain2());
}
private void testChain(String alias1, String alias2) throws Exception {
install(getChain()[1], alias1);
install(getChain()[2], alias2);
assertIntermediateCa(getChain()[1], alias1);
assertRootCa(getChain()[2], alias2);
assertAliases(alias1, alias2);
assertEquals(getChain()[2], store.findIssuer(getChain()[1]));
assertEquals(getChain()[1], store.findIssuer(getChain()[0]));
X509Certificate[] expected = getChain();
List<X509Certificate> actualList = store.getCertificateChain(expected[0]);
assertEquals("Generated CA list should be same length", expected.length, actualList.size());
for (int i = 0; i < expected.length; i++) {
assertEquals("Chain value should be the same for position " + i, expected[i],
actualList.get(i));
}
resetStore();
}
public void testMissingSystemDirectory() throws Exception {
cleanStore();
createStore();
assertEmpty();
}
public void testWithExistingUserDirectories() throws Exception {
dirAdded.mkdirs();
dirDeleted.mkdirs();
install(getCa1(), getAliasSystemCa1());
assertRootCa(getCa1(), getAliasSystemCa1());
assertAliases(getAliasSystemCa1());
}
public void testIsTrustAnchorWithReissuedgetCa() throws Exception {
PublicKey publicKey = getPrivate().getCertificate().getPublicKey();
PrivateKey privateKey = getPrivate().getPrivateKey();
String name = "CN=CA4";
X509Certificate ca1 = TestKeyStore.createCa(publicKey, privateKey, name);
Thread.sleep(1 * 1000); // wait to ensure CAs vary by expiration
X509Certificate ca2 = TestKeyStore.createCa(publicKey, privateKey, name);
assertFalse(ca1.equals(ca2));
String systemAlias = alias(false, ca1, 0);
install(ca1, systemAlias);
assertRootCa(ca1, systemAlias);
assertEquals(ca1, store.getTrustAnchor(ca2));
assertEquals(ca1, store.findIssuer(ca2));
resetStore();
String userAlias = alias(true, ca1, 0);
store.installCertificate(ca1);
assertRootCa(ca1, userAlias);
assertNotNull(store.getTrustAnchor(ca2));
assertEquals(ca1, store.findIssuer(ca2));
resetStore();
}
public void testInstallEmpty() throws Exception {
store.installCertificate(getCa1());
assertRootCa(getCa1(), getAliasUserCa1());
assertAliases(getAliasUserCa1());
// reinstalling should not change anything
store.installCertificate(getCa1());
assertRootCa(getCa1(), getAliasUserCa1());
assertAliases(getAliasUserCa1());
}
public void testInstallEmptySystemExists() throws Exception {
install(getCa1(), getAliasSystemCa1());
assertRootCa(getCa1(), getAliasSystemCa1());
assertAliases(getAliasSystemCa1());
// reinstalling should not affect system CA
store.installCertificate(getCa1());
assertRootCa(getCa1(), getAliasSystemCa1());
assertAliases(getAliasSystemCa1());
}
public void testInstallEmptyDeletedSystemExists() throws Exception {
install(getCa1(), getAliasSystemCa1());
store.deleteCertificateEntry(getAliasSystemCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
// installing should restore deleted system CA
store.installCertificate(getCa1());
assertRootCa(getCa1(), getAliasSystemCa1());
assertAliases(getAliasSystemCa1());
}
public void testDeleteEmpty() throws Exception {
store.deleteCertificateEntry(getAliasSystemCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
}
public void testDeleteUser() throws Exception {
store.installCertificate(getCa1());
assertRootCa(getCa1(), getAliasUserCa1());
assertAliases(getAliasUserCa1());
store.deleteCertificateEntry(getAliasUserCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasUserCa1());
assertNoTombstone(getAliasUserCa1());
}
public void testDeleteSystem() throws Exception {
install(getCa1(), getAliasSystemCa1());
assertRootCa(getCa1(), getAliasSystemCa1());
assertAliases(getAliasSystemCa1());
store.deleteCertificateEntry(getAliasSystemCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
// deleting again should not change anything
store.deleteCertificateEntry(getAliasSystemCa1());
assertEmpty();
assertDeleted(getCa1(), getAliasSystemCa1());
}
public void testGetLoopedCert() throws Exception {
install(getCertLoopEe(), getAliasCertLoopEe());
install(getCertLoopCa1(), getAliasCertLoopCa1());
install(getCertLoopCa2(), getAliasCertLoopCa2());
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<List<X509Certificate>> future = executor
.submit(new Callable<List<X509Certificate>>() {
@Override
public List<X509Certificate> call() throws Exception {
return store.getCertificateChain(getCertLoopEe());
}
});
executor.shutdown();
final List<X509Certificate> certs;
try {
certs = future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
fail("Could not finish building chain; possibly confused by loops");
return; // Not actually reached.
}
assertEquals(3, certs.size());
assertEquals(getCertLoopEe(), certs.get(0));
assertEquals(getCertLoopCa1(), certs.get(1));
assertEquals(getCertLoopCa2(), certs.get(2));
}
public void testIsUserAddedCertificate() throws Exception {
assertFalse(store.isUserAddedCertificate(getCa1()));
assertFalse(store.isUserAddedCertificate(getCa2()));
install(getCa1(), getAliasSystemCa1());
assertFalse(store.isUserAddedCertificate(getCa1()));
assertFalse(store.isUserAddedCertificate(getCa2()));
install(getCa1(), getAliasUserCa1());
assertTrue(store.isUserAddedCertificate(getCa1()));
assertFalse(store.isUserAddedCertificate(getCa2()));
install(getCa2(), getAliasUserCa2());
assertTrue(store.isUserAddedCertificate(getCa1()));
assertTrue(store.isUserAddedCertificate(getCa2()));
store.deleteCertificateEntry(getAliasUserCa1());
assertFalse(store.isUserAddedCertificate(getCa1()));
assertTrue(store.isUserAddedCertificate(getCa2()));
store.deleteCertificateEntry(getAliasUserCa2());
assertFalse(store.isUserAddedCertificate(getCa1()));
assertFalse(store.isUserAddedCertificate(getCa2()));
}
public void testSystemCaCertsUseCorrectFileNames() throws Exception {
TrustedCertificateStore store = new TrustedCertificateStore();
// Assert that all the certificates in the system cacerts directory are stored in files with
// expected names.
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
File dir = new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
int systemCertFileCount = 0;
for (File actualFile : listFilesNoNull(dir)) {
if (!actualFile.isFile()) {
continue;
}
systemCertFileCount++;
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(
new ByteArrayInputStream(readFully(actualFile)));
File expectedFile = store.getCertificateFile(dir, cert);
assertEquals("System certificate stored in the wrong file",
expectedFile.getAbsolutePath(), actualFile.getAbsolutePath());
// The two statements below indirectly assert that the certificate can be looked up
// from a file (hopefully the same one as the expectedFile above). As opposed to
// getCertifiacteFile above, these are the actual methods used when verifying chain of
// trust. Thus, we assert that they work as expected for all system certificates.
assertNotNull("Issuer certificate not found for system certificate " + actualFile,
store.findIssuer(cert));
assertNotNull("Trust anchor not found for system certificate " + actualFile,
store.getTrustAnchor(cert));
}
// Assert that all files corresponding to all system certs/aliases known to the store are
// present.
int systemCertAliasCount = 0;
for (String alias : store.aliases()) {
if (!TrustedCertificateStore.isSystem(alias)) {
continue;
}
systemCertAliasCount++;
// Checking that the certificate is stored in a file is extraneous given the current
// implementation of the class under test. We do it just in case the implementation
// changes.
X509Certificate cert = (X509Certificate) store.getCertificate(alias);
File expectedFile = store.getCertificateFile(dir, cert);
if (!expectedFile.isFile()) {
fail("Missing certificate file for alias " + alias
+ ": " + expectedFile.getAbsolutePath());
}
}
assertEquals("Number of system cert files and aliases doesn't match",
systemCertFileCount, systemCertAliasCount);
}
public void testMultipleIssuers() throws Exception {
Set<X509Certificate> result;
install(getMultipleIssuersCa1(), getAliasMultipleIssuersCa1());
result = store.findAllIssuers(getMultipleIssuersEe());
assertEquals("Unexpected number of issuers found", 1, result.size());
assertTrue("findAllIssuers does not contain expected issuer",
result.contains(getMultipleIssuersCa1()));
install(getMultipleIssuersCa1Cross(), getAliasMultipleIssuersCa1Cross());
result = store.findAllIssuers(getMultipleIssuersEe());
assertEquals("findAllIssuers did not return all issuers", 2, result.size());
assertTrue("findAllIssuers does not contain CA1",
result.contains(getMultipleIssuersCa1()));
assertTrue("findAllIssuers does not contain CA1 signed by CA2",
result.contains(getMultipleIssuersCa1Cross()));
}
private static File[] listFilesNoNull(File dir) {
File[] files = dir.listFiles();
return (files != null) ? files : new File[0];
}
private static byte[] readFully(File file) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[16384];
int chunkSize;
while ((chunkSize = in.read(buf)) != -1) {
out.write(buf, 0, chunkSize);
}
return out.toByteArray();
} finally {
if (in != null) {
in.close();
}
}
}
private void assertRootCa(X509Certificate x, String alias) {
assertIntermediateCa(x, alias);
assertEquals(x, store.findIssuer(x));
}
private void assertTrusted(X509Certificate x, String alias) {
assertEquals(x, store.getCertificate(alias));
assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
assertTrue(store.containsAlias(alias));
assertEquals(x, store.getTrustAnchor(x));
}
private void assertIntermediateCa(X509Certificate x, String alias) {
assertTrusted(x, alias);
assertEquals(alias, store.getCertificateAlias(x));
}
private void assertMasked(X509Certificate x, String alias) {
assertTrusted(x, alias);
assertFalse(alias.equals(store.getCertificateAlias(x)));
}
private void assertDeleted(X509Certificate x, String alias) {
assertNull(store.getCertificate(alias));
assertFalse(store.containsAlias(alias));
assertNull(store.getCertificateAlias(x));
assertNull(store.getTrustAnchor(x));
assertEquals(store.allSystemAliases().contains(alias),
store.getCertificate(alias, true) != null);
}
private void assertTombstone(String alias) {
assertTrue(TrustedCertificateStore.isUser(alias));
File file = file(alias);
assertTrue(file.exists());
assertEquals(0, file.length());
}
private void assertNoTombstone(String alias) {
assertTrue(TrustedCertificateStore.isUser(alias));
assertFalse(file(alias).exists());
}
private void assertAliases(String... aliases) {
Set<String> expected = new HashSet<String>(Arrays.asList(aliases));
Set<String> actual = new HashSet<String>();
for (String alias : store.aliases()) {
boolean system = TrustedCertificateStore.isSystem(alias);
boolean user = TrustedCertificateStore.isUser(alias);
if (system || user) {
assertEquals(system, store.allSystemAliases().contains(alias));
assertEquals(user, store.userAliases().contains(alias));
actual.add(alias);
} else {
throw new AssertionError(alias);
}
}
assertEquals(expected, actual);
}
/**
* format a certificate alias
*/
private static String alias(boolean user, X509Certificate x, int index) {
String prefix = user ? "user:" : "system:";
X500Principal subject = x.getSubjectX500Principal();
int intHash = NativeCrypto.X509_NAME_hash_old(subject);
String strHash = Hex.intToHexString(intHash, 8);
return prefix + strHash + '.' + index;
}
/**
* Install certificate under specified alias
*/
private void install(X509Certificate x, String alias) {
try {
File file = file(alias);
file.getParentFile().mkdirs();
OutputStream out = new FileOutputStream(file);
out.write(x.getEncoded());
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Compute file for an alias
*/
private File file(String alias) {
File dir;
if (TrustedCertificateStore.isSystem(alias)) {
dir = dirSystem;
} else if (TrustedCertificateStore.isUser(alias)) {
dir = dirAdded;
} else {
throw new IllegalArgumentException(alias);
}
int index = alias.lastIndexOf(":");
if (index == -1) {
throw new IllegalArgumentException(alias);
}
String filename = alias.substring(index+1);
return new File(dir, filename);
}
}