diff --git a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java index 14df3764d..a42a4fc26 100644 --- a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java +++ b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java @@ -82,9 +82,13 @@ */ @Internal public class TrustedCertificateStore implements ConscryptCertStore { + private static final String PREFIX_MANAGED = "managed:"; private static String PREFIX_SYSTEM = "system:"; private static final String PREFIX_USER = "user:"; + public static final boolean isManaged(String alias) { + return alias.startsWith(PREFIX_MANAGED); + } public static final boolean isSystem(String alias) { return alias.startsWith(PREFIX_SYSTEM); } @@ -93,6 +97,7 @@ public static final boolean isUser(String alias) { } private static class PreloadHolder { + private static File defaultCaCertsManagedDir; private static File defaultCaCertsSystemDir; private static File defaultCaCertsAddedDir; private static File defaultCaCertsDeletedDir; @@ -101,6 +106,7 @@ private static class PreloadHolder { String ANDROID_ROOT = System.getenv("ANDROID_ROOT"); String ANDROID_DATA = System.getenv("ANDROID_DATA"); File updatableDir = new File("/apex/com.android.conscrypt/cacerts"); + defaultCaCertsManagedDir = new File(ANDROID_DATA + "/misc/cacerts_managed"); if (shouldUseApex(updatableDir)) { defaultCaCertsSystemDir = updatableDir; } else { @@ -147,20 +153,23 @@ public static void setDefaultUserDirectory(File root) { PreloadHolder.defaultCaCertsDeletedDir = new File(root, "cacerts-removed"); } + private final File managedDir; private final File systemDir; private final File addedDir; private final File deletedDir; public TrustedCertificateStore() { - this(PreloadHolder.defaultCaCertsSystemDir, PreloadHolder.defaultCaCertsAddedDir, - PreloadHolder.defaultCaCertsDeletedDir); + this(PreloadHolder.defaultCaCertsManagedDir, PreloadHolder.defaultCaCertsSystemDir, + PreloadHolder.defaultCaCertsAddedDir, PreloadHolder.defaultCaCertsDeletedDir); } public TrustedCertificateStore(File baseDir) { - this(baseDir, PreloadHolder.defaultCaCertsAddedDir, PreloadHolder.defaultCaCertsDeletedDir); + this(PreloadHolder.defaultCaCertsManagedDir, baseDir, PreloadHolder.defaultCaCertsAddedDir, + PreloadHolder.defaultCaCertsDeletedDir); } - public TrustedCertificateStore(File systemDir, File addedDir, File deletedDir) { + public TrustedCertificateStore(File managedDir, File systemDir, File addedDir, File deletedDir) { + this.managedDir = managedDir; this.systemDir = systemDir; this.addedDir = addedDir; this.deletedDir = deletedDir; @@ -173,7 +182,7 @@ public Certificate getCertificate(String alias) { public Certificate getCertificate(String alias, boolean includeDeletedSystem) { File file = fileForAlias(alias); - if (file == null || (isUser(alias) && isTombstone(file))) { + if (file == null || (isUser(alias) && isTombstone(file)) || (isManaged(alias) && isTombstone(file))) { return null; } X509Certificate cert = readCertificate(file); @@ -193,6 +202,8 @@ private File fileForAlias(String alias) { File file; if (isSystem(alias)) { file = new File(systemDir, alias.substring(PREFIX_SYSTEM.length())); + } else if (isManaged(alias)) { + file = new File(managedDir, alias.substring(PREFIX_MANAGED.length())); } else if (isUser(alias)) { file = new File(addedDir, alias.substring(PREFIX_USER.length())); } else { @@ -268,6 +279,7 @@ public Date getCreationDate(String alias) { public Set aliases() { Set result = new HashSet(); + addAliases(result, PREFIX_MANAGED, managedDir); addAliases(result, PREFIX_USER, addedDir); addAliases(result, PREFIX_SYSTEM, systemDir); return result; @@ -292,6 +304,21 @@ private void addAliases(Set result, String prefix, File dir) { } } + public Set allManagedAliases() { + Set result = new HashSet(); + String[] files = managedDir.list(); + if (files == null) { + return result; + } + for (String filename : files) { + String alias = PREFIX_MANAGED + filename; + if (containsAlias(alias, true)) { + result.add(alias); + } + } + return result; + } + public Set allSystemAliases() { Set result = new HashSet(); String[] files = systemDir.list(); @@ -324,6 +351,10 @@ public String getCertificateAlias(Certificate c, boolean includeDeletedSystem) { return null; } X509Certificate x = (X509Certificate) c; + File managed = getCertificateFile(managedDir, x); + if (managed.exists()) { + return PREFIX_MANAGED + managed.getName(); + } File user = getCertificateFile(addedDir, x); if (user.exists()) { return PREFIX_USER + user.getName(); @@ -338,6 +369,14 @@ public String getCertificateAlias(Certificate c, boolean includeDeletedSystem) { return null; } + /** + * Returns true to indicate that the certificate was added by the + * device owner, false otherwise. + */ + public boolean isManagedCertificate(X509Certificate cert) { + return getCertificateFile(managedDir, cert).exists(); + } + /** * Returns true to indicate that the certificate was added by the * user, false otherwise. @@ -383,6 +422,13 @@ public boolean match(X509Certificate ca) { return ca.getPublicKey().equals(c.getPublicKey()); } }; + X509Certificate managed = findCert(managedDir, + c.getSubjectX500Principal(), + selector, + X509Certificate.class); + if (managed != null) { + return managed; + } X509Certificate user = findCert(addedDir, c.getSubjectX500Principal(), selector, @@ -419,6 +465,10 @@ public boolean match(X509Certificate ca) { } }; X500Principal issuer = c.getIssuerX500Principal(); + X509Certificate managed = findCert(managedDir, issuer, selector, X509Certificate.class); + if (managed != null) { + return managed; + } X509Certificate user = findCert(addedDir, issuer, selector, X509Certificate.class); if (user != null) { return user; @@ -445,6 +495,10 @@ public boolean match(X509Certificate ca) { } }; X500Principal issuer = c.getIssuerX500Principal(); + Set managedCerts = findCertSet(managedDir, issuer, selector); + if (managedCerts != null) { + issuers = managedCerts; + } Set userAddedCerts = findCertSet(addedDir, issuer, selector); if (userAddedCerts != null) { issuers = userAddedCerts; @@ -603,13 +657,21 @@ private File file(File dir, String hash, int index) { return new File(dir, hash + '.' + index); } + /** + * @deprecated Use {@link #installCertificate(boolean[], java.security.cert.X509Certificate)} instead. + */ + @Deprecated + public void installCertificate(X509Certificate cert) throws IOException, CertificateException { + installCertificate(false, cert); + } + /** * This non-{@code KeyStoreSpi} public interface is used by the * {@code KeyChainService} to install new CA certificates. It * silently ignores the certificate if it already exists in the * store. */ - public void installCertificate(X509Certificate cert) throws IOException, CertificateException { + public void installCertificate(boolean isManaged, X509Certificate cert) throws IOException, CertificateException { if (cert == null) { throw new NullPointerException("cert == null"); } @@ -628,6 +690,13 @@ public void installCertificate(X509Certificate cert) throws IOException, Certifi // return taking no further action. return; } + File managed = getCertificateFile(managedDir, cert); + if (isManaged) { + if (!managed.exists()) { + writeCertificate(managed, cert); + } + return; + } File user = getCertificateFile(addedDir, cert); if (user.exists()) { // we have an already installed user cert, bail. @@ -667,7 +736,7 @@ public void deleteCertificateEntry(String alias) throws IOException, Certificate writeCertificate(deleted, cert); return; } - if (isUser(alias)) { + if (isUser(alias) || isManaged(alias)) { // truncate the file to make a tombstone by opening and closing. // we need ensure that we don't leave a gap before a valid cert. new FileOutputStream(file).close(); @@ -678,7 +747,7 @@ public void deleteCertificateEntry(String alias) throws IOException, Certificate } private void removeUnnecessaryTombstones(String alias) throws IOException { - if (!isUser(alias)) { + if (!isUser(alias) && !isManaged(alias)) { throw new AssertionError(alias); } int dotIndex = alias.lastIndexOf('.'); @@ -686,14 +755,24 @@ private void removeUnnecessaryTombstones(String alias) throws IOException { throw new AssertionError(alias); } - String hash = alias.substring(PREFIX_USER.length(), dotIndex); + File dir = null; + String hash = null; + if (isUser(alias)) { + dir = addedDir; + hash = alias.substring(PREFIX_USER.length(), dotIndex); + } else if (isManaged(alias)) { + dir = managedDir; + hash = alias.substring(PREFIX_MANAGED.length(), dotIndex); + } else { + throw new AssertionError(alias); + } int lastTombstoneIndex = Integer.parseInt(alias.substring(dotIndex + 1)); - if (file(addedDir, hash, lastTombstoneIndex + 1).exists()) { + if (file(dir, hash, lastTombstoneIndex + 1).exists()) { return; } while (lastTombstoneIndex >= 0) { - File file = file(addedDir, hash, lastTombstoneIndex); + File file = file(dir, hash, lastTombstoneIndex); if (!isTombstone(file)) { break; } diff --git a/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java b/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java index 8108d4404..914cd6a57 100644 --- a/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java +++ b/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java @@ -69,6 +69,7 @@ public class TrustedCertificateStoreTest { private static final Random tempFileRandom = new Random(); private static File dirTest; + private static File dirManaged; private static File dirSystem; private static File dirAdded; private static File dirDeleted; @@ -418,6 +419,7 @@ public static Object[] data() { @Before public void setUp() throws Exception { dirTest = Files.createTempDirectory("cert-store-test").toFile(); + dirManaged = new File(dirTest, "managed"); dirSystem = new File(dirTest, "system"); dirAdded = new File(dirTest, "added"); dirDeleted = new File(dirTest, "removed"); @@ -425,6 +427,7 @@ public void setUp() throws Exception { } private void setupStore() { + dirManaged.mkdirs(); dirSystem.mkdirs(); cleanStore(); createStore(); @@ -432,7 +435,7 @@ private void setupStore() { private void createStore() { System.setProperty("system.certs.enabled", mApexCertsEnabled); - store = new TrustedCertificateStore(dirSystem, dirAdded, dirDeleted); + store = new TrustedCertificateStore(dirManaged, dirSystem, dirAdded, dirDeleted); } @After @@ -441,7 +444,7 @@ public void tearDown() { } private void cleanStore() { - for (File dir : new File[] { dirSystem, dirAdded, dirDeleted, dirTest }) { + for (File dir : new File[] { dirManaged, dirSystem, dirAdded, dirDeleted, dirTest }) { File[] files = dir.listFiles(); if (files == null) { continue; @@ -543,7 +546,7 @@ private void assertEmpty() throws Exception { assertNull(store.findIssuer(getCa1())); try { - store.installCertificate(null); + store.installCertificate(false, null); fail(); } catch (NullPointerException expected) { } @@ -617,7 +620,7 @@ private void testTwo(X509Certificate x1, String alias1, @Test public void testOneSystemOneUserOneDeleted() throws Exception { install(getCa1(), getAliasSystemCa1()); - store.installCertificate(getCa2()); + store.installCertificate(false, getCa2()); store.deleteCertificateEntry(getAliasSystemCa1()); assertDeleted(getCa1(), getAliasSystemCa1()); assertRootCa(getCa2(), getAliasUserCa2()); @@ -627,7 +630,7 @@ public void testOneSystemOneUserOneDeleted() throws Exception { @Test public void testOneSystemOneUserOneDeletedSameSubject() throws Exception { install(getCa1(), getAliasSystemCa1()); - store.installCertificate(getCa3WithCa1Subject()); + store.installCertificate(false, getCa3WithCa1Subject()); store.deleteCertificateEntry(getAliasSystemCa1()); assertDeleted(getCa1(), getAliasSystemCa1()); assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3()); @@ -705,7 +708,7 @@ public void testIsTrustAnchorWithReissuedgetCa() throws Exception { resetStore(); String userAlias = alias(true, ca1, 0); - store.installCertificate(ca1); + store.installCertificate(false, ca1); assertRootCa(ca1, userAlias); assertNotNull(store.getTrustAnchor(ca2)); assertEquals(ca1, store.findIssuer(ca2)); @@ -714,12 +717,12 @@ public void testIsTrustAnchorWithReissuedgetCa() throws Exception { @Test public void testInstallEmpty() throws Exception { - store.installCertificate(getCa1()); + store.installCertificate(false, getCa1()); assertRootCa(getCa1(), getAliasUserCa1()); assertAliases(getAliasUserCa1()); // reinstalling should not change anything - store.installCertificate(getCa1()); + store.installCertificate(false, getCa1()); assertRootCa(getCa1(), getAliasUserCa1()); assertAliases(getAliasUserCa1()); } @@ -731,7 +734,7 @@ public void testInstallEmptySystemExists() throws Exception { assertAliases(getAliasSystemCa1()); // reinstalling should not affect system CA - store.installCertificate(getCa1()); + store.installCertificate(false, getCa1()); assertRootCa(getCa1(), getAliasSystemCa1()); assertAliases(getAliasSystemCa1()); } @@ -744,7 +747,7 @@ public void testInstallEmptyDeletedSystemExists() throws Exception { assertDeleted(getCa1(), getAliasSystemCa1()); // installing should restore deleted system CA - store.installCertificate(getCa1()); + store.installCertificate(false, getCa1()); assertRootCa(getCa1(), getAliasSystemCa1()); assertAliases(getAliasSystemCa1()); } @@ -758,7 +761,7 @@ public void testDeleteEmpty() throws Exception { @Test public void testDeleteUser() throws Exception { - store.installCertificate(getCa1()); + store.installCertificate(false, getCa1()); assertRootCa(getCa1(), getAliasUserCa1()); assertAliases(getAliasUserCa1()); @@ -1035,6 +1038,8 @@ private File file(String alias) { File dir; if (TrustedCertificateStore.isSystem(alias)) { dir = dirSystem; + } else if (TrustedCertificateStore.isManaged(alias)) { + dir = dirManaged; } else if (TrustedCertificateStore.isUser(alias)) { dir = dirAdded; } else {