summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--luni/src/main/java/java/security/cert/PKIXParameters.java5
-rw-r--r--luni/src/main/java/javax/security/auth/x500/X500Principal.java10
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/IndexedPKIXParameters.java21
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java2
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/RootKeyStoreSpi.java186
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java77
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java499
-rw-r--r--luni/src/test/java/libcore/java/security/KeyStoreTest.java14
-rw-r--r--luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java51
-rw-r--r--luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java513
-rw-r--r--support/src/test/java/libcore/java/security/TestKeyStore.java526
11 files changed, 1417 insertions, 487 deletions
diff --git a/luni/src/main/java/java/security/cert/PKIXParameters.java b/luni/src/main/java/java/security/cert/PKIXParameters.java
index 7ca9443..450bdd1 100644
--- a/luni/src/main/java/java/security/cert/PKIXParameters.java
+++ b/luni/src/main/java/java/security/cert/PKIXParameters.java
@@ -68,6 +68,11 @@ public class PKIXParameters implements CertPathParameters {
private boolean policyQualifiersRejected = true;
/**
+ * @hide For use by IndexedPKIXParameters which lazily discovers TrustAnchors
+ */
+ protected PKIXParameters() {}
+
+ /**
* Creates a new {@code PKIXParameters} instance with the specified set of
* <i>trusted</i> certificate authorities.
*
diff --git a/luni/src/main/java/javax/security/auth/x500/X500Principal.java b/luni/src/main/java/javax/security/auth/x500/X500Principal.java
index f13dd4f..e6453e9 100644
--- a/luni/src/main/java/javax/security/auth/x500/X500Principal.java
+++ b/luni/src/main/java/javax/security/auth/x500/X500Principal.java
@@ -128,7 +128,7 @@ public final class X500Principal implements Serializable, Principal {
try {
dn = new Name(name);
} catch (IOException e) {
- throw incorrectInputName(e);
+ throw incorrectInputName(e, name);
}
}
@@ -139,12 +139,12 @@ public final class X500Principal implements Serializable, Principal {
try {
dn = new Name(substituteNameFromMap(name, keywordMap));
} catch (IOException e) {
- throw incorrectInputName(e);
+ throw incorrectInputName(e, name);
}
}
- private IllegalArgumentException incorrectInputName(IOException e) {
- IllegalArgumentException iae = new IllegalArgumentException("Incorrect input name");
+ private IllegalArgumentException incorrectInputName(IOException e, String name) {
+ IllegalArgumentException iae = new IllegalArgumentException("Incorrect input name:" + name);
iae.initCause(e);
throw iae;
}
@@ -277,7 +277,7 @@ public final class X500Principal implements Serializable, Principal {
}
return resultName.toString();
} else {
- throw new IllegalArgumentException("invalid format specified");
+ throw new IllegalArgumentException("invalid format specified: " + format);
}
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/IndexedPKIXParameters.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/IndexedPKIXParameters.java
index 90baa87..e8acf18 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/IndexedPKIXParameters.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/IndexedPKIXParameters.java
@@ -23,9 +23,10 @@ import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,14 +40,30 @@ public final class IndexedPKIXParameters extends PKIXParameters {
private final Map<X500Principal, List<TrustAnchor>> subjectToTrustAnchors
= new HashMap<X500Principal, List<TrustAnchor>>();
+ public IndexedPKIXParameters() {}
+
public IndexedPKIXParameters(Set<TrustAnchor> anchors)
throws InvalidAlgorithmParameterException {
super(anchors);
index();
}
+ @Override public Set<TrustAnchor> getTrustAnchors() {
+ Set<TrustAnchor> result = new HashSet<TrustAnchor>();
+ synchronized (subjectToTrustAnchors) {
+ for (List<TrustAnchor> trustAnchors : subjectToTrustAnchors.values()) {
+ result.addAll(trustAnchors);
+ }
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ @Override public void setTrustAnchors(Set<TrustAnchor> trustAnchors) {
+ throw new UnsupportedOperationException();
+ }
+
private void index() {
- for (TrustAnchor anchor : getTrustAnchors()) {
+ for (TrustAnchor anchor : super.getTrustAnchors()) {
index(anchor);
}
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
index 609df94..68ab952 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
@@ -671,6 +671,8 @@ public class OpenSSLSocketImpl
} catch (CertificateException e) {
throw e;
+ } catch (RuntimeException e) {
+ throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/RootKeyStoreSpi.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/RootKeyStoreSpi.java
index bac01df..9ae4139 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/RootKeyStoreSpi.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/RootKeyStoreSpi.java
@@ -16,63 +16,21 @@
package org.apache.harmony.xnet.provider.jsse;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStoreSpi;
-import java.security.PublicKey;
-import java.security.cert.CertSelector;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
-import javax.security.auth.x500.X500Principal;
-import libcore.io.IoUtils;
/**
- * A Root Certificate Authority (CA) store for Android
- *
- * This KeyStoreSpi provides a read-only view of the
- * TrustedCertificateEntry objects found in the
- * $ANDROID_ROOT/etc/security/cacerts/ directory. The alias names used
- * correspond to filenames in that directory, which themselves are
- * named based on the OpenSSL X509_NAME_hash_old function. The
- * property that the filenames are based on a hash of the subject name
- * allows operations such as engineGetCertificateAlias to be
- * implemented efficiently without scanning the entire store.
- *
- * In addition to the KeyStoreSpi, RootKeyStoreSpi also provides the
- * additional public methods {@link #isTrustAnchor isTrustAnchor} and
- * {@link #findIssuer findIssuer} which allow efficient lookup
- * operations for CAs again based on the file naming convention.
+ * A KeyStoreSpi wrapper for the TrustedCertificateStore.
*/
public final class RootKeyStoreSpi extends KeyStoreSpi {
- private static final File CA_CERTS_DIR
- = new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
-
- private static final CertificateFactory CERT_FACTORY;
- static {
- try {
- CERT_FACTORY = CertificateFactory.getInstance("X509");
- } catch (CertificateException e) {
- throw new AssertionError(e);
- }
- }
-
- public RootKeyStoreSpi() {
- if (!CA_CERTS_DIR.isDirectory()) {
- throw new IllegalStateException(CA_CERTS_DIR + " is not a directory");
- }
- }
+ private final TrustedCertificateStore store = new TrustedCertificateStore();
@Override public Key engineGetKey(String alias, char[] password) {
if (alias == null) {
@@ -89,42 +47,11 @@ public final class RootKeyStoreSpi extends KeyStoreSpi {
}
@Override public Certificate engineGetCertificate(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
- return getCertificate(new File(CA_CERTS_DIR, alias));
- }
-
- private static X509Certificate getCertificate(File file) {
- if (!file.isFile()) {
- return null;
- }
- InputStream is = null;
- try {
- is = new BufferedInputStream(new FileInputStream(file));
- return (X509Certificate) CERT_FACTORY.generateCertificate(is);
- } catch (IOException e) {
- return null;
- } catch (CertificateException e) {
- throw new AssertionError(e);
- } finally {
- IoUtils.closeQuietly(is);
- }
+ return store.getCertificate(alias);
}
@Override public Date engineGetCreationDate(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
- File file = new File(CA_CERTS_DIR, alias);
- if (!file.isFile()) {
- return null;
- }
- long time = file.lastModified();
- if (time == 0) {
- return null;
- }
- return new Date(time);
+ return store.getCreationDate(alias);
}
@Override public void engineSetKeyEntry(
@@ -148,18 +75,15 @@ public final class RootKeyStoreSpi extends KeyStoreSpi {
}
@Override public Enumeration<String> engineAliases() {
- return Collections.enumeration(Arrays.asList(CA_CERTS_DIR.list()));
+ return Collections.enumeration(store.aliases());
}
@Override public boolean engineContainsAlias(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
- return new File(CA_CERTS_DIR, alias).isFile();
+ return store.containsAlias(alias);
}
@Override public int engineSize() {
- return CA_CERTS_DIR.list().length;
+ return store.aliases().size();
}
@Override public boolean engineIsKeyEntry(String alias) {
@@ -174,101 +98,7 @@ public final class RootKeyStoreSpi extends KeyStoreSpi {
}
@Override public String engineGetCertificateAlias(Certificate c) {
- if (c == null || !(c instanceof X509Certificate)) {
- return null;
- }
- final X509Certificate x = (X509Certificate) c;
- // compare X509Certificate.getEncoded values
- CertSelector selector = new CertSelector() {
- public boolean match(Certificate cert) {
- return cert.equals(x);
- }
- public Object clone() {
- throw new UnsupportedOperationException();
- }
- };
- return findCert(x.getSubjectX500Principal(), selector, String.class);
- }
-
- /**
- * This non-{@code KeyStoreSpi} public interface is used by {@code
- * TrustManagerImpl} to locate a CA certificate with the same
- * public key as the provided {@code X509Certificate}. We match on
- * public key and not the certificate itself since a CA may be
- * reissued with the same PublicKey but different signature (for
- * example when switching signature from md2WithRSAEncryption to
- * SHA1withRSA)
- */
- public static final boolean isTrustAnchor(final X509Certificate x) {
- // compare X509Certificate.getPublicKey values
- CertSelector selector = new CertSelector() {
- public boolean match(Certificate c) {
- X509Certificate ca = (X509Certificate)c;
- PublicKey caPublic = ca.getPublicKey();
- PublicKey certPublic = x.getPublicKey();
- return caPublic != null && certPublic != null && caPublic.equals(certPublic);
- }
- public Object clone() {
- throw new UnsupportedOperationException();
- }
- };
- return findCert(x.getSubjectX500Principal(), selector, Boolean.class);
- }
-
- /**
- * This non-{@code KeyStoreSpi} public interface is used by {@code
- * TrustManagerImpl} to locate the CA certificate that signed the
- * provided {@code X509Certificate}.
- */
- public static final X509Certificate findIssuer(final X509Certificate x) {
- // match on verified issuer of Certificate
- CertSelector selector = new CertSelector() {
- public boolean match(Certificate c) {
- X509Certificate ca = (X509Certificate)c;
- try {
- x.verify(ca.getPublicKey());
- return true;
- } catch (Exception e) {
- return false;
- }
- }
- public Object clone() {
- throw new UnsupportedOperationException();
- }
- };
- return findCert(x.getIssuerX500Principal(), selector, X509Certificate.class);
- }
-
- private static <T> T findCert(
- X500Principal subject, CertSelector selector, Class<T> desiredReturnType) {
-
- int intHash = NativeCrypto.X509_NAME_hash_old(subject);
- String strHash = IntegralToString.intToHexString(intHash, false, 8);
-
- for (int index = 0; true; index++) {
- String alias = strHash + "." + index;
- File file = new File(CA_CERTS_DIR, alias);
- if (!file.isFile()) {
- // could not find a match, no file exists, bail
- if (desiredReturnType == Boolean.class) {
- return (T) Boolean.FALSE;
- }
- return null;
- }
- X509Certificate cert = getCertificate(file);
- if (selector.match(cert)) {
- if (desiredReturnType == X509Certificate.class) {
- return (T) cert;
- }
- if (desiredReturnType == Boolean.class) {
- return (T) Boolean.TRUE;
- }
- if (desiredReturnType == String.class) {
- return (T) alias;
- }
- throw new AssertionError();
- }
- }
+ return store.getCertificateAlias(c);
}
@Override public void engineStore(OutputStream stream, char[] password) {
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
index 6c2552d..2a56618 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
@@ -47,10 +47,17 @@ import javax.net.ssl.X509TrustManager;
public final class TrustManagerImpl implements X509TrustManager {
/**
- * The AndroidCAStore if non-null, null otherwise
+ * The AndroidCAStore if non-null, null otherwise.
*/
private final KeyStore rootKeyStore;
+ /**
+ * The backing store for the AndroidCAStore if non-null. This will
+ * be null when the rootKeyStore is null, implying we are not
+ * using the AndroidCAStore.
+ */
+ private final TrustedCertificateStore trustedCertificateStore;
+
private final CertPathValidator validator;
private final IndexedPKIXParameters params;
@@ -79,6 +86,7 @@ public final class TrustManagerImpl implements X509TrustManager {
CertPathValidator validatorLocal = null;
CertificateFactory factoryLocal = null;
KeyStore rootKeyStoreLocal = null;
+ TrustedCertificateStore trustedCertificateStoreLocal = null;
IndexedPKIXParameters paramsLocal = null;
X509Certificate[] acceptedIssuersLocal = null;
Exception errLocal = null;
@@ -86,26 +94,25 @@ public final class TrustManagerImpl implements X509TrustManager {
validatorLocal = CertPathValidator.getInstance("PKIX");
factoryLocal = CertificateFactory.getInstance("X509");
- Set<TrustAnchor> trustAnchors;
// if we have an AndroidCAStore, we will lazily load CAs
if ("AndroidCAStore".equals(keyStore.getType())) {
rootKeyStoreLocal = keyStore;
+ trustedCertificateStoreLocal = new TrustedCertificateStore();
acceptedIssuersLocal = null;
- // Note we need to include at least one TrustAnchor
- // for the IndexedPKIXParameters super class to be happy.
- trustAnchors = trustAnchors(acceptedIssuers(keyStore, true));
+ paramsLocal = new IndexedPKIXParameters();
} else {
rootKeyStoreLocal = null;
- acceptedIssuersLocal = acceptedIssuers(keyStore, false);
- trustAnchors = trustAnchors(acceptedIssuersLocal);
+ trustedCertificateStoreLocal = null;
+ acceptedIssuersLocal = acceptedIssuers(keyStore);
+ paramsLocal = new IndexedPKIXParameters(trustAnchors(acceptedIssuersLocal));
}
- paramsLocal = new IndexedPKIXParameters(trustAnchors);
paramsLocal.setRevocationEnabled(false);
} catch (Exception e) {
errLocal = e;
}
this.rootKeyStore = rootKeyStoreLocal;
+ this.trustedCertificateStore = trustedCertificateStoreLocal;
this.validator = validatorLocal;
this.factory = factoryLocal;
this.params = paramsLocal;
@@ -113,7 +120,7 @@ public final class TrustManagerImpl implements X509TrustManager {
this.err = errLocal;
}
- private static X509Certificate[] acceptedIssuers(KeyStore ks, boolean onlyOne)
+ private static X509Certificate[] acceptedIssuers(KeyStore ks)
throws KeyStoreException {
// Note that unlike the PKIXParameters code to create a Set of
// TrustAnchors from a KeyStore, this version takes from both
@@ -130,9 +137,6 @@ public final class TrustManagerImpl implements X509TrustManager {
final X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
if (cert != null) {
trusted.add(cert);
- if (onlyOne) {
- break;
- }
}
}
return trusted.toArray(new X509Certificate[trusted.size()]);
@@ -171,12 +175,6 @@ public final class TrustManagerImpl implements X509TrustManager {
return;
}
- if (rootKeyStore != null) {
- // check if we need to add a missing TrustAnchor value to
- // the IndexedPKIXParameters from the KeyStore
- optionallyAddTrustAnchorFromKeyStore(newChain[newChain.length-1]);
- }
-
CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
if (!Arrays.equals(chain[0].getEncoded(),
certPath.getCertificates().get(0).getEncoded())) {
@@ -184,6 +182,20 @@ public final class TrustManagerImpl implements X509TrustManager {
// are using pretty remote code)
throw new CertificateException("Certificate chain error");
}
+
+ if (trustedCertificateStore != null) {
+ // check if we need to add a missing TrustAnchor value to
+ // the IndexedPKIXParameters from the KeyStore.
+ boolean found = optionallyAddTrustAnchorToIndex(newChain[newChain.length-1]);
+ // If we can't find the TrustAnchor, we throw
+ // CertPathValidatorException to avoid an
+ // ExtendedPKIXParameters constructor error in validate().
+ if (!found) {
+ throw new CertificateException(new CertPathValidatorException(
+ "Trust anchor for certification path not found.", null, certPath, -1));
+ }
+ }
+
try {
validator.validate(certPath, params);
// Add intermediate CAs to the index to tolerate sites
@@ -265,7 +277,7 @@ public final class TrustManagerImpl implements X509TrustManager {
return Arrays.copyOf(chain, chainLength);
}
- private void optionallyAddTrustAnchorFromKeyStore(X509Certificate lastCert) {
+ private boolean optionallyAddTrustAnchorToIndex(X509Certificate lastCert) {
TrustAnchor trustAnchor;
try {
// returns null if no match based on issuer
@@ -277,15 +289,18 @@ public final class TrustManagerImpl implements X509TrustManager {
// to the IndexedPKIXParameters.
trustAnchor = null;
}
- if (trustAnchor == null) {
- // we have a KeyStore and the issuer of the last cert in
- // the chain seems to be missing from the
- // IndexedPKIXParameters, check the KeyStore for a hit
- X509Certificate issuer = RootKeyStoreSpi.findIssuer(lastCert);
- if (issuer != null) {
- index(issuer);
- }
+ if (trustAnchor != null) {
+ return true;
+ }
+ // we have a KeyStore and the issuer of the last cert in
+ // the chain seems to be missing from the
+ // IndexedPKIXParameters, check the KeyStore for a hit
+ X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
+ if (issuer != null) {
+ index(issuer);
+ return true;
}
+ return false;
}
/**
@@ -298,14 +313,14 @@ public final class TrustManagerImpl implements X509TrustManager {
if (isTrustAnchor) {
return true;
}
- if (rootKeyStore == null) {
- // not trusted and no KeyStore to check
+ if (trustedCertificateStore == null) {
+ // not trusted and no TrustedCertificateStore to check
return false;
}
// probe KeyStore for a cert. AndroidCAStore stores its
// contents hashed by cert subject on the filesystem to make
// this faster than scanning all key store entries.
- if (RootKeyStoreSpi.isTrustAnchor(cert)) {
+ if (trustedCertificateStore.isTrustAnchor(cert)) {
// add new TrustAnchor to params index to avoid
// checking filesystem next time around.
index(cert);
@@ -326,7 +341,7 @@ public final class TrustManagerImpl implements X509TrustManager {
if (result == null) {
// single-check idiom
try {
- acceptedIssuers = result = acceptedIssuers(rootKeyStore, false);
+ acceptedIssuers = result = acceptedIssuers(rootKeyStore);
} catch (KeyStoreException e) {
acceptedIssuers = result = new X509Certificate[0];
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java
new file mode 100644
index 0000000..f440eec
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java
@@ -0,0 +1,499 @@
+/*
+ * 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.apache.harmony.xnet.provider.jsse;
+
+import java.io.BufferedInputStream;
+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.KeyStoreSpi;
+import java.security.PublicKey;
+import java.security.cert.CertSelector;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import javax.security.auth.x500.X500Principal;
+import libcore.io.IoUtils;
+
+/**
+ * A source for trusted root certificate authority (CA) certificates
+ * supporting an immutable system CA directory along with mutable
+ * directories allowing the user addition of custom CAs and user
+ * removal of system CAs. This store supports the RootKeyStoreSpi
+ * wrapper to allow a traditional KeyStore interface for use with
+ * {@link javax.net.ssl.TrustManagerFactory.init}.
+ *
+ * <p>The CAs are accessed via {@code KeyStore} style aliases. Aliases
+ * are made up of a prefix identifying the source ("system:" vs
+ * "user:") and a suffix based on the OpenSSL X509_NAME_hash_old
+ * function of the CA's subject name. For example, the system CA for
+ * "C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification
+ * Authority" could be represented as "system:7651b327.0". By using
+ * the subject hash, operations such as {@link #getCertificateAlias
+ * getCertificateAlias} can be implemented efficiently without
+ * scanning the entire store.
+ *
+ * <p>In addition to supporting the {@code RootKeyStoreSpi}
+ * implementation, {@code TrustedCertificateStore} also provides the
+ * additional public methods {@link #isTrustAnchor} and {@link
+ * #findIssuer} to allow efficient lookup operations for CAs again
+ * based on the file naming convention.
+ *
+ * <p>The KeyChainService users the {@link installCertificate} and
+ * {@link #deleteCertificateEntry} to install user CAs as well as
+ * delete those user CAs as well as system CAs. The deletion of system
+ * CAs is performed by placing an exact copy of that CA in the deleted
+ * directory. Such deletions are intended to persist across upgrades
+ * but not intended to mask a CA with a matching name or public key
+ * but is otherwise reissued in a system update. Reinstalling a
+ * deleted system certificate simply removes the copy from the deleted
+ * directory, reenabling the original in the system directory.
+ *
+ * <p>Note that the default mutable directory is created by init via
+ * configuration in the system/core/rootdir/init.rc file. The
+ * directive "mkdir /data/misc/keychain 0775 keychain keychain"
+ * ensures that its owner and group are the keychain uid and keychain
+ * gid and that it is world readable but only writable by the keychain
+ * user.
+ */
+public final class TrustedCertificateStore {
+
+ private static final String PREFIX_SYSTEM = "system:";
+ private static final String PREFIX_USER = "user:";
+
+ public static final boolean isSystem(String alias) {
+ return alias.startsWith(PREFIX_SYSTEM);
+ }
+ public static final boolean isUser(String alias) {
+ return alias.startsWith(PREFIX_USER);
+ }
+
+ private static final File CA_CERTS_DIR_SYSTEM;
+ private static final File CA_CERTS_DIR_ADDED;
+ private static final File CA_CERTS_DIR_DELETED;
+ private static final CertificateFactory CERT_FACTORY;
+ static {
+ String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
+ String ANDROID_DATA = System.getenv("ANDROID_DATA");
+ CA_CERTS_DIR_SYSTEM = new File(ANDROID_ROOT + "/etc/security/cacerts");
+ CA_CERTS_DIR_ADDED = new File(ANDROID_DATA + "/misc/keychain/cacerts-added");
+ CA_CERTS_DIR_DELETED = new File(ANDROID_DATA + "/misc/keychain/cacerts-removed");
+
+ try {
+ CERT_FACTORY = CertificateFactory.getInstance("X509");
+ } catch (CertificateException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private final File systemDir;
+ private final File addedDir;
+ private final File deletedDir;
+
+ public TrustedCertificateStore() {
+ this(CA_CERTS_DIR_SYSTEM, CA_CERTS_DIR_ADDED, CA_CERTS_DIR_DELETED);
+ }
+
+ public TrustedCertificateStore(File systemDir, File addedDir, File deletedDir) {
+ if (!systemDir.isDirectory()) {
+ throw new IllegalStateException(systemDir + " is not a directory");
+ }
+
+ this.systemDir = systemDir;
+ this.addedDir = addedDir;
+ this.deletedDir = deletedDir;
+ }
+
+ public Certificate getCertificate(String alias) {
+ File file = fileForAlias(alias);
+ if (file == null || (isUser(alias) && isTombstone(file))) {
+ return null;
+ }
+ X509Certificate cert = readCertificate(file);
+ if (cert == null || (isSystem(alias) && isDeletedSystemCertificate(cert))) {
+ // skip malformed certs as well as deleted system ones
+ return null;
+ }
+ return cert;
+ }
+
+ private File fileForAlias(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+ File file;
+ if (isSystem(alias)) {
+ file = new File(systemDir, alias.substring(PREFIX_SYSTEM.length()));
+ } else if (isUser(alias)) {
+ file = new File(addedDir, alias.substring(PREFIX_USER.length()));
+ } else {
+ return null;
+ }
+ if (!file.exists() || isTombstone(file)) {
+ // silently elide tombstones
+ return null;
+ }
+ return file;
+ }
+
+ private boolean isTombstone(File file) {
+ return file.length() == 0;
+ }
+
+ private X509Certificate readCertificate(File file) {
+ if (!file.isFile()) {
+ return null;
+ }
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(new FileInputStream(file));
+ return (X509Certificate) CERT_FACTORY.generateCertificate(is);
+ } catch (IOException e) {
+ return null;
+ } catch (CertificateException e) {
+ // reading a cert while its being installed can lead to this.
+ // just pretend like its not available yet.
+ return null;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ private void writeCertificate(File file, X509Certificate cert)
+ throws IOException, CertificateException {
+ File dir = file.getParentFile();
+ dir.mkdirs();
+ dir.setReadable(true, false);
+ dir.setExecutable(true, false);
+ OutputStream os = null;
+ try {
+ os = new FileOutputStream(file);
+ os.write(cert.getEncoded());
+ } finally {
+ os.close();
+ }
+ file.setReadable(true, false);
+ }
+
+ private boolean isDeletedSystemCertificate(X509Certificate x) {
+ return getCertificateFile(deletedDir, x).exists();
+ }
+
+ public Date getCreationDate(String alias) {
+ // containsAlias check ensures the later fileForAlias result
+ // was not a deleted system cert.
+ if (!containsAlias(alias)) {
+ return null;
+ }
+ File file = fileForAlias(alias);
+ if (file == null) {
+ return null;
+ }
+ long time = file.lastModified();
+ if (time == 0) {
+ return null;
+ }
+ return new Date(time);
+ }
+
+ public Set<String> aliases() {
+ Set<String> result = new HashSet<String>();
+ addAliases(result, PREFIX_USER, addedDir);
+ addAliases(result, PREFIX_SYSTEM, systemDir);
+ return result;
+ }
+
+ private void addAliases(Set<String> result, String prefix, File dir) {
+ String[] files = dir.list();
+ if (files == null) {
+ return;
+ }
+ for (String filename : files) {
+ String alias = prefix + filename;
+ if (containsAlias(alias)) {
+ result.add(alias);
+ }
+ }
+ }
+
+ public boolean containsAlias(String alias) {
+ return getCertificate(alias) != null;
+ }
+
+ public String getCertificateAlias(Certificate c) {
+ if (c == null || !(c instanceof X509Certificate)) {
+ return null;
+ }
+ X509Certificate x = (X509Certificate) c;
+ File user = getCertificateFile(addedDir, x);
+ if (user.exists()) {
+ return PREFIX_USER + user.getName();
+ }
+ if (isDeletedSystemCertificate(x)) {
+ return null;
+ }
+ File system = getCertificateFile(systemDir, x);
+ if (system.exists()) {
+ return PREFIX_SYSTEM + system.getName();
+ }
+ return null;
+ }
+
+ /**
+ * Returns a File for where the certificate is found if it exists
+ * or where it should be installed if it does not exist. The
+ * caller can disambiguate these cases by calling {@code
+ * File.exists()} on the result.
+ */
+ private File getCertificateFile(File dir, final X509Certificate x) {
+ // compare X509Certificate.getEncoded values
+ CertSelector selector = new CertSelector() {
+ @Override public boolean match(X509Certificate cert) {
+ return cert.equals(x);
+ }
+ };
+ return findCert(dir, x.getSubjectX500Principal(), selector, File.class);
+ }
+
+ /**
+ * This non-{@code KeyStoreSpi} public interface is used by {@code
+ * TrustManagerImpl} to locate a CA certificate with the same name
+ * and public key as the provided {@code X509Certificate}. We
+ * match on the name and public key and not the entire certificate
+ * since a CA may be reissued with the same name and PublicKey but
+ * with other differences (for example when switching signature
+ * from md2WithRSAEncryption to SHA1withRSA)
+ */
+ public boolean isTrustAnchor(final X509Certificate c) {
+ // compare X509Certificate.getPublicKey values
+ CertSelector selector = new CertSelector() {
+ @Override public boolean match(X509Certificate ca) {
+ return ca.getPublicKey().equals(c.getPublicKey());
+ }
+ };
+ boolean user = findCert(addedDir,
+ c.getSubjectX500Principal(),
+ selector,
+ Boolean.class);
+ if (user) {
+ return true;
+ }
+ X509Certificate system = findCert(systemDir,
+ c.getSubjectX500Principal(),
+ selector,
+ X509Certificate.class);
+ return system != null && !isDeletedSystemCertificate(system);
+ }
+
+ /**
+ * This non-{@code KeyStoreSpi} public interface is used by {@code
+ * TrustManagerImpl} to locate the CA certificate that signed the
+ * provided {@code X509Certificate}.
+ */
+ public X509Certificate findIssuer(final X509Certificate c) {
+ // match on verified issuer of Certificate
+ CertSelector selector = new CertSelector() {
+ @Override public boolean match(X509Certificate ca) {
+ try {
+ c.verify(ca.getPublicKey());
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ };
+ X509Certificate user = findCert(addedDir,
+ c.getIssuerX500Principal(),
+ selector,
+ X509Certificate.class);
+ if (user != null) {
+ return user;
+ }
+ X509Certificate system = findCert(systemDir,
+ c.getIssuerX500Principal(),
+ selector,
+ X509Certificate.class);
+ if (system != null && !isDeletedSystemCertificate(system)) {
+ return system;
+ }
+ return null;
+ }
+
+ // like java.security.cert.CertSelector but with X509Certificate and without cloning
+ private static interface CertSelector {
+ public boolean match(X509Certificate cert);
+ }
+
+ private <T> T findCert(
+ File dir, X500Principal subject, CertSelector selector, Class<T> desiredReturnType) {
+
+ String hash = hash(subject);
+ for (int index = 0; true; index++) {
+ File file = file(dir, hash, index);
+ if (!file.isFile()) {
+ // could not find a match, no file exists, bail
+ if (desiredReturnType == Boolean.class) {
+ return (T) Boolean.FALSE;
+ }
+ if (desiredReturnType == File.class) {
+ // we return file so that caller that wants to
+ // write knows what the next available has
+ // location is
+ return (T) file;
+ }
+ return null;
+ }
+ if (isTombstone(file)) {
+ continue;
+ }
+ X509Certificate cert = readCertificate(file);
+ if (cert == null) {
+ // skip problem certificates
+ continue;
+ }
+ if (selector.match(cert)) {
+ if (desiredReturnType == X509Certificate.class) {
+ return (T) cert;
+ }
+ if (desiredReturnType == Boolean.class) {
+ return (T) Boolean.TRUE;
+ }
+ if (desiredReturnType == File.class) {
+ return (T) file;
+ }
+ throw new AssertionError();
+ }
+ }
+ }
+
+ private String hash(X500Principal name) {
+ int hash = NativeCrypto.X509_NAME_hash_old(name);
+ return IntegralToString.intToHexString(hash, false, 8);
+ }
+
+ private File file(File dir, String hash, int index) {
+ return new File(dir, hash + '.' + index);
+ }
+
+ /**
+ * 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 {
+ if (cert == null) {
+ throw new NullPointerException("cert == null");
+ }
+ File system = getCertificateFile(systemDir, cert);
+ if (system.exists()) {
+ File deleted = getCertificateFile(deletedDir, cert);
+ if (deleted.exists()) {
+ // we have a system cert that was marked deleted.
+ // remove the deleted marker to expose the original
+ if (!deleted.delete()) {
+ throw new IOException("Could not remove " + deleted);
+ }
+ return;
+ }
+ // otherwise we just have a dup of an existing system cert.
+ // return taking no further action.
+ return;
+ }
+ File user = getCertificateFile(addedDir, cert);
+ if (user.exists()) {
+ // we have an already installed user cert, bail.
+ return;
+ }
+ // install the user cert
+ writeCertificate(user, cert);
+ }
+
+ /**
+ * This could be considered the implementation of {@code
+ * RootKeyStoreSpi.engineDeleteEntry} but we consider
+ * RootKeyStoreSpi to be read only. Instead, this is used by the
+ * {@code KeyChainService} to delete CA certificates.
+ */
+ public void deleteCertificateEntry(String alias) throws IOException, CertificateException {
+ if (alias == null) {
+ return;
+ }
+ File file = fileForAlias(alias);
+ if (file == null) {
+ return;
+ }
+ if (isSystem(alias)) {
+ X509Certificate cert = readCertificate(file);
+ if (cert == null) {
+ // skip problem certificates
+ return;
+ }
+ File deleted = getCertificateFile(deletedDir, cert);
+ if (deleted.exists()) {
+ // already deleted system certificate
+ return;
+ }
+ // write copy of system cert to marked as deleted
+ writeCertificate(deleted, cert);
+ return;
+ }
+ if (isUser(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();
+ removeUnnecessaryTombstones(alias);
+ return;
+ }
+ // non-existant user cert, nothing to delete
+ }
+
+ private void removeUnnecessaryTombstones(String alias) throws IOException {
+ if (!isUser(alias)) {
+ throw new AssertionError(alias);
+ }
+ int dotIndex = alias.lastIndexOf('.');
+ if (dotIndex == -1) {
+ throw new AssertionError(alias);
+ }
+
+ String hash = alias.substring(PREFIX_USER.length(), dotIndex);
+ int lastTombstoneIndex = Integer.parseInt(alias.substring(dotIndex + 1));
+
+ if (file(addedDir, hash, lastTombstoneIndex + 1).exists()) {
+ return;
+ }
+ while (lastTombstoneIndex >= 0) {
+ File file = file(addedDir, hash, lastTombstoneIndex);
+ if (!isTombstone(file)) {
+ break;
+ }
+ if (!file.delete()) {
+ throw new IOException("Could not remove " + file);
+ }
+ lastTombstoneIndex--;
+ }
+ }
+}
diff --git a/luni/src/test/java/libcore/java/security/KeyStoreTest.java b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
index f255392..af66ded 100644
--- a/luni/src/test/java/libcore/java/security/KeyStoreTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
@@ -54,16 +54,10 @@ import libcore.io.IoUtils;
public class KeyStoreTest extends TestCase {
- private static final PrivateKeyEntry PRIVATE_KEY;
- private static final PrivateKeyEntry PRIVATE_KEY_2;
- static {
- try {
- PRIVATE_KEY = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
- PRIVATE_KEY_2 = TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
+ private static final PrivateKeyEntry PRIVATE_KEY
+ = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+ private static final PrivateKeyEntry PRIVATE_KEY_2
+ = TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
private static final SecretKey SECRET_KEY = generateSecretKey();
private static final SecretKey SECRET_KEY_2 = generateSecretKey();
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
index c0be701..b5f8a82 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
@@ -24,8 +24,7 @@ import java.net.SocketTimeoutException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore;
import java.security.KeyStoreException;
-import libcore.java.security.StandardNames;
-import libcore.java.security.TestKeyStore;
+import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -42,6 +41,8 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLProtocolException;
import javax.security.auth.x500.X500Principal;
import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
+import libcore.java.security.TestKeyStore;
import org.apache.harmony.xnet.provider.jsse.CipherSuite;
import org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSLHandshakeCallbacks;
@@ -107,28 +108,23 @@ public class NativeCryptoTest extends TestCase {
NativeCrypto.SSL_CTX_free(c);
}
- private static final PrivateKeyEntry SERVER_PRIVATE_KEY_ENTRY;
- private static final byte[] SERVER_PRIVATE_KEY;
- private static final byte[][] SERVER_CERTIFICATES;
- private static final PrivateKeyEntry CLIENT_PRIVATE_KEY_ENTRY;
- private static final byte[] CLIENT_PRIVATE_KEY;
- private static final byte[][] CLIENT_CERTIFICATES;
-
- static {
+ private static final PrivateKeyEntry SERVER_PRIVATE_KEY_ENTRY
+ = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+ private static final byte[] SERVER_PRIVATE_KEY
+ = SERVER_PRIVATE_KEY_ENTRY.getPrivateKey().getEncoded();
+ private static final byte[][] SERVER_CERTIFICATES
+ = encodeCertificates(SERVER_PRIVATE_KEY_ENTRY.getCertificateChain());
+ private static final PrivateKeyEntry CLIENT_PRIVATE_KEY_ENTRY
+ = TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
+ private static final byte[] CLIENT_PRIVATE_KEY
+ = CLIENT_PRIVATE_KEY_ENTRY.getPrivateKey().getEncoded();
+ private static final byte[][] CLIENT_CERTIFICATES
+ = encodeCertificates(CLIENT_PRIVATE_KEY_ENTRY.getCertificateChain());
+
+ private static byte[][] encodeCertificates (Certificate[] certificates) {
try {
- SERVER_PRIVATE_KEY_ENTRY
- = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
- SERVER_PRIVATE_KEY
- = SERVER_PRIVATE_KEY_ENTRY.getPrivateKey().getEncoded();
- SERVER_CERTIFICATES = NativeCrypto.encodeCertificates(
- SERVER_PRIVATE_KEY_ENTRY.getCertificateChain());
- CLIENT_PRIVATE_KEY_ENTRY
- = TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
- CLIENT_PRIVATE_KEY
- = CLIENT_PRIVATE_KEY_ENTRY.getPrivateKey().getEncoded();
- CLIENT_CERTIFICATES = NativeCrypto.encodeCertificates(
- CLIENT_PRIVATE_KEY_ENTRY.getCertificateChain());
- } catch (Exception e) {
+ return NativeCrypto.encodeCertificates(certificates);
+ } catch (CertificateEncodingException e) {
throw new RuntimeException(e);
}
}
@@ -1786,4 +1782,13 @@ public class NativeCryptoTest extends TestCase {
// positively testing by test_i2d_SSL_SESSION
}
+
+ public void test_X509_NAME_hashes() {
+ // ensure these hash functions are stable over time since the
+ // /system/etc/security/cacerts CA filenames have to be
+ // consistent with the output.
+ X500Principal name = new X500Principal("CN=localhost");
+ assertEquals(-1372642656, NativeCrypto.X509_NAME_hash(name)); // SHA1
+ assertEquals(-1626170662, NativeCrypto.X509_NAME_hash_old(name)); // MD5
+ }
}
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java
new file mode 100644
index 0000000..7bac8e8
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java
@@ -0,0 +1,513 @@
+/*
+ * 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.apache.harmony.xnet.provider.jsse;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import javax.security.auth.x500.X500Principal;
+import junit.framework.TestCase;
+import libcore.java.security.TestKeyStore;
+
+public class TrustedCertificateStoreTest extends TestCase {
+
+ private static final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
+ private static final File DIR_TEST = new File(DIR_TEMP, "test");
+ private static final File DIR_SYSTEM = new File(DIR_TEST, "system");
+ private static final File DIR_ADDED = new File(DIR_TEST, "added");
+ private static final File DIR_DELETED = new File(DIR_TEST, "removed");
+
+ private static final X509Certificate CA1 = TestKeyStore.getClient().getRootCertificate("RSA");
+ private static final X509Certificate CA2
+ = TestKeyStore.getClientCA2().getRootCertificate("RSA");
+
+ private static final KeyStore.PrivateKeyEntry PRIVATE
+ = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+ private static final X509Certificate[] CHAIN = (X509Certificate[])PRIVATE.getCertificateChain();
+
+ private static final X509Certificate CA3_WITH_CA1_SUBJECT
+ = new TestKeyStore.Builder()
+ .aliasPrefix("unused")
+ .subject(CA1.getSubjectX500Principal())
+ .ca(true)
+ .build().getRootCertificate("RSA");
+
+
+ private static final String ALIAS_SYSTEM_CA1 = alias(false, CA1, 0);
+ private static final String ALIAS_SYSTEM_CA2 = alias(false, CA2, 0);
+ private static final String ALIAS_USER_CA1 = alias(true, CA1, 0);
+ private static final String ALIAS_USER_CA2 = alias(true, CA2, 0);
+
+ private static final String ALIAS_SYSTEM_CHAIN0 = alias(false, CHAIN[0], 0);
+ private static final String ALIAS_SYSTEM_CHAIN1 = alias(false, CHAIN[1], 0);
+ private static final String ALIAS_SYSTEM_CHAIN2 = alias(false, CHAIN[2], 0);
+ private static final String ALIAS_USER_CHAIN0 = alias(true, CHAIN[0], 0);
+ private static final String ALIAS_USER_CHAIN1 = alias(true, CHAIN[1], 0);
+ private static final String ALIAS_USER_CHAIN2 = alias(true, CHAIN[2], 0);
+
+ private static final String ALIAS_SYSTEM_CA3 = alias(false, CA3_WITH_CA1_SUBJECT, 0);
+ private static final String ALIAS_SYSTEM_CA3_COLLISION
+ = alias(false, CA3_WITH_CA1_SUBJECT, 1);
+ private static final String ALIAS_USER_CA3 = alias(true, CA3_WITH_CA1_SUBJECT, 0);
+ private static final String ALIAS_USER_CA3_COLLISION
+ = alias(true, CA3_WITH_CA1_SUBJECT, 1);
+
+ private TrustedCertificateStore store;
+
+ @Override protected void setUp() {
+ setupStore();
+ }
+
+ private void setupStore() {
+ DIR_SYSTEM.mkdirs();
+ createStore();
+ }
+
+ private void createStore() {
+ store = new TrustedCertificateStore(DIR_SYSTEM, DIR_ADDED, DIR_DELETED);
+ }
+
+ @Override protected void tearDown() {
+ cleanStore();
+ }
+
+ private void cleanStore() {
+ for (File dir : new File[] { DIR_SYSTEM, DIR_ADDED, DIR_DELETED, DIR_TEST }) {
+ File[] files = dir.listFiles();
+ if (files == null) {
+ continue;
+ }
+ for (File file : files) {
+ assertTrue(file.delete());
+ }
+ }
+ store = null;
+ }
+
+ private void resetStore() {
+ cleanStore();
+ setupStore();
+ }
+
+ public void testEmptyDirectories() throws Exception {
+ assertEmpty();
+ }
+
+ public void testOneSystemOneDeleted() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ }
+
+ public void testTwoSystemTwoDeleted() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ install(CA2, ALIAS_SYSTEM_CA2);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA2);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ assertDeleted(CA2, ALIAS_SYSTEM_CA2);
+ }
+
+ public void testPartialFileIsIgnored() throws Exception {
+ File file = file(ALIAS_SYSTEM_CA1);
+ 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();
+
+ try {
+ store.containsAlias(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ assertFalse(store.containsAlias(""));
+
+ assertNull(store.getCertificateAlias(null));
+ assertNull(store.getCertificateAlias(CA1));
+
+ try {
+ store.isTrustAnchor(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ assertFalse(store.isTrustAnchor(CA1));
+
+ try {
+ store.findIssuer(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ assertNull(store.findIssuer(CA1));
+
+ try {
+ store.installCertificate(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+
+ store.deleteCertificateEntry(null);
+ store.deleteCertificateEntry("");
+
+ String[] userFiles = DIR_ADDED.list();
+ assertTrue(userFiles == null || userFiles.length == 0);
+ }
+
+ public void testTwoSystem() throws Exception {
+ testTwo(CA1, ALIAS_SYSTEM_CA1,
+ CA2, ALIAS_SYSTEM_CA2);
+ }
+
+ public void testTwoUser() throws Exception {
+ testTwo(CA1, ALIAS_USER_CA1,
+ CA2, ALIAS_USER_CA2);
+ }
+
+ public void testOneSystemOneUser() throws Exception {
+ testTwo(CA1, ALIAS_SYSTEM_CA1,
+ CA2, ALIAS_USER_CA2);
+ }
+
+ public void testTwoSystemSameSubject() throws Exception {
+ testTwo(CA1, ALIAS_SYSTEM_CA1,
+ CA3_WITH_CA1_SUBJECT, ALIAS_SYSTEM_CA3_COLLISION);
+ }
+
+ public void testTwoUserSameSubject() throws Exception {
+ testTwo(CA1, ALIAS_USER_CA1,
+ CA3_WITH_CA1_SUBJECT, ALIAS_USER_CA3_COLLISION);
+
+ store.deleteCertificateEntry(ALIAS_USER_CA1);
+ assertDeleted(CA1, ALIAS_USER_CA1);
+ assertTombstone(ALIAS_USER_CA1);
+ assertRootCA(CA3_WITH_CA1_SUBJECT, ALIAS_USER_CA3_COLLISION);
+ assertAliases(ALIAS_USER_CA3_COLLISION);
+
+ store.deleteCertificateEntry(ALIAS_USER_CA3_COLLISION);
+ assertDeleted(CA3_WITH_CA1_SUBJECT, ALIAS_USER_CA3_COLLISION);
+ assertNoTombstone(ALIAS_USER_CA3_COLLISION);
+ assertNoTombstone(ALIAS_USER_CA1);
+ assertEmpty();
+ }
+
+ public void testOneSystemOneUserSameSubject() throws Exception {
+ testTwo(CA1, ALIAS_SYSTEM_CA1,
+ CA3_WITH_CA1_SUBJECT, ALIAS_USER_CA3);
+ testTwo(CA1, ALIAS_USER_CA1,
+ CA3_WITH_CA1_SUBJECT, ALIAS_SYSTEM_CA3);
+ }
+
+ 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(CA1, ALIAS_SYSTEM_CA1);
+ store.installCertificate(CA2);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA2, ALIAS_USER_CA2);
+ assertAliases(ALIAS_USER_CA2);
+ }
+
+ public void testOneSystemOneUserOneDeletedSameSubject() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ store.installCertificate(CA3_WITH_CA1_SUBJECT);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA3_WITH_CA1_SUBJECT, ALIAS_USER_CA3);
+ assertAliases(ALIAS_USER_CA3);
+ }
+
+ public void testUserMaskingSystem() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ install(CA1, ALIAS_USER_CA1);
+ assertMasked(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA1, ALIAS_USER_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1, ALIAS_USER_CA1);
+ }
+
+ public void testChain() throws Exception {
+ testChain(ALIAS_SYSTEM_CHAIN1, ALIAS_SYSTEM_CHAIN2);
+ testChain(ALIAS_SYSTEM_CHAIN1, ALIAS_USER_CHAIN2);
+ testChain(ALIAS_USER_CHAIN1, ALIAS_SYSTEM_CA1);
+ testChain(ALIAS_USER_CHAIN1, ALIAS_USER_CHAIN2);
+ }
+
+ private void testChain(String alias1, String alias2) throws Exception {
+ install(CHAIN[1], alias1);
+ install(CHAIN[2], alias2);
+ assertIntermediateCA(CHAIN[1], alias1);
+ assertRootCA(CHAIN[2], alias2);
+ assertAliases(alias1, alias2);
+ assertEquals(CHAIN[2], store.findIssuer(CHAIN[1]));
+ assertEquals(CHAIN[1], store.findIssuer(CHAIN[0]));
+ resetStore();
+ }
+
+ public void testMissingSystemDirectory() {
+ cleanStore();
+ try {
+ createStore();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testWithExistingUserDirectories() throws Exception {
+ DIR_ADDED.mkdirs();
+ DIR_DELETED.mkdirs();
+ install(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA1, ALIAS_SYSTEM_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1);
+ }
+
+ public void testIsTrustAnchorWithReissuedCA() throws Exception {
+ PublicKey publicKey = PRIVATE.getCertificate().getPublicKey();
+ PrivateKey privateKey = PRIVATE.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);
+ assertTrue(store.isTrustAnchor(ca2));
+ assertEquals(ca1, store.findIssuer(ca2));
+ resetStore();
+
+ String userAlias = alias(true, ca1, 0);
+ store.installCertificate(ca1);
+ assertRootCA(ca1, userAlias);
+ assertTrue(store.isTrustAnchor(ca2));
+ assertEquals(ca1, store.findIssuer(ca2));
+ resetStore();
+ }
+
+ public void testInstallEmpty() throws Exception {
+ store.installCertificate(CA1);
+ assertRootCA(CA1, ALIAS_USER_CA1);
+ assertAliases(ALIAS_USER_CA1);
+
+ // reinstalling should not change anything
+ store.installCertificate(CA1);
+ assertRootCA(CA1, ALIAS_USER_CA1);
+ assertAliases(ALIAS_USER_CA1);
+ }
+
+ public void testInstallEmptySystemExists() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA1, ALIAS_SYSTEM_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1);
+
+ // reinstalling should not affect system CA
+ store.installCertificate(CA1);
+ assertRootCA(CA1, ALIAS_SYSTEM_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1);
+
+ }
+
+ public void testInstallEmptyDeletedSystemExists() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+
+ // installing should restore deleted system CA
+ store.installCertificate(CA1);
+ assertRootCA(CA1, ALIAS_SYSTEM_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1);
+ }
+
+ public void testDeleteEmpty() throws Exception {
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ }
+
+ public void testDeleteUser() throws Exception {
+ store.installCertificate(CA1);
+ assertRootCA(CA1, ALIAS_USER_CA1);
+ assertAliases(ALIAS_USER_CA1);
+
+ store.deleteCertificateEntry(ALIAS_USER_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_USER_CA1);
+ assertNoTombstone(ALIAS_USER_CA1);
+ }
+
+ public void testDeleteSystem() throws Exception {
+ install(CA1, ALIAS_SYSTEM_CA1);
+ assertRootCA(CA1, ALIAS_SYSTEM_CA1);
+ assertAliases(ALIAS_SYSTEM_CA1);
+
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+
+ // deleting again should not change anything
+ store.deleteCertificateEntry(ALIAS_SYSTEM_CA1);
+ assertEmpty();
+ assertDeleted(CA1, ALIAS_SYSTEM_CA1);
+ }
+
+ 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));
+ assertTrue(store.isTrustAnchor(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));
+ assertFalse(store.isTrustAnchor(x));
+ }
+
+ 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()) {
+ if (TrustedCertificateStore.isSystem(alias) || TrustedCertificateStore.isUser(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 = IntegralToString.intToHexString(intHash, false, 8);
+
+ return prefix + strHash + '.' + index;
+ }
+
+ /**
+ * Install certificate under specified alias
+ */
+ private static 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 static File file(String alias) {
+ File dir;
+ if (TrustedCertificateStore.isSystem(alias)) {
+ dir = DIR_SYSTEM;
+ } else if (TrustedCertificateStore.isUser(alias)) {
+ dir = DIR_ADDED;
+ } 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);
+ }
+}
diff --git a/support/src/test/java/libcore/java/security/TestKeyStore.java b/support/src/test/java/libcore/java/security/TestKeyStore.java
index 353ca20..6bb44a4 100644
--- a/support/src/test/java/libcore/java/security/TestKeyStore.java
+++ b/support/src/test/java/libcore/java/security/TestKeyStore.java
@@ -24,22 +24,19 @@ import com.android.org.bouncycastle.asn1.x509.GeneralSubtree;
import com.android.org.bouncycastle.asn1.x509.KeyUsage;
import com.android.org.bouncycastle.asn1.x509.NameConstraints;
import com.android.org.bouncycastle.asn1.x509.X509Extensions;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
-import com.android.org.bouncycastle.jce.X509Principal;
import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
import java.io.ByteArrayInputStream;
import java.io.PrintStream;
import java.math.BigInteger;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
-import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
@@ -56,13 +53,13 @@ import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
-import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
import junit.framework.Assert;
import libcore.javax.net.ssl.TestKeyManager;
import libcore.javax.net.ssl.TestTrustManager;
@@ -103,8 +100,7 @@ public final class TestKeyStore extends Assert {
public final TestKeyManager keyManager;
public final TestTrustManager trustManager;
- private TestKeyStore(KeyStore keyStore, char[] storePassword, char[] keyPassword)
- throws Exception {
+ private TestKeyStore(KeyStore keyStore, char[] storePassword, char[] keyPassword) {
this.keyStore = keyStore;
this.storePassword = storePassword;
this.keyPassword = keyPassword;
@@ -114,19 +110,26 @@ public final class TestKeyStore extends Assert {
this.trustManager = (TestTrustManager)trustManagers[0];
}
- public static KeyManager[] createKeyManagers(KeyStore keyStore, char[] storePassword)
- throws Exception {
- String kmfa = KeyManagerFactory.getDefaultAlgorithm();
- KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfa);
- kmf.init(keyStore, storePassword);
- return TestKeyManager.wrap(kmf.getKeyManagers());
+ public static KeyManager[] createKeyManagers(KeyStore keyStore, char[] storePassword) {
+ try {
+ String kmfa = KeyManagerFactory.getDefaultAlgorithm();
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfa);
+ kmf.init(keyStore, storePassword);
+ return TestKeyManager.wrap(kmf.getKeyManagers());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
- public static TrustManager[] createTrustManagers(final KeyStore keyStore) throws Exception {
- String tmfa = TrustManagerFactory.getDefaultAlgorithm();
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfa);
- tmf.init(keyStore);
- return TestTrustManager.wrap(tmf.getTrustManagers());
+ public static TrustManager[] createTrustManagers(final KeyStore keyStore) {
+ try {
+ String tmfa = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfa);
+ tmf.init(keyStore);
+ return TestTrustManager.wrap(tmf.getTrustManagers());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -136,40 +139,36 @@ public final class TestKeyStore extends Assert {
if (ROOT_CA != null) {
return;
}
- try {
- ROOT_CA = new Builder()
- .aliasPrefix("RootCA")
- .subject("Test Root Certificate Authority")
- .ca(true)
- .build();
- TestKeyStore intermediateCa = new Builder()
- .aliasPrefix("IntermediateCA")
- .subject("Test Intermediate Certificate Authority")
- .ca(true)
- .signer(ROOT_CA.getPrivateKey("RSA", "RSA"))
- .rootCa(ROOT_CA.getRootCertificate("RSA"))
- .build();
- SERVER = new Builder()
- .aliasPrefix("server")
- .signer(intermediateCa.getPrivateKey("RSA", "RSA"))
- .rootCa(intermediateCa.getRootCertificate("RSA"))
- .build();
- CLIENT = new TestKeyStore(createClient(intermediateCa.keyStore), null, null);
- CLIENT_CERTIFICATE = new Builder()
- .aliasPrefix("client")
- .subject("test@user")
- .signer(intermediateCa.getPrivateKey("RSA", "RSA"))
- .rootCa(intermediateCa.getRootCertificate("RSA"))
- .build();
- TestKeyStore rootCa2 = new Builder()
- .aliasPrefix("RootCA2")
- .subject("Test Root Certificate Authority 2")
- .ca(true)
- .build();
- CLIENT_2 = new TestKeyStore(createClient(rootCa2.keyStore), null, null);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ ROOT_CA = new Builder()
+ .aliasPrefix("RootCA")
+ .subject("CN=Test Root Certificate Authority")
+ .ca(true)
+ .build();
+ TestKeyStore intermediateCa = new Builder()
+ .aliasPrefix("IntermediateCA")
+ .subject("CN=Test Intermediate Certificate Authority")
+ .ca(true)
+ .signer(ROOT_CA.getPrivateKey("RSA", "RSA"))
+ .rootCa(ROOT_CA.getRootCertificate("RSA"))
+ .build();
+ SERVER = new Builder()
+ .aliasPrefix("server")
+ .signer(intermediateCa.getPrivateKey("RSA", "RSA"))
+ .rootCa(intermediateCa.getRootCertificate("RSA"))
+ .build();
+ CLIENT = new TestKeyStore(createClient(intermediateCa.keyStore), null, null);
+ CLIENT_CERTIFICATE = new Builder()
+ .aliasPrefix("client")
+ .subject("emailAddress=test@user")
+ .signer(intermediateCa.getPrivateKey("RSA", "RSA"))
+ .rootCa(intermediateCa.getRootCertificate("RSA"))
+ .build();
+ TestKeyStore rootCa2 = new Builder()
+ .aliasPrefix("RootCA2")
+ .subject("CN=Test Root Certificate Authority 2")
+ .ca(true)
+ .build();
+ CLIENT_2 = new TestKeyStore(createClient(rootCa2.keyStore), null, null);
}
/**
@@ -218,7 +217,7 @@ public final class TestKeyStore extends Assert {
private char[] storePassword;
private char[] keyPassword;
private String aliasPrefix;
- private X509Principal subject;
+ private X500Principal subject;
private int keyUsage;
private boolean ca;
private PrivateKeyEntry signer;
@@ -228,7 +227,7 @@ public final class TestKeyStore extends Assert {
= new Vector<GeneralSubtree>();
private final Vector<GeneralSubtree> excludedNameConstraints = new Vector<GeneralSubtree>();
- public Builder() throws Exception {
+ public Builder() {
subject = localhost();
}
@@ -251,13 +250,13 @@ public final class TestKeyStore extends Assert {
* Sets the subject common name. The default is the local host's
* canonical name.
*/
- public Builder subject(X509Principal subject) {
+ public Builder subject(X500Principal subject) {
this.subject = subject;
return this;
}
public Builder subject(String commonName) {
- return subject(x509Principal(commonName));
+ return subject(new X500Principal(commonName));
}
/** {@link KeyUsage} bit mask for 2.5.29.15 extension */
@@ -308,35 +307,39 @@ public final class TestKeyStore extends Assert {
new GeneralName(GeneralName.iPAddress, new DEROctetString(ipAddress)));
}
- public TestKeyStore build() throws Exception {
- if (StandardNames.IS_RI) {
- // JKS does not allow null password
- if (storePassword == null) {
- storePassword = "password".toCharArray();
- }
- if (keyPassword == null) {
- keyPassword = "password".toCharArray();
+ public TestKeyStore build() {
+ try {
+ if (StandardNames.IS_RI) {
+ // JKS does not allow null password
+ if (storePassword == null) {
+ storePassword = "password".toCharArray();
+ }
+ if (keyPassword == null) {
+ keyPassword = "password".toCharArray();
+ }
}
- }
- KeyStore keyStore = createKeyStore();
- for (String keyAlgorithm : keyAlgorithms) {
- String publicAlias = aliasPrefix + "-public-" + keyAlgorithm;
- String privateAlias = aliasPrefix + "-private-" + keyAlgorithm;
- if (keyAlgorithm.equals("EC_RSA") && signer == null && rootCa == null) {
- createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias,
- privateKey(keyStore, keyPassword, "RSA", "RSA"));
- continue;
+ KeyStore keyStore = createKeyStore();
+ for (String keyAlgorithm : keyAlgorithms) {
+ String publicAlias = aliasPrefix + "-public-" + keyAlgorithm;
+ String privateAlias = aliasPrefix + "-private-" + keyAlgorithm;
+ if (keyAlgorithm.equals("EC_RSA") && signer == null && rootCa == null) {
+ createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias,
+ privateKey(keyStore, keyPassword, "RSA", "RSA"));
+ continue;
+ }
+ createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, signer);
}
- createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, signer);
- }
- if (rootCa != null) {
- keyStore.setCertificateEntry(aliasPrefix
- + "-root-ca-"
- + rootCa.getPublicKey().getAlgorithm(),
- rootCa);
+ if (rootCa != null) {
+ keyStore.setCertificateEntry(aliasPrefix
+ + "-root-ca-"
+ + rootCa.getPublicKey().getAlgorithm(),
+ rootCa);
+ }
+ return new TestKeyStore(keyStore, storePassword, keyPassword);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
- return new TestKeyStore(keyStore, storePassword, keyPassword);
}
/**
@@ -382,20 +385,15 @@ public final class TestKeyStore extends Assert {
} else {
// 1.) we make the keys
int keySize;
- String signatureAlgorithm;
if (keyAlgorithm.equals("RSA")) {
keySize = StandardNames.IS_RI ? 1024 : 512; // 512 breaks SSL_RSA_EXPORT_* on RI
- signatureAlgorithm = "sha1WithRSA";
} else if (keyAlgorithm.equals("DSA")) {
keySize = 512;
- signatureAlgorithm = "sha1WithDSA";
} else if (keyAlgorithm.equals("EC")) {
keySize = 256;
- signatureAlgorithm = "sha1WithECDSA";
} else if (keyAlgorithm.equals("EC_RSA")) {
keySize = 256;
keyAlgorithm = "EC";
- signatureAlgorithm = "sha1WithRSA";
} else {
throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
}
@@ -406,76 +404,15 @@ public final class TestKeyStore extends Assert {
KeyPair kp = kpg.generateKeyPair();
privateKey = kp.getPrivate();
PublicKey publicKey = kp.getPublic();
- // 2.) use keys to make certificate
-
- // note that there doesn't seem to be a standard way to make a
- // certificate using java.* or javax.*. The CertificateFactory
- // interface assumes you want to read in a stream of bytes a
- // factory specific format. So here we use Bouncy Castle's
- // X509V3CertificateGenerator and related classes.
- X509Principal issuer;
- if (caCert == null) {
- issuer = subject;
- } else {
- Principal xp = caCert.getSubjectDN();
- issuer = new X509Principal(new X509Name(xp.getName()));
- }
-
- long millisPerDay = 24 * 60 * 60 * 1000;
- long now = System.currentTimeMillis();
- Date start = new Date(now - millisPerDay);
- Date end = new Date(now + millisPerDay);
- BigInteger serial = BigInteger.valueOf(1);
-
- X509V3CertificateGenerator x509cg = new X509V3CertificateGenerator();
- x509cg.setSubjectDN(subject);
- x509cg.setIssuerDN(issuer);
- x509cg.setNotBefore(start);
- x509cg.setNotAfter(end);
- x509cg.setPublicKey(publicKey);
- x509cg.setSignatureAlgorithm(signatureAlgorithm);
- x509cg.setSerialNumber(serial);
- if (keyUsage != 0) {
- x509cg.addExtension(X509Extensions.KeyUsage,
- true,
- new KeyUsage(keyUsage));
- }
- if (ca) {
- x509cg.addExtension(X509Extensions.BasicConstraints,
- true,
- new BasicConstraints(true));
- }
- for (GeneralName subjectAltName : subjectAltNames) {
- x509cg.addExtension(X509Extensions.SubjectAlternativeName, false,
- new GeneralNames(subjectAltName).getEncoded());
- }
- if (!permittedNameConstraints.isEmpty() || !excludedNameConstraints.isEmpty()) {
- x509cg.addExtension(X509Extensions.NameConstraints, true,
- new NameConstraints(permittedNameConstraints, excludedNameConstraints));
- }
+ // 2.) use keys to make certificate
+ X500Principal issuer = ((caCert != null)
+ ? caCert.getSubjectX500Principal()
+ : subject);
PrivateKey signingKey = (caKey == null) ? privateKey : caKey;
- if (signingKey instanceof ECPrivateKey) {
- /*
- * bouncycastle needs its own ECPrivateKey implementation
- */
- KeyFactory kf = KeyFactory.getInstance(keyAlgorithm, "BC");
- PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(signingKey.getEncoded());
- signingKey = kf.generatePrivate(ks);
- }
- x509c = x509cg.generateX509Certificate(signingKey);
- if (StandardNames.IS_RI) {
- /*
- * The RI can't handle the BC EC signature algorithm
- * string of "ECDSA", since it expects "...WITHEC...",
- * so convert from BC to RI X509Certificate
- * implementation via bytes.
- */
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- ByteArrayInputStream bais = new ByteArrayInputStream(x509c.getEncoded());
- Certificate c = cf.generateCertificate(bais);
- x509c = (X509Certificate) c;
- }
+ x509c = createCertificate(publicKey, signingKey, subject, issuer, keyUsage, ca,
+ subjectAltNames,
+ permittedNameConstraints, excludedNameConstraints);
}
X509Certificate[] x509cc;
@@ -500,20 +437,119 @@ public final class TestKeyStore extends Assert {
return keyStore;
}
- private X509Principal localhost() throws UnknownHostException {
- return x509Principal(InetAddress.getLoopbackAddress().getHostName());
+ private X500Principal localhost() {
+ return new X500Principal("CN=" + InetAddress.getLoopbackAddress().getHostName());
}
+ }
- /**
- * Create an X509Principal with the given attributes
- */
- public static X509Principal x509Principal(String commonName) {
- Hashtable attributes = new Hashtable();
- attributes.put(X509Principal.CN, commonName);
- return new X509Principal(attributes);
+ public static X509Certificate createCA(PublicKey publicKey,
+ PrivateKey privateKey,
+ String subject) {
+ try {
+ X500Principal principal = new X500Principal(subject);
+ return createCertificate(publicKey, privateKey,
+ principal, principal,
+ 0, true,
+ new Vector<GeneralName>(),
+ new Vector<GeneralSubtree>(),
+ new Vector<GeneralSubtree>());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
}
+ private static X509Certificate createCertificate(
+ PublicKey publicKey,
+ PrivateKey privateKey,
+ X500Principal subject,
+ X500Principal issuer,
+ int keyUsage,
+ boolean ca,
+ List<GeneralName> subjectAltNames,
+ Vector<GeneralSubtree> permittedNameConstraints,
+ Vector<GeneralSubtree> excludedNameConstraints) throws Exception {
+ // Note that there is no way to programmatically make a
+ // Certificate using java.* or javax.* APIs. The
+ // CertificateFactory interface assumes you want to read
+ // in a stream of bytes, typically the X.509 factory would
+ // allow ASN.1 DER encoded bytes and optionally some PEM
+ // formats. Here we use Bouncy Castle's
+ // X509V3CertificateGenerator and related classes.
+
+ long millisPerDay = 24 * 60 * 60 * 1000;
+ long now = System.currentTimeMillis();
+ Date start = new Date(now - millisPerDay);
+ Date end = new Date(now + millisPerDay);
+ BigInteger serial = BigInteger.valueOf(1);
+
+ String keyAlgorithm = privateKey.getAlgorithm();
+ String signatureAlgorithm;
+ if (keyAlgorithm.equals("RSA")) {
+ signatureAlgorithm = "sha1WithRSA";
+ } else if (keyAlgorithm.equals("DSA")) {
+ signatureAlgorithm = "sha1WithDSA";
+ } else if (keyAlgorithm.equals("EC")) {
+ signatureAlgorithm = "sha1WithECDSA";
+ } else if (keyAlgorithm.equals("EC_RSA")) {
+ signatureAlgorithm = "sha1WithRSA";
+ } else {
+ throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+ }
+
+ X509V3CertificateGenerator x509cg = new X509V3CertificateGenerator();
+ x509cg.setSubjectDN(subject);
+ x509cg.setIssuerDN(issuer);
+ x509cg.setNotBefore(start);
+ x509cg.setNotAfter(end);
+ x509cg.setPublicKey(publicKey);
+ x509cg.setSignatureAlgorithm(signatureAlgorithm);
+ x509cg.setSerialNumber(serial);
+ if (keyUsage != 0) {
+ x509cg.addExtension(X509Extensions.KeyUsage,
+ true,
+ new KeyUsage(keyUsage));
+ }
+ if (ca) {
+ x509cg.addExtension(X509Extensions.BasicConstraints,
+ true,
+ new BasicConstraints(true));
+ }
+ for (GeneralName subjectAltName : subjectAltNames) {
+ x509cg.addExtension(X509Extensions.SubjectAlternativeName,
+ false,
+ new GeneralNames(subjectAltName).getEncoded());
+ }
+ if (!permittedNameConstraints.isEmpty() || !excludedNameConstraints.isEmpty()) {
+ x509cg.addExtension(X509Extensions.NameConstraints,
+ true,
+ new NameConstraints(permittedNameConstraints,
+ excludedNameConstraints));
+ }
+
+ if (privateKey instanceof ECPrivateKey) {
+ /*
+ * bouncycastle needs its own ECPrivateKey implementation
+ */
+ KeyFactory kf = KeyFactory.getInstance(keyAlgorithm, "BC");
+ PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(privateKey.getEncoded());
+ privateKey = kf.generatePrivate(ks);
+ }
+ X509Certificate x509c = x509cg.generateX509Certificate(privateKey);
+ if (StandardNames.IS_RI) {
+ /*
+ * The RI can't handle the BC EC signature algorithm
+ * string of "ECDSA", since it expects "...WITHEC...",
+ * so convert from BC to RI X509Certificate
+ * implementation via bytes.
+ */
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ ByteArrayInputStream bais = new ByteArrayInputStream(x509c.getEncoded());
+ Certificate c = cf.generateCertificate(bais);
+ x509c = (X509Certificate) c;
+ }
+ return x509c;
+ }
+
/**
* Return the key algorithm for a possible compound algorithm
* identifier containing an underscore. If not underscore is
@@ -551,10 +587,14 @@ public final class TestKeyStore extends Assert {
* keyStorePassword argument, which can be null if a password is
* not desired.
*/
- public static KeyStore createKeyStore() throws Exception {
- KeyStore keyStore = KeyStore.getInstance(StandardNames.KEY_STORE_ALGORITHM);
- keyStore.load(null, null);
- return keyStore;
+ public static KeyStore createKeyStore() {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(StandardNames.KEY_STORE_ALGORITHM);
+ keyStore.load(null, null);
+ return keyStore;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -562,8 +602,7 @@ public final class TestKeyStore extends Assert {
* algorithms. Throws IllegalStateException if there are are more
* or less than one.
*/
- public PrivateKeyEntry getPrivateKey(String keyAlgorithm, String signatureAlgorithm)
- throws Exception {
+ public PrivateKeyEntry getPrivateKey(String keyAlgorithm, String signatureAlgorithm) {
return privateKey(keyStore, keyPassword, keyAlgorithm, signatureAlgorithm);
}
@@ -573,36 +612,40 @@ public final class TestKeyStore extends Assert {
* or less than one.
*/
public static PrivateKeyEntry privateKey(KeyStore keyStore, char[] keyPassword,
- String keyAlgorithm, String signatureAlgorithm) throws Exception {
- PrivateKeyEntry found = null;
- PasswordProtection password = new PasswordProtection(keyPassword);
- for (String alias : Collections.list(keyStore.aliases())) {
- if (!keyStore.entryInstanceOf(alias, PrivateKeyEntry.class)) {
- continue;
- }
- PrivateKeyEntry privateKey = (PrivateKeyEntry) keyStore.getEntry(alias, password);
- if (!privateKey.getPrivateKey().getAlgorithm().equals(keyAlgorithm)) {
- continue;
- }
- X509Certificate certificate = (X509Certificate) privateKey.getCertificate();
- if (!certificate.getSigAlgName().contains(signatureAlgorithm)) {
- continue;
+ String keyAlgorithm, String signatureAlgorithm) {
+ try {
+ PrivateKeyEntry found = null;
+ PasswordProtection password = new PasswordProtection(keyPassword);
+ for (String alias : Collections.list(keyStore.aliases())) {
+ if (!keyStore.entryInstanceOf(alias, PrivateKeyEntry.class)) {
+ continue;
+ }
+ PrivateKeyEntry privateKey = (PrivateKeyEntry) keyStore.getEntry(alias, password);
+ if (!privateKey.getPrivateKey().getAlgorithm().equals(keyAlgorithm)) {
+ continue;
+ }
+ X509Certificate certificate = (X509Certificate) privateKey.getCertificate();
+ if (!certificate.getSigAlgName().contains(signatureAlgorithm)) {
+ continue;
+ }
+ if (found != null) {
+ throw new IllegalStateException("KeyStore has more than one private key for "
+ + " keyAlgorithm: " + keyAlgorithm
+ + " signatureAlgorithm: " + signatureAlgorithm
+ + "\nfirst: " + found.getPrivateKey()
+ + "\nsecond: " + privateKey.getPrivateKey() );
+ }
+ found = privateKey;
}
- if (found != null) {
- throw new IllegalStateException("KeyStore has more than one private key for "
+ if (found == null) {
+ throw new IllegalStateException("KeyStore contained no private key for "
+ " keyAlgorithm: " + keyAlgorithm
- + " signatureAlgorithm: " + signatureAlgorithm
- + "\nfirst: " + found.getPrivateKey()
- + "\nsecond: " + privateKey.getPrivateKey() );
+ + " signatureAlgorithm: " + signatureAlgorithm);
}
- found = privateKey;
- }
- if (found == null) {
- throw new IllegalStateException("KeyStore contained no private key for "
- + " keyAlgorithm: " + keyAlgorithm
- + " signatureAlgorithm: " + signatureAlgorithm);
+ return found;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
- return found;
}
/**
@@ -660,7 +703,7 @@ public final class TestKeyStore extends Assert {
* for the given algorithm. Throws IllegalStateException if there
* are are more or less than one.
*/
- public Certificate getRootCertificate(String algorithm) throws Exception {
+ public X509Certificate getRootCertificate(String algorithm) {
return rootCertificate(keyStore, algorithm);
}
@@ -669,44 +712,47 @@ public final class TestKeyStore extends Assert {
* the given algorithm. Throws IllegalStateException if there are
* are more or less than one.
*/
- public static Certificate rootCertificate(KeyStore keyStore, String algorithm)
- throws Exception {
- Certificate found = null;
- for (String alias : Collections.list(keyStore.aliases())) {
- if (!keyStore.entryInstanceOf(alias, TrustedCertificateEntry.class)) {
- continue;
- }
- TrustedCertificateEntry certificateEntry =
- (TrustedCertificateEntry) keyStore.getEntry(alias, null);
- Certificate certificate = certificateEntry.getTrustedCertificate();
- if (!certificate.getPublicKey().getAlgorithm().equals(algorithm)) {
- continue;
- }
- if (!(certificate instanceof X509Certificate)) {
- continue;
- }
- X509Certificate x = (X509Certificate) certificate;
- if (!x.getIssuerDN().equals(x.getSubjectDN())) {
- continue;
+ public static X509Certificate rootCertificate(KeyStore keyStore, String algorithm) {
+ try {
+ X509Certificate found = null;
+ for (String alias : Collections.list(keyStore.aliases())) {
+ if (!keyStore.entryInstanceOf(alias, TrustedCertificateEntry.class)) {
+ continue;
+ }
+ TrustedCertificateEntry certificateEntry =
+ (TrustedCertificateEntry) keyStore.getEntry(alias, null);
+ Certificate certificate = certificateEntry.getTrustedCertificate();
+ if (!certificate.getPublicKey().getAlgorithm().equals(algorithm)) {
+ continue;
+ }
+ if (!(certificate instanceof X509Certificate)) {
+ continue;
+ }
+ X509Certificate x = (X509Certificate) certificate;
+ if (!x.getIssuerDN().equals(x.getSubjectDN())) {
+ continue;
+ }
+ if (found != null) {
+ throw new IllegalStateException("KeyStore has more than one root CA for "
+ + algorithm
+ + "\nfirst: " + found
+ + "\nsecond: " + certificate );
+ }
+ found = x;
}
- if (found != null) {
- throw new IllegalStateException("KeyStore has more than one root CA for "
- + algorithm
- + "\nfirst: " + found
- + "\nsecond: " + certificate );
+ if (found == null) {
+ throw new IllegalStateException("KeyStore contained no root CA for " + algorithm);
}
- found = certificate;
- }
- if (found == null) {
- throw new IllegalStateException("KeyStore contained no root CA for " + algorithm);
+ return found;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
- return found;
}
/**
* Create a client key store that only contains self-signed certificates but no private keys
*/
- public static KeyStore createClient(KeyStore caKeyStore) throws Exception {
+ public static KeyStore createClient(KeyStore caKeyStore) {
KeyStore clientKeyStore = createKeyStore();
copySelfSignedCertificates(clientKeyStore, caKeyStore);
return clientKeyStore;
@@ -716,20 +762,24 @@ public final class TestKeyStore extends Assert {
* Copy self-signed certificates from one key store to another.
* Returns true if successful, false if no match found.
*/
- public static boolean copySelfSignedCertificates(KeyStore dst, KeyStore src) throws Exception {
- boolean copied = false;
- for (String alias : Collections.list(src.aliases())) {
- if (!src.isCertificateEntry(alias)) {
- continue;
- }
- X509Certificate cert = (X509Certificate)src.getCertificate(alias);
- if (!cert.getSubjectDN().equals(cert.getIssuerDN())) {
- continue;
+ public static boolean copySelfSignedCertificates(KeyStore dst, KeyStore src) {
+ try {
+ boolean copied = false;
+ for (String alias : Collections.list(src.aliases())) {
+ if (!src.isCertificateEntry(alias)) {
+ continue;
+ }
+ X509Certificate cert = (X509Certificate)src.getCertificate(alias);
+ if (!cert.getSubjectDN().equals(cert.getIssuerDN())) {
+ continue;
+ }
+ dst.setCertificateEntry(alias, cert);
+ copied = true;
}
- dst.setCertificateEntry(alias, cert);
- copied = true;
+ return copied;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
- return copied;
}
/**