blob: e2d8828cb554f4129059ebf865516366edb2ca77 [file] [log] [blame]
/*
* Copyright (C) 2010 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 libcore.java.security;
import java.io.PrintStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.Hashtable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import junit.framework.Assert;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
/**
* TestKeyStore is a convenience class for other tests that
* want a canned KeyStore with a variety of key pairs.
*
* Creating a key store is relatively slow, so a singleton instance is
* accessible via TestKeyStore.get().
*/
public final class TestKeyStore extends Assert {
static {
if (StandardNames.IS_RI) {
// Needed to create BKS keystore
Security.addProvider(new BouncyCastleProvider());
}
}
public final KeyStore keyStore;
public final char[] storePassword;
public final char[] keyPassword;
public final KeyManager[] keyManagers;
public final TrustManager[] trustManagers;
private TestKeyStore(KeyStore keyStore,
char[] storePassword,
char[] keyPassword) {
this.keyStore = keyStore;
this.storePassword = storePassword;
this.keyPassword = keyPassword;
this.keyManagers = createKeyManagers(keyStore, storePassword);
this.trustManagers = createTrustManagers(keyStore);
}
public static KeyManager[] createKeyManagers(final KeyStore keyStore,
final char[] storePassword) {
try {
String kmfa = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfa);
kmf.init(keyStore, storePassword);
return kmf.getKeyManagers();
} catch (Exception e) {
throw new RuntimeException();
}
}
public static TrustManager[] createTrustManagers(final KeyStore keyStore) {
try {
String tmfa = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfa);
tmf.init(keyStore);
return tmf.getTrustManagers();
} catch (Exception e) {
throw new RuntimeException();
}
}
private static final TestKeyStore ROOT_CA
= create(new String[] { "RSA" },
null,
null,
"RootCA",
x509Principal("Test Root Certificate Authority"),
true,
null);
private static final TestKeyStore INTERMEDIATE_CA
= create(new String[] { "RSA" },
null,
null,
"IntermediateCA",
x509Principal("Test Intermediate Certificate Authority"),
true,
ROOT_CA);
private static final TestKeyStore SERVER
= create(new String[] { "RSA" },
null,
null,
"server",
localhost(),
false,
INTERMEDIATE_CA);
private static final TestKeyStore CLIENT
= new TestKeyStore(createClient(INTERMEDIATE_CA.keyStore), null, null);
private static final TestKeyStore CLIENT_CERTIFICATE
= create(new String[] { "RSA" },
null,
null,
"client",
x509Principal("test@user"),
false,
INTERMEDIATE_CA);
private static final TestKeyStore ROOT_CA_2
= create(new String[] { "RSA" },
null,
null,
"RootCA2",
x509Principal("Test Root Certificate Authority 2"),
true,
null);
private static final TestKeyStore CLIENT_2
= new TestKeyStore(createClient(ROOT_CA_2.keyStore), null, null);
/**
* Return a server keystore with a matched RSA certificate and
* private key as well as a CA certificate.
*/
public static TestKeyStore getServer() {
return SERVER;
}
/**
* Return a keystore with a CA certificate
*/
public static TestKeyStore getClient() {
return CLIENT;
}
/**
* Return a client keystore with a matched RSA certificate and
* private key as well as a CA certificate.
*/
public static TestKeyStore getClientCertificate() {
return CLIENT_CERTIFICATE;
}
/**
* Return a keystore with a second CA certificate that does not
* trust the server certificate returned by getServer for negative
* testing.
*/
public static TestKeyStore getClientCA2() {
return CLIENT_2;
}
/**
* Create a new KeyStore containing the requested key types.
* Since key generation can be expensive, most tests should reuse
* the RSA-only singleton instance returned by TestKeyStore.get
*
* @param keyAlgorithms The requested key types to generate and include
* @param keyStorePassword Password used to protect the private key
* @param aliasPrefix A unique prefix to identify the key aliases
* @param ca true If the keys being created are for a CA
* @param signer If non-null, key store used for signing key, otherwise self-signed
*/
public static TestKeyStore create(String[] keyAlgorithms,
char[] storePassword,
char[] keyPassword,
String aliasPrefix,
X509Principal subject,
boolean ca,
TestKeyStore signer) {
try {
KeyStore keyStore = createKeyStore();
for (String keyAlgorithm : keyAlgorithms) {
String publicAlias = aliasPrefix + "-public-" + keyAlgorithm;
String privateAlias = aliasPrefix + "-private-" + keyAlgorithm;
createKeys(keyStore, keyPassword,
keyAlgorithm,
publicAlias, privateAlias,
subject,
ca,
signer);
}
if (signer != null) {
copySelfSignedCertificates(keyStore, signer.keyStore);
}
return new TestKeyStore(keyStore, storePassword, keyPassword);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Create an empty BKS KeyStore
*
* The KeyStore is optionally password protected by the
* keyStorePassword argument, which can be null if a password is
* not desired.
*/
public static KeyStore createKeyStore() throws Exception {
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
return keyStore;
}
/**
* Add newly generated keys of a given key type to an existing
* KeyStore. The PrivateKey will be stored under the specified
* private alias name. The X509Certificate will be stored on the
* public alias name and have the given subject distiguished
* name.
*
* If a CA is provided, it will be used to sign the generated
* certificate. Otherwise, the certificate will be self
* signed. The certificate will be valid for one day before and
* one day after the time of creation.
*
* Based on:
* org.bouncycastle.jce.provider.test.SigTest
* org.bouncycastle.jce.provider.test.CertTest
*/
public static KeyStore createKeys(KeyStore keyStore,
char[] keyPassword,
String keyAlgorithm,
String publicAlias,
String privateAlias,
X509Principal subject,
boolean ca,
TestKeyStore signer) throws Exception {
PrivateKey caKey;
X509Certificate caCert;
X509Certificate[] caCertChain;
if (signer == null) {
caKey = null;
caCert = null;
caCertChain = null;
} else {
PrivateKeyEntry privateKeyEntry
= privateKey(signer.keyStore, signer.keyPassword, keyAlgorithm);
caKey = privateKeyEntry.getPrivateKey();
caCert = (X509Certificate)privateKeyEntry.getCertificate();
caCertChain = (X509Certificate[])privateKeyEntry.getCertificateChain();
}
PrivateKey privateKey;
X509Certificate x509c;
if (publicAlias == null && privateAlias == null) {
// don't want anything apparently
privateKey = null;
x509c = null;
} else {
// 1.) we make the keys
int keysize = 1024;
KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgorithm);
kpg.initialize(keysize, new SecureRandom());
KeyPair kp = kpg.generateKeyPair();
privateKey = (PrivateKey)kp.getPrivate();
PublicKey publicKey = (PublicKey)kp.getPublic();
// 2.) use keys to make certficate
// 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 {
issuer = (X509Principal) caCert.getSubjectDN();
}
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("sha1With" + keyAlgorithm);
x509cg.setSerialNumber(serial);
if (ca) {
x509cg.addExtension(X509Extensions.BasicConstraints,
true,
new BasicConstraints(true));
}
PrivateKey signingKey = (caKey == null) ? privateKey : caKey;
x509c = x509cg.generateX509Certificate(signingKey);
}
X509Certificate[] x509cc;
if (privateAlias == null) {
// don't need certificate chain
x509cc = null;
} else if (caCertChain == null) {
x509cc = new X509Certificate[] { x509c };
} else {
x509cc = new X509Certificate[caCertChain.length+1];
x509cc[0] = x509c;
System.arraycopy(caCertChain, 0, x509cc, 1, caCertChain.length);
}
// 3.) put certificate and private key into the key store
if (privateAlias != null) {
keyStore.setKeyEntry(privateAlias, privateKey, keyPassword, x509cc);
}
if (publicAlias != null) {
keyStore.setCertificateEntry(publicAlias, x509c);
}
return keyStore;
}
/**
* Create an X509Principal with the given attributes
*/
public static X509Principal localhost() {
try {
return x509Principal(InetAddress.getLocalHost().getCanonicalHostName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 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);
}
/**
* Return the only private key in a keystore for the given
* algorithm. Throws IllegalStateException if there are are more
* or less than one.
*/
public static PrivateKeyEntry privateKey(KeyStore keyStore,
char[] keyPassword,
String algorithm) {
try {
PrivateKeyEntry found = null;
PasswordProtection password = new PasswordProtection(keyPassword);
for (String alias: Collections.list(keyStore.aliases())) {
if (!keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) {
continue;
}
PrivateKeyEntry privateKey = (PrivateKeyEntry) keyStore.getEntry(alias, password);
if (!privateKey.getPrivateKey().getAlgorithm().equals(algorithm)) {
continue;
}
if (found != null) {
throw new IllegalStateException("keyStore has more than one private key");
}
found = privateKey;
}
if (found == null) {
throw new IllegalStateException("keyStore contained no private key");
}
return found;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Create a client key store that only contains self-signed certificates but no private keys
*/
public static KeyStore createClient(KeyStore caKeyStore) {
try {
KeyStore clientKeyStore = clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(null, null);
copySelfSignedCertificates(clientKeyStore, caKeyStore);
return clientKeyStore;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Copy self-signed certifcates from one key store to another
*/
public static void copySelfSignedCertificates(KeyStore dst, KeyStore src) throws Exception {
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);
}
}
/**
* Dump a key store for debugging.
*/
public static void dump(String context,
KeyStore keyStore,
char[] keyPassword) {
try {
PrintStream out = System.out;
out.println("context=" + context);
out.println("\tkeyStore=" + keyStore);
out.println("\tkeyStore.type=" + keyStore.getType());
out.println("\tkeyStore.provider=" + keyStore.getProvider());
out.println("\tkeyPassword="
+ ((keyPassword == null) ? null : new String(keyPassword)));
out.println("\tsize=" + keyStore.size());
for (String alias: Collections.list(keyStore.aliases())) {
out.println("alias=" + alias);
out.println("\tcreationDate=" + keyStore.getCreationDate(alias));
if (keyStore.isCertificateEntry(alias)) {
out.println("\tcertificate:");
out.println("==========================================");
out.println(keyStore.getCertificate(alias));
out.println("==========================================");
continue;
}
if (keyStore.isKeyEntry(alias)) {
out.println("\tkey:");
out.println("==========================================");
String key;
try {
key = ("Key retreived using password\n"
+ keyStore.getKey(alias, keyPassword).toString());
} catch (UnrecoverableKeyException e1) {
try {
key = ("Key retreived without password\n"
+ keyStore.getKey(alias, null).toString());
} catch (UnrecoverableKeyException e2) {
key = "Key could not be retreived";
}
}
out.println(key);
out.println("==========================================");
continue;
}
out.println("\tunknown entry type");
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void assertChainLength(Object[] chain) {
/*
* Note chain is Object[] to support both
* java.security.cert.X509Certificate and
* javax.security.cert.X509Certificate
*/
assertEquals(3, chain.length);
}
}