blob: 58a701ff807f786b8b312588dad9871ecfa970df [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.kafka.test;
import org.apache.kafka.common.config.SslConfigs;
import org.apache.kafka.common.config.types.Password;
import org.apache.kafka.common.network.Mode;
import org.apache.kafka.common.security.auth.SslEngineFactory;
import org.apache.kafka.common.security.ssl.DefaultSslEngineFactory;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERT61String;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
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.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcContentSignerBuilder;
import org.bouncycastle.operator.bc.BcDSAContentSignerBuilder;
import org.bouncycastle.operator.bc.BcECContentSignerBuilder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
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.io.OutputStreamWriter;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import static org.apache.kafka.common.security.ssl.DefaultSslEngineFactory.PEM_TYPE;
public class TestSslUtils {
public static final String TRUST_STORE_PASSWORD = "TrustStorePassword";
public static final String DEFAULT_TLS_PROTOCOL_FOR_TESTS = SslConfigs.DEFAULT_SSL_PROTOCOL;
/**
* Create a self-signed X.509 Certificate.
* From http://bfo.com/blog/2011/03/08/odds_and_ends_creating_a_new_x_509_certificate.html.
*
* @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
* @param pair the KeyPair
* @param days how many days from now the Certificate is valid for, or - for negative values - how many days before now
* @param algorithm the signing algorithm, eg "SHA1withRSA"
* @return the self-signed certificate
* @throws CertificateException thrown if a security error or an IO error occurred.
*/
public static X509Certificate generateCertificate(String dn, KeyPair pair,
int days, String algorithm)
throws CertificateException {
return new CertificateBuilder(days, algorithm).generate(dn, pair);
}
/**
* Generate a signed certificate. Self-signed, if no issuer and parentKeyPair are supplied
*
* @param dn The distinguished name of this certificate
* @param keyPair A key pair
* @param daysBeforeNow how many days before now the Certificate is valid for
* @param daysAfterNow how many days from now the Certificate is valid for
* @param issuer The issuer who signs the certificate. Leave null if you want to generate a root
* CA.
* @param parentKeyPair The key pair of the issuer. Leave null if you want to generate a root
* CA.
* @param algorithm the signing algorithm, eg "SHA1withRSA"
* @return the signed certificate
* @throws CertificateException
*/
public static X509Certificate generateSignedCertificate(String dn, KeyPair keyPair,
int daysBeforeNow, int daysAfterNow, String issuer, KeyPair parentKeyPair,
String algorithm, boolean isCA, boolean isServerCert, boolean isClientCert) throws CertificateException {
return new CertificateBuilder(0, algorithm).generateSignedCertificate(dn, keyPair,
daysBeforeNow, daysAfterNow, issuer, parentKeyPair, isCA, isServerCert, isClientCert);
}
public static X509Certificate generateSignedCertificate(String dn, KeyPair keyPair,
int daysBeforeNow, int daysAfterNow, String issuer, KeyPair parentKeyPair,
String algorithm, boolean isCA, boolean isServerCert, boolean isClientCert,
String[] hostNames) throws CertificateException, IOException {
return new CertificateBuilder(0, algorithm).sanDnsNames(hostNames).generateSignedCertificate(dn, keyPair,
daysBeforeNow, daysAfterNow, issuer, parentKeyPair, isCA, isServerCert, isClientCert);
}
public static KeyPair generateKeyPair(String algorithm) throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
keyGen.initialize(algorithm.equals("EC") ? 256 : 2048);
return keyGen.genKeyPair();
}
private static KeyStore createEmptyKeyStore() throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null); // initialize
return ks;
}
private static void saveKeyStore(KeyStore ks, String filename,
Password password) throws GeneralSecurityException, IOException {
try (OutputStream out = Files.newOutputStream(Paths.get(filename))) {
ks.store(out, password.value().toCharArray());
}
}
/**
* Creates a keystore with a single key and saves it to a file.
*
* @param filename String file to save
* @param password String store password to set on keystore
* @param keyPassword String key password to set on key
* @param alias String alias to use for the key
* @param privateKey Key to save in keystore
* @param cert Certificate to use as certificate chain associated to key
* @throws GeneralSecurityException for any error with the security APIs
* @throws IOException if there is an I/O error saving the file
*/
public static void createKeyStore(String filename,
Password password, Password keyPassword, String alias,
Key privateKey, Certificate cert) throws GeneralSecurityException, IOException {
KeyStore ks = createEmptyKeyStore();
ks.setKeyEntry(alias, privateKey, keyPassword.value().toCharArray(),
new Certificate[]{cert});
saveKeyStore(ks, filename, password);
}
public static <T extends Certificate> void createTrustStore(
String filename, Password password, Map<String, T> certs) throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream in = Files.newInputStream(Paths.get(filename))) {
ks.load(in, password.value().toCharArray());
} catch (EOFException e) {
ks = createEmptyKeyStore();
}
for (Map.Entry<String, T> cert : certs.entrySet()) {
ks.setCertificateEntry(cert.getKey(), cert.getValue());
}
saveKeyStore(ks, filename, password);
}
public static Map<String, Object> createSslConfig(String keyManagerAlgorithm, String trustManagerAlgorithm, String tlsProtocol) {
Map<String, Object> sslConfigs = new HashMap<>();
sslConfigs.put(SslConfigs.SSL_PROTOCOL_CONFIG, tlsProtocol); // protocol to create SSLContext
sslConfigs.put(SslConfigs.SSL_KEYMANAGER_ALGORITHM_CONFIG, keyManagerAlgorithm);
sslConfigs.put(SslConfigs.SSL_TRUSTMANAGER_ALGORITHM_CONFIG, trustManagerAlgorithm);
List<String> enabledProtocols = new ArrayList<>();
enabledProtocols.add(tlsProtocol);
sslConfigs.put(SslConfigs.SSL_ENABLED_PROTOCOLS_CONFIG, enabledProtocols);
return sslConfigs;
}
public static Map<String, Object> createSslConfig(boolean useClientCert, boolean trustStore, Mode mode, File trustStoreFile, String certAlias)
throws IOException, GeneralSecurityException {
return createSslConfig(useClientCert, trustStore, mode, trustStoreFile, certAlias, "localhost");
}
public static Map<String, Object> createSslConfig(boolean useClientCert, boolean trustStore,
Mode mode, File trustStoreFile, String certAlias, String cn)
throws IOException, GeneralSecurityException {
return createSslConfig(useClientCert, trustStore, mode, trustStoreFile, certAlias, cn, new CertificateBuilder());
}
public static Map<String, Object> createSslConfig(boolean useClientCert, boolean createTrustStore,
Mode mode, File trustStoreFile, String certAlias, String cn, CertificateBuilder certBuilder)
throws IOException, GeneralSecurityException {
SslConfigsBuilder builder = new SslConfigsBuilder(mode)
.useClientCert(useClientCert)
.certAlias(certAlias)
.cn(cn)
.certBuilder(certBuilder);
if (createTrustStore)
builder = builder.createNewTrustStore(trustStoreFile);
else
builder = builder.useExistingTrustStore(trustStoreFile);
return builder.build();
}
public static void convertToPem(Map<String, Object> sslProps, boolean writeToFile, boolean encryptPrivateKey) throws Exception {
String tsPath = (String) sslProps.get(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG);
String tsType = (String) sslProps.get(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG);
Password tsPassword = (Password) sslProps.remove(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG);
Password trustCerts = (Password) sslProps.remove(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG);
if (trustCerts == null && tsPath != null) {
trustCerts = exportCertificates(tsPath, tsPassword, tsType);
}
if (trustCerts != null) {
if (tsPath == null) {
tsPath = TestUtils.tempFile("truststore", ".pem").getPath();
sslProps.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, tsPath);
}
sslProps.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, PEM_TYPE);
if (writeToFile)
writeToFile(tsPath, trustCerts);
else {
sslProps.put(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, trustCerts);
sslProps.remove(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG);
}
}
String ksPath = (String) sslProps.get(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG);
Password certChain = (Password) sslProps.remove(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG);
Password key = (Password) sslProps.remove(SslConfigs.SSL_KEYSTORE_KEY_CONFIG);
if (certChain == null && ksPath != null) {
String ksType = (String) sslProps.get(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG);
Password ksPassword = (Password) sslProps.remove(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG);
Password keyPassword = (Password) sslProps.get(SslConfigs.SSL_KEY_PASSWORD_CONFIG);
certChain = exportCertificates(ksPath, ksPassword, ksType);
Password pemKeyPassword = encryptPrivateKey ? keyPassword : null;
key = exportPrivateKey(ksPath, ksPassword, keyPassword, ksType, pemKeyPassword);
if (!encryptPrivateKey)
sslProps.remove(SslConfigs.SSL_KEY_PASSWORD_CONFIG);
}
if (certChain != null) {
if (ksPath == null) {
ksPath = TestUtils.tempFile("keystore", ".pem").getPath();
sslProps.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, ksPath);
}
sslProps.put(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, PEM_TYPE);
if (writeToFile)
writeToFile(ksPath, key, certChain);
else {
sslProps.put(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, key);
sslProps.put(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, certChain);
sslProps.remove(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG);
}
}
}
private static void writeToFile(String path, Password... entries) throws IOException {
try (FileOutputStream out = new FileOutputStream(path)) {
for (Password entry: entries) {
out.write(entry.value().getBytes(StandardCharsets.UTF_8));
}
}
}
public static void convertToPemWithoutFiles(Properties sslProps) throws Exception {
String tsPath = sslProps.getProperty(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG);
if (tsPath != null) {
Password trustCerts = exportCertificates(tsPath,
(Password) sslProps.get(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG),
sslProps.getProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG));
sslProps.remove(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG);
sslProps.remove(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG);
sslProps.setProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, PEM_TYPE);
sslProps.put(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, trustCerts);
}
String ksPath = sslProps.getProperty(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG);
if (ksPath != null) {
String ksType = sslProps.getProperty(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG);
Password ksPassword = (Password) sslProps.get(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG);
Password keyPassword = (Password) sslProps.get(SslConfigs.SSL_KEY_PASSWORD_CONFIG);
Password certChain = exportCertificates(ksPath, ksPassword, ksType);
Password key = exportPrivateKey(ksPath, ksPassword, keyPassword, ksType, keyPassword);
sslProps.remove(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG);
sslProps.remove(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG);
sslProps.setProperty(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, PEM_TYPE);
sslProps.put(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, certChain);
sslProps.put(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, key);
}
}
public static Password exportCertificates(String storePath, Password storePassword, String storeType) throws Exception {
StringBuilder builder = new StringBuilder();
try (FileInputStream in = new FileInputStream(storePath)) {
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(in, storePassword.value().toCharArray());
Enumeration<String> aliases = ks.aliases();
if (!aliases.hasMoreElements())
throw new IllegalArgumentException("No certificates found in file " + storePath);
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Certificate[] certs = ks.getCertificateChain(alias);
if (certs != null) {
for (Certificate cert : certs) {
builder.append(pem(cert));
}
} else {
builder.append(pem(ks.getCertificate(alias)));
}
}
}
return new Password(builder.toString());
}
public static Password exportPrivateKey(String storePath,
Password storePassword,
Password keyPassword,
String storeType,
Password pemKeyPassword) throws Exception {
try (FileInputStream in = new FileInputStream(storePath)) {
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(in, storePassword.value().toCharArray());
String alias = ks.aliases().nextElement();
return new Password(pem((PrivateKey) ks.getKey(alias, keyPassword.value().toCharArray()), pemKeyPassword));
}
}
static String pem(Certificate cert) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
pemWriter.writeObject(new JcaMiscPEMGenerator(cert));
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
static String pem(PrivateKey privateKey, Password password) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
if (password == null) {
pemWriter.writeObject(new JcaPKCS8Generator(privateKey, null));
} else {
JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.PBE_SHA1_3DES);
encryptorBuilder.setPassword(password.value().toCharArray());
try {
pemWriter.writeObject(new JcaPKCS8Generator(privateKey, encryptorBuilder.build()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
public static class CertificateBuilder {
private final int days;
private final String algorithm;
private byte[] subjectAltName;
public CertificateBuilder() {
this(30, "SHA1withRSA");
}
public CertificateBuilder(int days, String algorithm) {
this.days = days;
this.algorithm = algorithm;
}
public CertificateBuilder sanDnsNames(String... hostNames) throws IOException {
if (hostNames.length > 0) {
GeneralName[] altNames = new GeneralName[hostNames.length];
for (int i = 0; i < hostNames.length; i++)
altNames[i] = new GeneralName(GeneralName.dNSName, hostNames[i]);
subjectAltName = GeneralNames.getInstance(new DERSequence(altNames)).getEncoded();
} else {
subjectAltName = null;
}
return this;
}
public CertificateBuilder sanIpAddress(InetAddress hostAddress) throws IOException {
subjectAltName = new GeneralNames(new GeneralName(GeneralName.iPAddress, new DEROctetString(hostAddress.getAddress()))).getEncoded();
return this;
}
public X509Certificate generate(String dn, KeyPair keyPair) throws CertificateException {
return generate(new X500Name(dn), keyPair);
}
public X509Certificate generate(String commonName, String org, boolean utf8, KeyPair keyPair) throws CertificateException {
RDN[] rdns = new RDN[2];
rdns[0] = new RDN(new AttributeTypeAndValue(BCStyle.CN, utf8 ? new DERUTF8String(commonName) : new DERT61String(commonName)));
rdns[1] = new RDN(new AttributeTypeAndValue(BCStyle.O, utf8 ? new DERUTF8String(org) : new DERT61String(org)));
return generate(new X500Name(rdns), keyPair);
}
public X509Certificate generate(X500Name dn, KeyPair keyPair) throws CertificateException {
try {
Security.addProvider(new BouncyCastleProvider());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(algorithm);
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter privateKeyAsymKeyParam = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
BcContentSignerBuilder signerBuilder;
String keyAlgorithm = keyPair.getPublic().getAlgorithm();
if (keyAlgorithm.equals("RSA"))
signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
else if (keyAlgorithm.equals("DSA"))
signerBuilder = new BcDSAContentSignerBuilder(sigAlgId, digAlgId);
else if (keyAlgorithm.equals("EC"))
signerBuilder = new BcECContentSignerBuilder(sigAlgId, digAlgId);
else
throw new IllegalArgumentException("Unsupported algorithm " + keyAlgorithm);
ContentSigner sigGen = signerBuilder.build(privateKeyAsymKeyParam);
// Negative numbers for "days" can be used to generate expired certificates
Date now = new Date();
Date from = (days >= 0) ? now : new Date(now.getTime() + days * 86400000L);
Date to = (days >= 0) ? new Date(now.getTime() + days * 86400000L) : now;
BigInteger sn = new BigInteger(64, new SecureRandom());
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(dn, sn, from, to, dn, subPubKeyInfo);
if (subjectAltName != null)
v3CertGen.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
} catch (CertificateException ce) {
throw ce;
} catch (Exception e) {
throw new CertificateException(e);
}
}
/**
* @param dn The distinguished name to use
* @param keyPair A key pair to use
* @param daysBeforeNow how many days before now the Certificate is valid for
* @param daysAfterNow how many days from now the Certificate is valid for
* @param issuer The issuer name. if null, "dn" is used
* @param parentKeyPair The parent key pair used to sign this certificate. If null, create
* self-signed certificate authority (CA)
* @return A (self-) signed certificate
* @throws CertificateException
*/
public X509Certificate generateSignedCertificate(String dn, KeyPair keyPair,
int daysBeforeNow, int daysAfterNow, String issuer, KeyPair parentKeyPair, boolean isCA, boolean isServerCert, boolean isClientCert)
throws CertificateException {
X500Name issuerOrDn = (issuer != null) ? new X500Name(issuer) : new X500Name(dn);
return generateSignedCertificate(new X500Name(dn), keyPair, daysBeforeNow, daysAfterNow,
issuerOrDn, parentKeyPair, isCA, isServerCert, isClientCert);
}
/**
*
* @param dn The distinguished name to use
* @param keyPair A key pair to use
* @param daysBeforeNow how many days before now the Certificate is valid for
* @param daysAfterNow how many days from now the Certificate is valid for
* @param issuer The issuer name. if null, "dn" is used
* @param parentKeyPair The parent key pair used to sign this certificate. If null, create
* self-signed certificate authority (CA)
* @return A (self-) signed certificate
* @throws CertificateException
*/
public X509Certificate generateSignedCertificate(X500Name dn, KeyPair keyPair,
int daysBeforeNow, int daysAfterNow, X500Name issuer, KeyPair parentKeyPair, boolean isCA, boolean isServerCert, boolean isClientCert)
throws CertificateException {
try {
Security.addProvider(new BouncyCastleProvider());
AlgorithmIdentifier sigAlgId =
new DefaultSignatureAlgorithmIdentifierFinder().find(algorithm);
AlgorithmIdentifier digAlgId =
new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
// Create self-signed certificate if no parentKeyPair has been specified, otherwise
// sign with private key of parentKeyPair
KeyPair signingKeyPair = (parentKeyPair != null) ? parentKeyPair : keyPair;
AsymmetricKeyParameter privateKeyAsymKeyParam =
PrivateKeyFactory.createKey(signingKeyPair.getPrivate().getEncoded());
SubjectPublicKeyInfo subPubKeyInfo =
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
BcContentSignerBuilder signerBuilder;
String keyAlgorithm = keyPair.getPublic().getAlgorithm();
if (keyAlgorithm.equals("RSA"))
signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
else if (keyAlgorithm.equals("DSA"))
signerBuilder = new BcDSAContentSignerBuilder(sigAlgId, digAlgId);
else if (keyAlgorithm.equals("EC"))
signerBuilder = new BcECContentSignerBuilder(sigAlgId, digAlgId);
else
throw new IllegalArgumentException("Unsupported algorithm " + keyAlgorithm);
ContentSigner sigGen = signerBuilder.build(privateKeyAsymKeyParam);
// Negative numbers for "days" can be used to generate expired certificates
Date now = new Date();
Date from = new Date(now.getTime() - daysBeforeNow * 86400000L);
Date to = new Date(now.getTime() + daysAfterNow * 86400000L);
BigInteger sn = new BigInteger(64, new SecureRandom());
X500Name issuerOrDn = (issuer != null) ? issuer : dn;
X509v3CertificateBuilder v3CertGen =
new X509v3CertificateBuilder(issuerOrDn, sn, from, to, dn, subPubKeyInfo);
if (isCA) {
v3CertGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCA));
}
if (isServerCert || isClientCert) {
ASN1EncodableVector purposes = new ASN1EncodableVector();
if (isServerCert) {
purposes.add(KeyPurposeId.id_kp_serverAuth);
}
if (isClientCert) {
purposes.add(KeyPurposeId.id_kp_clientAuth);
}
v3CertGen.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));
}
if (subjectAltName != null) {
v3CertGen.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
}
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certificateHolder);
} catch (CertificateException ce) {
throw ce;
} catch (Exception e) {
throw new CertificateException(e);
}
}
}
public static class SslConfigsBuilder {
final Mode mode;
String tlsProtocol;
boolean useClientCert;
boolean createTrustStore;
File trustStoreFile;
Password trustStorePassword;
Password keyStorePassword;
Password keyPassword;
String certAlias;
String cn;
String algorithm;
CertificateBuilder certBuilder;
boolean usePem;
public SslConfigsBuilder(Mode mode) {
this.mode = mode;
this.tlsProtocol = DEFAULT_TLS_PROTOCOL_FOR_TESTS;
trustStorePassword = new Password(TRUST_STORE_PASSWORD);
keyStorePassword = mode == Mode.SERVER ? new Password("ServerPassword") : new Password("ClientPassword");
keyPassword = keyStorePassword;
this.certBuilder = new CertificateBuilder();
this.cn = "localhost";
this.certAlias = mode.name().toLowerCase(Locale.ROOT);
this.algorithm = "RSA";
this.createTrustStore = true;
}
public SslConfigsBuilder tlsProtocol(String tlsProtocol) {
this.tlsProtocol = tlsProtocol;
return this;
}
public SslConfigsBuilder createNewTrustStore(File trustStoreFile) {
this.trustStoreFile = trustStoreFile;
this.createTrustStore = true;
return this;
}
public SslConfigsBuilder useExistingTrustStore(File trustStoreFile) {
this.trustStoreFile = trustStoreFile;
this.createTrustStore = false;
return this;
}
public SslConfigsBuilder useClientCert(boolean useClientCert) {
this.useClientCert = useClientCert;
return this;
}
public SslConfigsBuilder certAlias(String certAlias) {
this.certAlias = certAlias;
return this;
}
public SslConfigsBuilder cn(String cn) {
this.cn = cn;
return this;
}
public SslConfigsBuilder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public SslConfigsBuilder certBuilder(CertificateBuilder certBuilder) {
this.certBuilder = certBuilder;
return this;
}
public SslConfigsBuilder usePem(boolean usePem) {
this.usePem = usePem;
return this;
}
public Map<String, Object> build() throws IOException, GeneralSecurityException {
if (usePem) {
return buildPem();
} else
return buildJks();
}
private Map<String, Object> buildJks() throws IOException, GeneralSecurityException {
Map<String, X509Certificate> certs = new HashMap<>();
File keyStoreFile = null;
if (mode == Mode.CLIENT && useClientCert) {
keyStoreFile = TestUtils.tempFile("clientKS", ".jks");
KeyPair cKP = generateKeyPair(algorithm);
X509Certificate cCert = certBuilder.generate("CN=" + cn + ", O=A client", cKP);
createKeyStore(keyStoreFile.getPath(), keyStorePassword, keyPassword, "client", cKP.getPrivate(), cCert);
certs.put(certAlias, cCert);
} else if (mode == Mode.SERVER) {
keyStoreFile = TestUtils.tempFile("serverKS", ".jks");
KeyPair sKP = generateKeyPair(algorithm);
X509Certificate sCert = certBuilder.generate("CN=" + cn + ", O=A server", sKP);
createKeyStore(keyStoreFile.getPath(), keyStorePassword, keyPassword, "server", sKP.getPrivate(), sCert);
certs.put(certAlias, sCert);
keyStoreFile.deleteOnExit();
}
if (createTrustStore) {
createTrustStore(trustStoreFile.getPath(), trustStorePassword, certs);
trustStoreFile.deleteOnExit();
}
Map<String, Object> sslConfigs = new HashMap<>();
sslConfigs.put(SslConfigs.SSL_PROTOCOL_CONFIG, tlsProtocol); // protocol to create SSLContext
if (mode == Mode.SERVER || (mode == Mode.CLIENT && keyStoreFile != null)) {
sslConfigs.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, keyStoreFile.getPath());
sslConfigs.put(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, "JKS");
sslConfigs.put(SslConfigs.SSL_KEYMANAGER_ALGORITHM_CONFIG, TrustManagerFactory.getDefaultAlgorithm());
sslConfigs.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, keyStorePassword);
sslConfigs.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, keyPassword);
}
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, trustStoreFile.getPath());
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, trustStorePassword);
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, "JKS");
sslConfigs.put(SslConfigs.SSL_TRUSTMANAGER_ALGORITHM_CONFIG, TrustManagerFactory.getDefaultAlgorithm());
List<String> enabledProtocols = new ArrayList<>();
enabledProtocols.add(tlsProtocol);
sslConfigs.put(SslConfigs.SSL_ENABLED_PROTOCOLS_CONFIG, enabledProtocols);
return sslConfigs;
}
private Map<String, Object> buildPem() throws IOException, GeneralSecurityException {
if (!createTrustStore) {
throw new IllegalArgumentException("PEM configs cannot be created with existing trust stores");
}
Map<String, Object> sslConfigs = new HashMap<>();
sslConfigs.put(SslConfigs.SSL_PROTOCOL_CONFIG, tlsProtocol);
sslConfigs.put(SslConfigs.SSL_ENABLED_PROTOCOLS_CONFIG, Collections.singletonList(tlsProtocol));
if (mode != Mode.CLIENT || useClientCert) {
KeyPair keyPair = generateKeyPair(algorithm);
X509Certificate cert = certBuilder.generate("CN=" + cn + ", O=A " + mode.name().toLowerCase(Locale.ROOT), keyPair);
Password privateKeyPem = new Password(pem(keyPair.getPrivate(), keyPassword));
Password certPem = new Password(pem(cert));
sslConfigs.put(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, PEM_TYPE);
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, PEM_TYPE);
sslConfigs.put(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, privateKeyPem);
sslConfigs.put(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, certPem);
sslConfigs.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, keyPassword);
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, certPem);
}
return sslConfigs;
}
}
public static final class TestSslEngineFactory implements SslEngineFactory {
public boolean closed = false;
DefaultSslEngineFactory defaultSslEngineFactory = new DefaultSslEngineFactory();
@Override
public SSLEngine createClientSslEngine(String peerHost, int peerPort, String endpointIdentification) {
return defaultSslEngineFactory.createClientSslEngine(peerHost, peerPort, endpointIdentification);
}
@Override
public SSLEngine createServerSslEngine(String peerHost, int peerPort) {
return defaultSslEngineFactory.createServerSslEngine(peerHost, peerPort);
}
@Override
public boolean shouldBeRebuilt(Map<String, Object> nextConfigs) {
return defaultSslEngineFactory.shouldBeRebuilt(nextConfigs);
}
@Override
public Set<String> reconfigurableConfigs() {
return defaultSslEngineFactory.reconfigurableConfigs();
}
@Override
public KeyStore keystore() {
return defaultSslEngineFactory.keystore();
}
@Override
public KeyStore truststore() {
return defaultSslEngineFactory.truststore();
}
@Override
public void close() throws IOException {
defaultSslEngineFactory.close();
closed = true;
}
@Override
public void configure(Map<String, ?> configs) {
defaultSslEngineFactory.configure(configs);
}
}
/**
* method to generate ssl configs for keystore with large number of entries. This is used to verify large key stores and
* post-handshake messages in SslEngineValidator with TLSv3/JDK17+
* @param tlsProtocol
* @return ssl configs
* @throws Exception
*/
public static Map<String, Object> generateConfigsWithCertificateChains(String tlsProtocol) throws Exception {
int nrOfCerts = 10;
KeyPair[] keyPairs = new KeyPair[nrOfCerts];
for (int i = 0; i < nrOfCerts; i++) {
keyPairs[i] = TestSslUtils.generateKeyPair("RSA");
}
//add a bunch of hostNames to keystore to increase the keystore size
String[] hostNames = new String[150];
for (int i = 0; i < hostNames.length; i++) {
hostNames[i] = "hostName" + i;
}
X509Certificate[] certs = new X509Certificate[nrOfCerts];
// Generate root CA
int caIndex = nrOfCerts - 1;
certs[caIndex] = TestSslUtils.generateSignedCertificate("CN=CA", keyPairs[caIndex], 365,
365, null, null, "SHA512withRSA", true, false, false, hostNames);
//Generate Intermediate certificates
for (int intermediateCertIndex = caIndex - 1; intermediateCertIndex > 0; intermediateCertIndex--) {
certs[intermediateCertIndex] = TestSslUtils.generateSignedCertificate("CN=Intermediate CA" + intermediateCertIndex,
keyPairs[intermediateCertIndex], 365, 365, certs[intermediateCertIndex + 1].getSubjectX500Principal().getName(),
keyPairs[intermediateCertIndex + 1], "SHA512withRSA", true, false, false, hostNames);
}
// Generate a valid end certificate
certs[0] = TestSslUtils.generateSignedCertificate("CN=kafka", keyPairs[0], 1, 1,
certs[1].getSubjectX500Principal().getName(), keyPairs[1], "SHA512withRSA", false, true, true, hostNames);
File keystoreStoreFile = TestUtils.tempFile("keystore", ".jks");
Password keyStorePassword = new Password("password");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.setKeyEntry("issued-cert", keyPairs[0].getPrivate(), keyStorePassword.value().toCharArray(), certs);
saveKeyStore(keyStore, keystoreStoreFile.getPath(), keyStorePassword);
File trustStoreFile = TestUtils.tempFile("truststore", ".jks");
Password trustStorePassword = new Password("password");
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(null, null);
for (X509Certificate cert : certs) {
trustStore.setCertificateEntry(cert.getSubjectX500Principal().getName(), cert);
}
saveKeyStore(trustStore, trustStoreFile.getPath(), trustStorePassword);
Map<String, Object> sslConfigs = new HashMap<>();
sslConfigs.put(SslConfigs.SSL_PROTOCOL_CONFIG, tlsProtocol); // protocol to create SSLContext
sslConfigs.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, keystoreStoreFile.getPath());
sslConfigs.put(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG, "JKS");
sslConfigs.put(SslConfigs.SSL_KEYMANAGER_ALGORITHM_CONFIG, TrustManagerFactory.getDefaultAlgorithm());
sslConfigs.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, keyStorePassword);
sslConfigs.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, keyStorePassword);
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, trustStoreFile.getPath());
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, trustStorePassword);
sslConfigs.put(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, "JKS");
sslConfigs.put(SslConfigs.SSL_TRUSTMANAGER_ALGORITHM_CONFIG, TrustManagerFactory.getDefaultAlgorithm());
List<String> enabledProtocols = new ArrayList<>();
enabledProtocols.add(tlsProtocol);
sslConfigs.put(SslConfigs.SSL_ENABLED_PROTOCOLS_CONFIG, enabledProtocols);
return sslConfigs;
}
}