net.eightlives.friendlyssl.service.PKCS12KeyStoreService Maven / Gradle / Ivy
package net.eightlives.friendlyssl.service;
import net.eightlives.friendlyssl.config.FriendlySSLConfig;
import net.eightlives.friendlyssl.exception.KeyStoreGeneratorException;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.engines.RC2Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.pkcs.*;
import org.bouncycastle.pkcs.bc.BcPKCS12MacCalculatorBuilder;
import org.bouncycastle.pkcs.bc.BcPKCS12PBEOutputEncryptorBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Optional;
@Component
public class PKCS12KeyStoreService {
private static final Logger LOG = LoggerFactory.getLogger(PKCS12KeyStoreService.class);
private static final String ROOT_FRIENDLY_NAME = "root";
private static final String KEYSTORE_TYPE = "PKCS12";
private static final String KEYFACTORY_TYPE = "RSA";
private final FriendlySSLConfig config;
private final LocalIdGeneratorService localIdGeneratorService;
public PKCS12KeyStoreService(FriendlySSLConfig config,
LocalIdGeneratorService localIdGeneratorService) {
this.config = config;
this.localIdGeneratorService = localIdGeneratorService;
}
/**
* Generate a PKCS12 keystore for the given certificate chain.
*
* @param certificates the certificate chain to put in the keystore
* @param privateKey the private key used to sign the local certificate. This is the same key that was used for the
* certificate signing request (CSR)
* @return the byte representation of the generated PKCS12 keystore
* @throws KeyStoreGeneratorException if an exception occurs while generating the keystore
*/
public byte[] generateKeyStore(List certificates, PrivateKey privateKey) {
try {
byte[] localKeyBytes = localIdGeneratorService.generate();
PKCS12SafeBag[] certBags = new PKCS12SafeBag[certificates.size()];
for (int i = certificates.size() - 1; i >= 0; i--) {
var certBagBuilder = new JcaPKCS12SafeBagBuilder(certificates.get(i));
if (i == 0) {
certBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(config.getCertificateKeyAlias()));
certBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new DEROctetString(localKeyBytes));
} else {
certBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(ROOT_FRIENDLY_NAME));
}
certBags[i] = certBagBuilder.build();
}
PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privateKey,
new BcPKCS12PBEOutputEncryptorBuilder(
PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC,
new CBCBlockCipher(new DESedeEngine())).setIterationCount(2048)
.build("".toCharArray()));
keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(config.getCertificateKeyAlias()));
keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new DEROctetString(localKeyBytes));
PKCS12PfxPduBuilder pfxBuilder = new PKCS12PfxPduBuilder();
pfxBuilder.addEncryptedData(
new BcPKCS12PBEOutputEncryptorBuilder(
PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC,
new CBCBlockCipher(new RC2Engine())).setIterationCount(2048)
.build("".toCharArray()), certBags);
pfxBuilder.addData(keyBagBuilder.build());
BcPKCS12MacCalculatorBuilder macBuilder = new BcPKCS12MacCalculatorBuilder();
macBuilder.setIterationCount(2048);
PKCS12PfxPdu pfx = pfxBuilder.build(macBuilder, "".toCharArray());
return pfx.getEncoded(ASN1Encoding.DL);
} catch (PKCSException | IOException e) {
throw new KeyStoreGeneratorException(e);
}
}
/**
* Return a key pair comprised of a private key from the configured keystore with the given alias and a public
* key from the given certificate.
*
* @param keyAlias the alias with which to retrieve the key pair from the configured key store
* @return a key pair comprised of a private key from the configured keystore with the given alias and a public
* key from the given certificate, or {@code null} if an exception occurs while accessing the keystore
* while accessing the keystore
*/
public KeyPair getKeyPair(String keyAlias) {
try {
KeyStore store = KeyStore.getInstance(KEYSTORE_TYPE);
store.load(Files.newInputStream(Path.of(config.getKeystoreFile())), "".toCharArray());
KeyFactory keyFactory = KeyFactory.getInstance(KEYFACTORY_TYPE);
Key key = store.getKey(keyAlias, "".toCharArray());
if (key == null) {
LOG.error("Private key alias " + keyAlias +
" not found in keystore " + config.getKeystoreFile() +
" when loading keystore");
return null;
}
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(key.getEncoded()));
Certificate certificate = store.getCertificate(keyAlias);
if (certificate == null) {
LOG.error("Certificate with alias " + keyAlias +
" not found in keystore " + config.getKeystoreFile() +
" when loading keystore");
return null;
}
return new KeyPair(certificate.getPublicKey(), privateKey);
} catch (InvalidKeySpecException | NoSuchAlgorithmException | CertificateException | KeyStoreException
| UnrecoverableKeyException | IOException e) {
LOG.error("Exception while accessing keystore", e);
return null;
}
}
/**
* Return a certificate from the configured keystore with the given alias.
*
* @param keyAlias the alias of the certificate to retrieve
* @return the certificate in the keystore with the given alias, or {@link Optional#empty()} if an exception occurs
* while accessing the keystore
*/
public Optional getCertificate(String keyAlias) {
try {
KeyStore store = KeyStore.getInstance(KEYSTORE_TYPE);
try {
store.load(Files.newInputStream(Path.of(config.getKeystoreFile())), "".toCharArray());
} catch (NoSuchFileException e) {
return Optional.empty();
}
Certificate certificate = store.getCertificate(keyAlias);
if (certificate instanceof X509Certificate) {
return Optional.of((X509Certificate) certificate);
}
return Optional.empty();
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
LOG.error("Exception while accessing keystore", e);
return Optional.empty();
}
}
}