| /* |
| * 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 static org.junit.Assert.assertEquals; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.math.BigInteger; |
| 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.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.Principal; |
| import java.security.PrivateKey; |
| import java.security.PublicKey; |
| import java.security.SecureRandom; |
| import java.security.Security; |
| import java.security.UnrecoverableEntryException; |
| import java.security.UnrecoverableKeyException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.AlgorithmParameterSpec; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import javax.crypto.spec.DHParameterSpec; |
| 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 libcore.javax.net.ssl.TestKeyManager; |
| import libcore.javax.net.ssl.TestTrustManager; |
| import org.bouncycastle.asn1.DEROctetString; |
| import org.bouncycastle.asn1.x500.X500Name; |
| import org.bouncycastle.asn1.x509.BasicConstraints; |
| import org.bouncycastle.asn1.x509.CRLReason; |
| import org.bouncycastle.asn1.x509.ExtendedKeyUsage; |
| import org.bouncycastle.asn1.x509.Extension; |
| import org.bouncycastle.asn1.x509.GeneralName; |
| import org.bouncycastle.asn1.x509.GeneralNames; |
| import org.bouncycastle.asn1.x509.GeneralSubtree; |
| import org.bouncycastle.asn1.x509.KeyPurposeId; |
| import org.bouncycastle.asn1.x509.KeyUsage; |
| import org.bouncycastle.asn1.x509.NameConstraints; |
| import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; |
| import org.bouncycastle.cert.X509CertificateHolder; |
| import org.bouncycastle.cert.X509v3CertificateBuilder; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; |
| import org.bouncycastle.cert.ocsp.BasicOCSPResp; |
| import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder; |
| import org.bouncycastle.cert.ocsp.CertificateID; |
| import org.bouncycastle.cert.ocsp.CertificateStatus; |
| import org.bouncycastle.cert.ocsp.OCSPResp; |
| import org.bouncycastle.cert.ocsp.OCSPRespBuilder; |
| import org.bouncycastle.cert.ocsp.RevokedStatus; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| import org.bouncycastle.operator.DigestCalculatorProvider; |
| import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; |
| import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; |
| |
| /** |
| * 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 { |
| /** Size of DSA keys to generate for testing. */ |
| private static final int DSA_KEY_SIZE_BITS = 1024; |
| |
| /** Size of EC keys to generate for testing. */ |
| private static final int EC_KEY_SIZE_BITS = 256; |
| |
| /** Size of RSA keys to generate for testing. */ |
| private static final int RSA_KEY_SIZE_BITS = 1024; |
| |
| // Generated with: openssl dhparam -C 1024 |
| private static final BigInteger DH_PARAMS_P = new BigInteger(1, |
| new byte[] { |
| (byte) 0xA2, (byte) 0x31, (byte) 0xB4, (byte) 0xB3, (byte) 0x6D, (byte) 0x9B, |
| (byte) 0x7E, (byte) 0xF4, (byte) 0xE7, (byte) 0x21, (byte) 0x51, (byte) 0x40, |
| (byte) 0xEB, (byte) 0xC6, (byte) 0xB6, (byte) 0xD6, (byte) 0x54, (byte) 0x56, |
| (byte) 0x72, (byte) 0xBE, (byte) 0x43, (byte) 0x18, (byte) 0x30, (byte) 0x5C, |
| (byte) 0x15, (byte) 0x5A, (byte) 0xF9, (byte) 0x19, (byte) 0x62, (byte) 0xAD, |
| (byte) 0xF4, (byte) 0x29, (byte) 0xCB, (byte) 0xC6, (byte) 0xF6, (byte) 0x64, |
| (byte) 0x0B, (byte) 0x9D, (byte) 0x23, (byte) 0x80, (byte) 0xF9, (byte) 0x5B, |
| (byte) 0x1C, (byte) 0x1C, (byte) 0x6A, (byte) 0xB4, (byte) 0xEA, (byte) 0xB9, |
| (byte) 0x80, (byte) 0x98, (byte) 0x8B, (byte) 0xAF, (byte) 0x15, (byte) 0xA8, |
| (byte) 0x5C, (byte) 0xC4, (byte) 0xB0, (byte) 0x41, (byte) 0x29, (byte) 0x66, |
| (byte) 0x9F, (byte) 0x9F, (byte) 0x1F, (byte) 0x88, (byte) 0x50, (byte) 0x97, |
| (byte) 0x38, (byte) 0x0B, (byte) 0x01, (byte) 0x16, (byte) 0xD6, (byte) 0x84, |
| (byte) 0x1D, (byte) 0x48, (byte) 0x6F, (byte) 0x7C, (byte) 0x06, (byte) 0x8C, |
| (byte) 0x6E, (byte) 0x68, (byte) 0xCD, (byte) 0x38, (byte) 0xE6, (byte) 0x22, |
| (byte) 0x30, (byte) 0x61, (byte) 0x37, (byte) 0x02, (byte) 0x3D, (byte) 0x47, |
| (byte) 0x62, (byte) 0xCE, (byte) 0xB9, (byte) 0x1A, (byte) 0x69, (byte) 0x9D, |
| (byte) 0xA1, (byte) 0x9F, (byte) 0x10, (byte) 0xA1, (byte) 0xAA, (byte) 0x70, |
| (byte) 0xF7, (byte) 0x27, (byte) 0x9C, (byte) 0xD4, (byte) 0xA5, (byte) 0x15, |
| (byte) 0xE2, (byte) 0x15, (byte) 0x0C, (byte) 0x20, (byte) 0x90, (byte) 0x08, |
| (byte) 0xB6, (byte) 0xF5, (byte) 0xDF, (byte) 0x1C, (byte) 0xCB, (byte) 0x82, |
| (byte) 0x6D, (byte) 0xC0, (byte) 0xE1, (byte) 0xBD, (byte) 0xCC, (byte) 0x4A, |
| (byte) 0x76, (byte) 0xE3, |
| }); |
| |
| // generator of 2 |
| private static final BigInteger DH_PARAMS_G = BigInteger.valueOf(2); |
| |
| private static TestKeyStore ROOT_CA; |
| private static TestKeyStore INTERMEDIATE_CA; |
| private static TestKeyStore INTERMEDIATE_CA_2; |
| private static TestKeyStore INTERMEDIATE_CA_EC; |
| |
| private static TestKeyStore SERVER; |
| private static TestKeyStore CLIENT; |
| private static TestKeyStore CLIENT_CERTIFICATE; |
| private static TestKeyStore CLIENT_EC_RSA_CERTIFICATE; |
| private static TestKeyStore CLIENT_EC_EC_CERTIFICATE; |
| |
| private static TestKeyStore CLIENT_2; |
| |
| static { |
| if (StandardNames.IS_RI) { |
| // Needed to create BKS keystore but add at end so most |
| // algorithm come from the default providers |
| Security.insertProviderAt( |
| new BouncyCastleProvider(), Security.getProviders().length + 1); |
| } else if (!BouncyCastleProvider.class.getName().startsWith("com.android")) { |
| // If we run outside of the Android system, we need to make sure |
| // that the BouncyCastleProvider's static field keyInfoConverters |
| // is initialized. This happens in the default constructor only. |
| new BouncyCastleProvider(); |
| } |
| } |
| |
| private static final byte[] LOCAL_HOST_ADDRESS = {127, 0, 0, 1}; |
| private static final String LOCAL_HOST_NAME = "localhost"; |
| |
| public final KeyStore keyStore; |
| public final char[] storePassword; |
| public final char[] keyPassword; |
| public final KeyManager[] keyManagers; |
| public final TrustManager[] trustManagers; |
| public final TestTrustManager trustManager; |
| |
| 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); |
| this.trustManager = (TestTrustManager) trustManagers[0]; |
| } |
| |
| 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) { |
| 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); |
| } |
| } |
| |
| /** |
| * Lazily create shared test certificates. |
| */ |
| private static synchronized void initCerts() { |
| if (ROOT_CA != null) { |
| return; |
| } |
| ROOT_CA = new Builder() |
| .aliasPrefix("RootCA") |
| .subject("CN=Test Root Certificate Authority") |
| .ca(true) |
| .certificateSerialNumber(BigInteger.valueOf(1)) |
| .build(); |
| INTERMEDIATE_CA_EC = new Builder() |
| .aliasPrefix("IntermediateCA-EC") |
| .keyAlgorithms("EC") |
| .subject("CN=Test Intermediate Certificate Authority ECDSA") |
| .ca(true) |
| .signer(ROOT_CA.getPrivateKey("RSA", "RSA")) |
| .rootCa(ROOT_CA.getRootCertificate("RSA")) |
| .certificateSerialNumber(BigInteger.valueOf(2)) |
| .build(); |
| INTERMEDIATE_CA = new Builder() |
| .aliasPrefix("IntermediateCA") |
| .subject("CN=Test Intermediate Certificate Authority") |
| .ca(true) |
| .signer(ROOT_CA.getPrivateKey("RSA", "RSA")) |
| .rootCa(ROOT_CA.getRootCertificate("RSA")) |
| .certificateSerialNumber(BigInteger.valueOf(2)) |
| .build(); |
| SERVER = new Builder() |
| .aliasPrefix("server") |
| .signer(INTERMEDIATE_CA.getPrivateKey("RSA", "RSA")) |
| .rootCa(INTERMEDIATE_CA.getRootCertificate("RSA")) |
| .addSubjectAltNameIpAddress(LOCAL_HOST_ADDRESS) |
| .certificateSerialNumber(BigInteger.valueOf(3)) |
| .build(); |
| CLIENT = new TestKeyStore(createClient(INTERMEDIATE_CA.keyStore), null, null); |
| CLIENT_EC_RSA_CERTIFICATE = new Builder() |
| .aliasPrefix("client-ec") |
| .keyAlgorithms("EC") |
| .subject("emailAddress=test-ec@user") |
| .signer(INTERMEDIATE_CA.getPrivateKey("RSA", "RSA")) |
| .rootCa(INTERMEDIATE_CA.getRootCertificate("RSA")) |
| .build(); |
| CLIENT_EC_EC_CERTIFICATE = new Builder() |
| .aliasPrefix("client-ec") |
| .keyAlgorithms("EC") |
| .subject("emailAddress=test-ec@user") |
| .signer(INTERMEDIATE_CA_EC.getPrivateKey("EC", "RSA")) |
| .rootCa(INTERMEDIATE_CA_EC.getRootCertificate("RSA")) |
| .build(); |
| CLIENT_CERTIFICATE = new Builder() |
| .aliasPrefix("client") |
| .subject("emailAddress=test@user") |
| .signer(INTERMEDIATE_CA.getPrivateKey("RSA", "RSA")) |
| .rootCa(INTERMEDIATE_CA.getRootCertificate("RSA")) |
| .build(); |
| TestKeyStore rootCa2 = new Builder() |
| .aliasPrefix("RootCA2") |
| .subject("CN=Test Root Certificate Authority 2") |
| .ca(true) |
| .build(); |
| INTERMEDIATE_CA_2 = new Builder() |
| .aliasPrefix("IntermediateCA") |
| .subject("CN=Test Intermediate Certificate Authority") |
| .ca(true) |
| .signer(rootCa2.getPrivateKey("RSA", "RSA")) |
| .rootCa(rootCa2.getRootCertificate("RSA")) |
| .build(); |
| CLIENT_2 = new TestKeyStore(createClient(rootCa2.keyStore), null, null); |
| } |
| |
| /** |
| * Return an root CA that can be used to issue new certificates. |
| */ |
| public static TestKeyStore getRootCa() { |
| initCerts(); |
| return ROOT_CA; |
| } |
| |
| /** |
| * Return an intermediate CA that can be used to issue new certificates. |
| */ |
| public static TestKeyStore getIntermediateCa() { |
| initCerts(); |
| return INTERMEDIATE_CA; |
| } |
| |
| /** |
| * Return an intermediate CA that can be used to issue new certificates. |
| */ |
| public static TestKeyStore getIntermediateCa2() { |
| initCerts(); |
| return INTERMEDIATE_CA_2; |
| } |
| |
| /** |
| * Return a server keystore with a matched RSA certificate and |
| * private key as well as a CA certificate. |
| */ |
| public static TestKeyStore getServer() { |
| initCerts(); |
| return SERVER; |
| } |
| |
| /** |
| * Return a keystore with a CA certificate |
| */ |
| public static TestKeyStore getClient() { |
| initCerts(); |
| return CLIENT; |
| } |
| |
| /** |
| * Return a client keystore with a matched RSA certificate and |
| * private key as well as a CA certificate. |
| */ |
| public static TestKeyStore getClientCertificate() { |
| initCerts(); |
| return CLIENT_CERTIFICATE; |
| } |
| |
| /** |
| * Return a client keystore with a matched RSA certificate and |
| * private key as well as a CA certificate. |
| */ |
| public static TestKeyStore getClientEcRsaCertificate() { |
| initCerts(); |
| return CLIENT_EC_RSA_CERTIFICATE; |
| } |
| |
| /** |
| * Return a client keystore with a matched RSA certificate and |
| * private key as well as a CA certificate. |
| */ |
| public static TestKeyStore getClientEcEcCertificate() { |
| initCerts(); |
| return CLIENT_EC_EC_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() { |
| initCerts(); |
| return CLIENT_2; |
| } |
| |
| /** |
| * Creates KeyStores containing the requested key types. Since key |
| * generation can be expensive, most tests should reuse the RSA-only |
| * singleton instance returned by TestKeyStore.get. |
| */ |
| public static class Builder { |
| private String[] keyAlgorithms = {"RSA"}; |
| private char[] storePassword; |
| private char[] keyPassword; |
| private String aliasPrefix; |
| private X500Principal subject; |
| private int keyUsage; |
| private boolean ca; |
| private PrivateKeyEntry privateEntry; |
| private PrivateKeyEntry signer; |
| private Certificate rootCa; |
| private final List<KeyPurposeId> extendedKeyUsages = new ArrayList<KeyPurposeId>(); |
| private final List<Boolean> criticalExtendedKeyUsages = new ArrayList<Boolean>(); |
| private final List<GeneralName> subjectAltNames = new ArrayList<GeneralName>(); |
| private final List<GeneralSubtree> permittedNameConstraints = |
| new ArrayList<GeneralSubtree>(); |
| private final List<GeneralSubtree> excludedNameConstraints = |
| new ArrayList<GeneralSubtree>(); |
| // Generated randomly if not set |
| private BigInteger certificateSerialNumber = null; |
| |
| public Builder() { |
| subject = localhost(); |
| } |
| |
| /** |
| * Sets the requested key types to generate and include. The default is |
| * RSA only. |
| */ |
| public Builder keyAlgorithms(String... keyAlgorithms) { |
| this.keyAlgorithms = keyAlgorithms; |
| return this; |
| } |
| |
| /** A unique prefix to identify the key aliases */ |
| public Builder aliasPrefix(String aliasPrefix) { |
| this.aliasPrefix = aliasPrefix; |
| return this; |
| } |
| |
| /** |
| * Sets the subject common name. The default is the local host's |
| * canonical name. |
| */ |
| public Builder subject(X500Principal subject) { |
| this.subject = subject; |
| return this; |
| } |
| |
| public Builder subject(String commonName) { |
| return subject(new X500Principal(commonName)); |
| } |
| |
| /** {@link KeyUsage} bit mask for 2.5.29.15 extension */ |
| public Builder keyUsage(int keyUsage) { |
| this.keyUsage = keyUsage; |
| return this; |
| } |
| |
| /** true If the keys being created are for a CA */ |
| public Builder ca(boolean ca) { |
| this.ca = ca; |
| return this; |
| } |
| |
| /** a private key entry to use for the generation of the certificate */ |
| public Builder privateEntry(PrivateKeyEntry privateEntry) { |
| this.privateEntry = privateEntry; |
| return this; |
| } |
| |
| /** a private key entry to be used for signing, otherwise self-sign */ |
| public Builder signer(PrivateKeyEntry signer) { |
| this.signer = signer; |
| return this; |
| } |
| |
| /** a root CA to include in the final store */ |
| public Builder rootCa(Certificate rootCa) { |
| this.rootCa = rootCa; |
| return this; |
| } |
| |
| public Builder addExtendedKeyUsage(KeyPurposeId keyPurposeId, boolean critical) { |
| extendedKeyUsages.add(keyPurposeId); |
| criticalExtendedKeyUsages.add(critical); |
| return this; |
| } |
| |
| public Builder addSubjectAltName(GeneralName generalName) { |
| subjectAltNames.add(generalName); |
| return this; |
| } |
| |
| public Builder addSubjectAltNameIpAddress(byte[] ipAddress) { |
| return addSubjectAltName( |
| new GeneralName(GeneralName.iPAddress, new DEROctetString(ipAddress))); |
| } |
| |
| private Builder addNameConstraint(boolean permitted, GeneralName generalName) { |
| if (permitted) { |
| permittedNameConstraints.add(new GeneralSubtree(generalName)); |
| } else { |
| excludedNameConstraints.add(new GeneralSubtree(generalName)); |
| } |
| return this; |
| } |
| |
| public Builder addNameConstraint(boolean permitted, byte[] ipAddress) { |
| return addNameConstraint(permitted, |
| new GeneralName(GeneralName.iPAddress, new DEROctetString(ipAddress))); |
| } |
| |
| public Builder certificateSerialNumber(BigInteger certificateSerialNumber) { |
| this.certificateSerialNumber = certificateSerialNumber; |
| return this; |
| } |
| |
| 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(); |
| } |
| } |
| |
| /* |
| * This is not implemented for other key types because the logic |
| * would be long to write and it's not needed currently. |
| */ |
| if (privateEntry != null |
| && (keyAlgorithms.length != 1 || !"RSA".equals(keyAlgorithms[0]))) { |
| throw new IllegalStateException( |
| "Only reusing an existing key is implemented for RSA"); |
| } |
| |
| KeyStore keyStore = createKeyStore(); |
| for (String keyAlgorithm : keyAlgorithms) { |
| String publicAlias = aliasPrefix + "-public-" + keyAlgorithm; |
| String privateAlias = aliasPrefix + "-private-" + keyAlgorithm; |
| if ((keyAlgorithm.equals("EC_RSA") || keyAlgorithm.equals("DH_RSA")) |
| && signer == null && rootCa == null) { |
| createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, null, |
| privateKey(keyStore, keyPassword, "RSA", "RSA")); |
| continue; |
| } else if (keyAlgorithm.equals("DH_DSA") && signer == null && rootCa == null) { |
| createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, null, |
| privateKey(keyStore, keyPassword, "DSA", "DSA")); |
| continue; |
| } |
| createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, privateEntry, |
| signer); |
| } |
| 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); |
| } |
| } |
| |
| /** |
| * 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 distinguished |
| * name. |
| * |
| * If a CA is provided, it will be used to sign the generated |
| * certificate and OCSP responses. 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 |
| */ |
| private KeyStore createKeys(KeyStore keyStore, String keyAlgorithm, String publicAlias, |
| String privateAlias, PrivateKeyEntry privateEntry, PrivateKeyEntry signer) |
| throws Exception { |
| PrivateKey caKey; |
| X509Certificate caCert; |
| X509Certificate[] caCertChain; |
| if (signer == null) { |
| caKey = null; |
| caCert = null; |
| caCertChain = null; |
| } else { |
| caKey = signer.getPrivateKey(); |
| caCert = (X509Certificate) signer.getCertificate(); |
| caCertChain = (X509Certificate[]) signer.getCertificateChain(); |
| } |
| |
| final PrivateKey privateKey; |
| final PublicKey publicKey; |
| X509Certificate x509c; |
| if (publicAlias == null && privateAlias == null) { |
| // don't want anything apparently |
| privateKey = null; |
| publicKey = null; |
| x509c = null; |
| } else { |
| if (privateEntry == null) { |
| // 1a.) we make the keys |
| int keySize = -1; |
| AlgorithmParameterSpec spec = null; |
| if (keyAlgorithm.equals("RSA")) { |
| keySize = RSA_KEY_SIZE_BITS; |
| } else if (keyAlgorithm.equals("DH_RSA")) { |
| spec = new DHParameterSpec(DH_PARAMS_P, DH_PARAMS_G); |
| keyAlgorithm = "DH"; |
| } else if (keyAlgorithm.equals("DSA")) { |
| keySize = DSA_KEY_SIZE_BITS; |
| } else if (keyAlgorithm.equals("DH_DSA")) { |
| spec = new DHParameterSpec(DH_PARAMS_P, DH_PARAMS_G); |
| keyAlgorithm = "DH"; |
| } else if (keyAlgorithm.equals("EC")) { |
| keySize = EC_KEY_SIZE_BITS; |
| } else if (keyAlgorithm.equals("EC_RSA")) { |
| keySize = EC_KEY_SIZE_BITS; |
| keyAlgorithm = "EC"; |
| } else { |
| throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm); |
| } |
| |
| KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgorithm); |
| if (spec != null) { |
| kpg.initialize(spec); |
| } else if (keySize != -1) { |
| kpg.initialize(keySize); |
| } else { |
| throw new AssertionError( |
| "Must either have set algorithm parameters or key size!"); |
| } |
| |
| KeyPair kp = kpg.generateKeyPair(); |
| privateKey = kp.getPrivate(); |
| publicKey = kp.getPublic(); |
| } else { |
| // 1b.) we use the previous keys |
| privateKey = privateEntry.getPrivateKey(); |
| publicKey = privateEntry.getCertificate().getPublicKey(); |
| } |
| |
| // 2.) use keys to make certificate |
| X500Principal issuer = |
| ((caCert != null) ? caCert.getSubjectX500Principal() : subject); |
| PrivateKey signingKey = (caKey == null) ? privateKey : caKey; |
| x509c = createCertificate(publicKey, signingKey, subject, issuer, keyUsage, ca, |
| extendedKeyUsages, criticalExtendedKeyUsages, subjectAltNames, |
| permittedNameConstraints, excludedNameConstraints, certificateSerialNumber); |
| } |
| |
| 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; |
| } |
| |
| private X500Principal localhost() { |
| return new X500Principal("CN=" + LOCAL_HOST_NAME); |
| } |
| } |
| |
| 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 ArrayList<KeyPurposeId>(), new ArrayList<Boolean>(), |
| new ArrayList<GeneralName>(), new ArrayList<GeneralSubtree>(), |
| new ArrayList<GeneralSubtree>(), null /* serialNumber, generated randomly */); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static X509Certificate createCertificate(PublicKey publicKey, PrivateKey privateKey, |
| X500Principal subject, X500Principal issuer, int keyUsage, boolean ca, |
| List<KeyPurposeId> extendedKeyUsages, List<Boolean> criticalExtendedKeyUsages, |
| List<GeneralName> subjectAltNames, List<GeneralSubtree> permittedNameConstraints, |
| List<GeneralSubtree> excludedNameConstraints, BigInteger serialNumber) |
| 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); |
| |
| 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); |
| } |
| |
| if (serialNumber == null) { |
| byte[] serialBytes = new byte[16]; |
| new SecureRandom().nextBytes(serialBytes); |
| serialNumber = new BigInteger(1, serialBytes); |
| } |
| |
| X509v3CertificateBuilder x509cg = |
| new X509v3CertificateBuilder(X500Name.getInstance(issuer.getEncoded()), |
| serialNumber, start, end, X500Name.getInstance(subject.getEncoded()), |
| SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); |
| if (keyUsage != 0) { |
| x509cg.addExtension(Extension.keyUsage, true, new KeyUsage(keyUsage)); |
| } |
| if (ca) { |
| x509cg.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); |
| } |
| for (int i = 0; i < extendedKeyUsages.size(); i++) { |
| KeyPurposeId keyPurposeId = extendedKeyUsages.get(i); |
| boolean critical = criticalExtendedKeyUsages.get(i); |
| x509cg.addExtension( |
| Extension.extendedKeyUsage, critical, new ExtendedKeyUsage(keyPurposeId)); |
| } |
| for (GeneralName subjectAltName : subjectAltNames) { |
| x509cg.addExtension(Extension.subjectAlternativeName, false, |
| new GeneralNames(subjectAltName).getEncoded()); |
| } |
| if (!permittedNameConstraints.isEmpty() || !excludedNameConstraints.isEmpty()) { |
| x509cg.addExtension(Extension.nameConstraints, true, |
| new NameConstraints( |
| permittedNameConstraints.toArray( |
| new GeneralSubtree[permittedNameConstraints.size()]), |
| excludedNameConstraints.toArray( |
| new GeneralSubtree[excludedNameConstraints.size()]))); |
| } |
| |
| X509CertificateHolder x509holder = |
| x509cg.build(new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey)); |
| X509Certificate x509c = new JcaX509CertificateConverter().getCertificate(x509holder); |
| 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 |
| * present, the argument is returned unmodified. However for an |
| * algorithm such as EC_RSA, return EC. |
| */ |
| public static String keyAlgorithm(String algorithm) { |
| int index = algorithm.indexOf('_'); |
| if (index == -1) { |
| return algorithm; |
| } |
| return algorithm.substring(0, index); |
| } |
| |
| /** |
| * Return the signature algorithm for a possible compound |
| * algorithm identifier containing an underscore. If not |
| * underscore is present, the argument is returned |
| * unmodified. However for an algorithm such as EC_RSA, return |
| * RSA. |
| */ |
| public static String signatureAlgorithm(String algorithm) { |
| int index = algorithm.indexOf('_'); |
| if (index == -1) { |
| return algorithm; |
| } |
| return algorithm.substring(index + 1, algorithm.length()); |
| } |
| |
| /** |
| * Create an empty 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); |
| } |
| } |
| |
| /** |
| * Return the only private key in a TestKeyStore for the given |
| * algorithms. Throws IllegalStateException if there are are more |
| * or less than one. |
| */ |
| public PrivateKeyEntry getPrivateKey(String keyAlgorithm, String signatureAlgorithm) { |
| return privateKey(keyStore, keyPassword, keyAlgorithm, signatureAlgorithm); |
| } |
| |
| /** |
| * Return the only private key in a keystore for the given |
| * algorithms. Throws IllegalStateException if there are are more |
| * or less than one. |
| */ |
| public static PrivateKeyEntry privateKey( |
| KeyStore keyStore, char[] keyPassword, 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 contained no private key for" |
| + " keyAlgorithm: " + keyAlgorithm |
| + " signatureAlgorithm: " + signatureAlgorithm); |
| } |
| return found; |
| } catch (Exception e) { |
| throw new RuntimeException("Problem getting key for " + keyAlgorithm + " and signature " |
| + signatureAlgorithm, |
| e); |
| } |
| } |
| |
| /** |
| * Return the issuing CA certificate of the given |
| * certificate. Throws IllegalStateException if there are are more |
| * or less than one. |
| */ |
| public Certificate getIssuer(Certificate cert) throws Exception { |
| return issuer(keyStore, cert); |
| } |
| |
| /** |
| * Return the issuing CA certificate of the given |
| * certificate. Throws IllegalStateException if there are are more |
| * or less than one. |
| */ |
| public static Certificate issuer(KeyStore keyStore, Certificate c) throws Exception { |
| if (!(c instanceof X509Certificate)) { |
| throw new IllegalStateException("issuer requires an X509Certificate, found " + c); |
| } |
| X509Certificate cert = (X509Certificate) c; |
| |
| 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 instanceof X509Certificate)) { |
| continue; |
| } |
| X509Certificate x = (X509Certificate) certificate; |
| if (!cert.getIssuerDN().equals(x.getSubjectDN())) { |
| continue; |
| } |
| if (found != null) { |
| throw new IllegalStateException("KeyStore has more than one issuing CA for " + cert |
| + "\nfirst: " + found + "\nsecond: " + certificate); |
| } |
| found = certificate; |
| } |
| if (found == null) { |
| throw new IllegalStateException("KeyStore contained no issuing CA for " + cert); |
| } |
| return found; |
| } |
| |
| /** |
| * Return the only self-signed root certificate in a TestKeyStore |
| * for the given algorithm. Throws IllegalStateException if there |
| * are are more or less than one. |
| */ |
| public X509Certificate getRootCertificate(String algorithm) { |
| return rootCertificate(keyStore, algorithm); |
| } |
| |
| private static OCSPResp generateOCSPResponse(PrivateKeyEntry server, PrivateKeyEntry issuer, |
| CertificateStatus status) throws CertificateException { |
| try { |
| X509Certificate serverCertJca = (X509Certificate) server.getCertificate(); |
| X509Certificate caCertJca = (X509Certificate) issuer.getCertificate(); |
| |
| X509CertificateHolder caCert = new JcaX509CertificateHolder(caCertJca); |
| |
| DigestCalculatorProvider digCalcProv = new BcDigestCalculatorProvider(); |
| BasicOCSPRespBuilder basicBuilder = new BasicOCSPRespBuilder( |
| SubjectPublicKeyInfo.getInstance(caCertJca.getPublicKey().getEncoded()), |
| digCalcProv.get(CertificateID.HASH_SHA1)); |
| |
| CertificateID certId = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), |
| caCert, serverCertJca.getSerialNumber()); |
| |
| basicBuilder.addResponse(certId, status); |
| |
| BasicOCSPResp resp = basicBuilder.build( |
| new JcaContentSignerBuilder("SHA256withRSA").build(issuer.getPrivateKey()), |
| null, new Date()); |
| |
| OCSPRespBuilder builder = new OCSPRespBuilder(); |
| return builder.build(OCSPRespBuilder.SUCCESSFUL, resp); |
| } catch (Exception e) { |
| throw new CertificateException("cannot generate OCSP response", e); |
| } |
| } |
| |
| public static byte[] getOCSPResponseForGood(PrivateKeyEntry server, PrivateKeyEntry issuer) |
| throws CertificateException { |
| try { |
| return generateOCSPResponse(server, issuer, CertificateStatus.GOOD).getEncoded(); |
| } catch (IOException e) { |
| throw new CertificateException(e); |
| } |
| } |
| |
| public static byte[] getOCSPResponseForRevoked(PrivateKeyEntry server, PrivateKeyEntry issuer) |
| throws CertificateException { |
| try { |
| return generateOCSPResponse( |
| server, issuer, new RevokedStatus(new Date(), CRLReason.keyCompromise)) |
| .getEncoded(); |
| } catch (IOException e) { |
| throw new CertificateException(e); |
| } |
| } |
| |
| /** |
| * Return the only self-signed root certificate in a keystore for |
| * the given algorithm. Throws IllegalStateException if there are |
| * are more or less than one. |
| */ |
| 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 contained no root CA for " + algorithm); |
| } |
| return found; |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Return an {@code X509Certificate} that matches the given {@code alias}. |
| */ |
| public KeyStore.Entry getEntryByAlias(String alias) { |
| return entryByAlias(keyStore, alias); |
| } |
| |
| /** |
| * Finds an entry in the keystore by the given alias. |
| */ |
| public static KeyStore.Entry entryByAlias(KeyStore keyStore, String alias) { |
| try { |
| return keyStore.getEntry(alias, null); |
| } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException 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) { |
| KeyStore clientKeyStore = createKeyStore(); |
| copySelfSignedCertificates(clientKeyStore, caKeyStore); |
| return clientKeyStore; |
| } |
| |
| /** |
| * 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) { |
| 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; |
| } |
| return copied; |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Copy named certificates from one key store to another. |
| * Returns true if successful, false if no match found. |
| */ |
| public static boolean copyCertificate(Principal subject, 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(subject)) { |
| continue; |
| } |
| dst.setCertificateEntry(alias, cert); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Dump a key store for debugging. |
| */ |
| public void dump(String context) throws KeyStoreException, NoSuchAlgorithmException { |
| dump(context, keyStore, keyPassword); |
| } |
| |
| /** |
| * Dump a key store for debugging. |
| */ |
| public static void dump(String context, KeyStore keyStore, char[] keyPassword) |
| throws KeyStoreException, NoSuchAlgorithmException { |
| 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 retrieved using password\n" + keyStore.getKey(alias, keyPassword)); |
| } catch (UnrecoverableKeyException e1) { |
| try { |
| key = ("Key retrieved without password\n" + keyStore.getKey(alias, null)); |
| } catch (UnrecoverableKeyException e2) { |
| key = "Key could not be retrieved"; |
| } |
| } |
| out.println(key); |
| out.println("=========================================="); |
| Certificate[] chain = keyStore.getCertificateChain(alias); |
| if (chain == null) { |
| out.println("No certificate chain associated with key"); |
| out.println("=========================================="); |
| } else { |
| for (int i = 0; i < chain.length; i++) { |
| out.println("Certificate chain element #" + i); |
| out.println(chain[i]); |
| out.println("=========================================="); |
| } |
| } |
| continue; |
| } |
| out.println("\tunknown entry type"); |
| } |
| } |
| |
| 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); |
| } |
| } |