Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for managed certificates #1202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 90 additions & 11 deletions platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -268,6 +279,7 @@ public Date getCreationDate(String alias) {

public Set<String> aliases() {
Set<String> result = new HashSet<String>();
addAliases(result, PREFIX_MANAGED, managedDir);
addAliases(result, PREFIX_USER, addedDir);
addAliases(result, PREFIX_SYSTEM, systemDir);
return result;
Expand All @@ -292,6 +304,21 @@ private void addAliases(Set<String> result, String prefix, File dir) {
}
}

public Set<String> allManagedAliases() {
Set<String> result = new HashSet<String>();
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<String> allSystemAliases() {
Set<String> result = new HashSet<String>();
String[] files = systemDir.list();
Expand Down Expand Up @@ -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();
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -445,6 +495,10 @@ public boolean match(X509Certificate ca) {
}
};
X500Principal issuer = c.getIssuerX500Principal();
Set<X509Certificate> managedCerts = findCertSet(managedDir, issuer, selector);
if (managedCerts != null) {
issuers = managedCerts;
}
Set<X509Certificate> userAddedCerts = findCertSet(addedDir, issuer, selector);
if (userAddedCerts != null) {
issuers = userAddedCerts;
Expand Down Expand Up @@ -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");
}
Expand All @@ -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.
Expand Down Expand Up @@ -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();
Expand All @@ -678,22 +747,32 @@ 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('.');
if (dotIndex == -1) {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -418,21 +419,23 @@ 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");
setupStore();
}

private void setupStore() {
dirManaged.mkdirs();
dirSystem.mkdirs();
cleanStore();
createStore();
}

private void createStore() {
System.setProperty("system.certs.enabled", mApexCertsEnabled);
store = new TrustedCertificateStore(dirSystem, dirAdded, dirDeleted);
store = new TrustedCertificateStore(dirManaged, dirSystem, dirAdded, dirDeleted);
}

@After
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
}
Expand Down Expand Up @@ -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());
Expand All @@ -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());
Expand Down Expand Up @@ -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));
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand All @@ -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());

Expand Down Expand Up @@ -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 {
Expand Down