BCCertFactory.java
/*
** Module : BCCertFactory.java
** Abstract : Implementation to generate SSL certificates and their private keys using Bouncy
** Castle library.
**
** Copyright (c) 2014-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 CA 20140207 First version.
** 002 CA 20160106 Enforce a RSA key length of at least 2048 bits.
** 003 GES 20170203 Expose a helper routine as a static.
** 004 CA 20170603 Include the subject alternative name (as DNS) in the certificate's extensions;
** this solves problems with validating the certificate in recent versions of FF
** and Chrome.
** 005 CA 20170630 Added setRootCA().
** 006 SBI 20171030 Added loadRootCA(certPemFile, privateKeyPemFile, certChainPemFile) to load
* external public and private key pair and its trusted certificate chain.
** 007 SBI 20171109 Fixed loadRootCA to load trusted certificate chain correctly, to save trusted
** chain into the certificates store.
** SBI 20171111 Added getFullChainFromRoot and setCertificateChain.
** 008 SBI 20171112 Changed to load certificate suite.
** SBI 20171116 Changed loadCertificateSuite to support the case of using the full chain file.
** 009 TJD 20240220 Removed compiler warnings related with libraries updates
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.security;
import java.io.*;
import java.math.*;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.security.spec.*;
import java.util.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.oiw.*;
import org.bouncycastle.asn1.pkcs.*;
import org.bouncycastle.asn1.x500.*;
import org.bouncycastle.asn1.x500.style.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.*;
import org.bouncycastle.cert.jcajce.*;
import org.bouncycastle.crypto.*;
import org.bouncycastle.crypto.engines.*;
import org.bouncycastle.crypto.generators.*;
import org.bouncycastle.crypto.paddings.*;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.util.*;
import org.bouncycastle.jce.provider.*;
import org.bouncycastle.openssl.*;
import org.bouncycastle.openssl.jcajce.*;
import org.bouncycastle.operator.*;
import org.bouncycastle.operator.bc.*;
import org.bouncycastle.operator.jcajce.*;
// explicit declaration to resolve Base64
import com.goldencode.p2j.directory.Base64;
import com.goldencode.util.*;
/**
* Implementation to generate SSL certificates and their private keys, using the Bouncy Castle
* library.
* <p>
* Before starting to generate certificates, a root CA needs to be generated by calling
* {@link #generateRootCA}. This will be saved and will be used for signing all the issued
* certificates, using this {@link BCCertFactory} instance.
*/
class BCCertFactory
extends SSLCertFactory
{
/** Secure random to generate the keys. */
private final SecureRandom srnd = new SecureRandom();
/** Helper to generate X509 extensions. */
private final X509ExtensionUtils extUtils;
/** The Bouncy Castle-style root CA certificate. */
private X509CertificateHolder rootCAHolder = null;
/** The private key for the root CA certificate, used to sign all issued certificates. */
private AsymmetricKeyParameter rootCAPrivateKey;
/** The X509-encoded root CA certificate. */
private X509Certificate rootCAX509Cert;
/** The certificate chain from the root CA up to the well-known authority.*/
private List<Certificate> certChain = new LinkedList<Certificate>();
static
{
// register the Bouncy Castle as a security algorithm provider.
Security.addProvider(new BouncyCastleProvider());
}
/**
* Create a new Bouncy Castle-style SSL certificate factory.
*
* @throws SSLCertGenException
* If the factory could not be initialized.
*/
public BCCertFactory()
throws SSLCertGenException
{
DigestCalculator digCalc;
try
{
digCalc = new BcDigestCalculatorProvider()
.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
this.extUtils = new X509ExtensionUtils(digCalc);
}
catch (OperatorCreationException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Encrypt or decrypt the given byte array, using AES and the provided password.
*
* @param encrypt
* Flag indicating if we need to encrypt or decrypt.
* @param bytes
* The bytes to be processed.
* @param password
* The encrypt/decrypt password.
*
* @return The processed byte array.
*
* @throws SSLCertGenException
* If the was problems during processing.
*/
public static byte[] cipherBytes(boolean encrypt, byte[] bytes, String password)
throws SSLCertGenException
{
int bits = password.getBytes().length * 8;
if (!(bits == 128 || bits == 192 || bits == 256))
{
throw new SSLCertGenException("The AES key must have 128, 192 or 256 bits!");
}
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(AESEngine.newInstance());
cipher.init(encrypt, new KeyParameter(password.getBytes()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
byte[] inBuf = new byte[16];
byte[] outBuf = new byte[512];
int bytesRead = -1;
int bytesProcessed = 0;
try
{
while ((bytesRead = in.read(inBuf)) >= 0)
{
bytesProcessed = cipher.processBytes(inBuf, 0, bytesRead, outBuf, 0);
out.write(outBuf, 0, bytesProcessed);
}
bytesProcessed = cipher.doFinal(outBuf, 0);
out.write(outBuf, 0, bytesProcessed);
out.flush();
}
catch (DataLengthException |
IllegalStateException |
InvalidCipherTextException |
IOException e)
{
throw new SSLCertGenException(e);
}
return out.toByteArray();
}
/**
* Initiatialize this SSL certificate factory.
* <p>
* The existing root CA will be removed.
*
* @param keyStrength
* The private key size: {@link SSLCertFactory#MIN_RSA_KEY_STRENGTH} bits or better.
* If {@code null}, defaults to {@link SSLCertFactory#MIN_RSA_KEY_STRENGTH}.
* @param exponent
* The public key exponent. If {@code null}, defaults to 65537.
* WARNING: a wrong value may result in vulnerable SSL private keys and also 3rd party
* software might not accept them. Use with care.
*
* @throws SSLCertGenException
* If the factory could not be instantiated.
*/
public void init(Integer keyStrength, BigInteger exponent)
throws SSLCertGenException
{
super.init(keyStrength, exponent);
this.rootCAHolder = null;
this.rootCAPrivateKey = null;
this.rootCAX509Cert = null;
}
/**
* Decrypt a private key which was previously AES encrypted with the given password.
*
* @param encrypted
* The bytes representing the encrypted private key.
* @param password
* The encryption password.
*
* @return The decrypted {@link PrivateKey private key}.
*
* @throws SSLCertGenException
* If the private key could not be decrypted.
*/
@Override
public PrivateKey decryptPrivateKey(byte[] encrypted, String password)
throws SSLCertGenException
{
try
{
byte[] encoded = cipherBytes(false, encrypted, password);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey key = kf.generatePrivate(keySpec);
return key;
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Encrypt the given key using AES and the provided password.
*
* @param key
* The key to be encrypted.
* @param password
* The encryption password.
*
* @return A byte array with the encrypted key.
*
* @throws SSLCertGenException
* If the key could not be encrypted.
*/
@Override
public byte[] encryptPrivateKey(Key key, String password)
throws SSLCertGenException
{
return cipherBytes(true, key.getEncoded(), password);
}
/**
* Generate a certificate and sign it with the already generated root CA.
* <p>
* The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
* password will be returned by this API.
* <p>
* The public certificate will be saved in the specified {@code certStore} and will be signed
* using the {@link #rootCAPrivateKey}.
*
* @param alias
* The certificate alias, used to store the private key and certificate.
* @param validity
* The certificate validity, in years.
* @param commonName
* The certificate's common name (CN).
* @param fieldMap
* A map with additional subject attributes.
* @param certStore
* The store where to save the certificate.
* @param certKeyStore
* The store where to save the private key.
*
* @return The password used to encrypt the private key in the store.
*
* @throws SSLCertGenException
* If the root CA is not yet generated or the certificate could not be generated.
*/
@Override
public String generateCertificate(String alias,
int validity,
String commonName,
Map<String, String> fieldMap,
KeyStore certStore,
KeyStore certKeyStore)
throws SSLCertGenException
{
if (rootCAHolder == null)
{
throw new SSLCertGenException("First a root CA must be generated using generateRootCA!");
}
X500Name subject = buildSubject(fieldMap, commonName);
AsymmetricCipherKeyPair subjectKeyPair = generateKeyPair();
// KeyPurposeId[] usages =
// {
// KeyPurposeId.anyExtendedKeyUsage,
// KeyPurposeId.id_kp_serverAuth,
// KeyPurposeId.id_kp_clientAuth
// };
X509CertificateHolder cert = generateCertificate(validity, subject, subjectKeyPair,
rootCAHolder, rootCAPrivateKey,
false, null);
X509Certificate x509 = convertCertificate(cert);
try
{
x509.verify(rootCAX509Cert.getPublicKey());
}
catch (Exception e)
{
throw new SSLCertGenException(e);
}
try
{
certStore.setCertificateEntry(alias, x509);
}
catch (KeyStoreException e)
{
throw new SSLCertGenException(e);
}
String keyPassword = RandomWordGenerator.create();
Certificate[] rootChain = getFullChainFromRoot();
Certificate[] endEntityChain = new Certificate[rootChain.length + 1];
endEntityChain[0] = x509;
System.arraycopy(rootChain, 0, endEntityChain, 1, rootChain.length);
// save the private key to the keystore
saveKeyEntry(certKeyStore,
subjectKeyPair,
alias,
keyPassword.toCharArray(),
endEntityChain);
return keyPassword;
}
/**
* Generate a self-signed root CA certificate, which will be used to sign all the issues
* certificates.
* <p>
* The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
* password will be returned by this API.
* <p>
* The public root CA certificate will be saved in the specified {@code certStore}.
*
* @param alias
* The certificate alias, used to store the private key and certificate.
* @param validity
* The certificate validity, in years.
* @param commonName
* The certificate's common name (CN).
* @param fieldMap
* A map with additional subject attributes.
* @param certStore
* The store where to save the certificate.
* @param certKeyStore
* The store where to save the private key.
*
* @return The password used to encrypt the private key in the store.
*
* @throws SSLCertGenException
* If the root CA is not yet generated or the certificate could not be generated.
*/
@Override
public String generateRootCA(String alias,
int validity,
String commonName,
Map<String, String> fieldMap,
KeyStore certStore,
KeyStore certKeyStore)
throws SSLCertGenException
{
return generateSelfSignedCertificate(alias, true, validity, commonName, fieldMap,
certStore, certKeyStore);
}
/**
* Set the details for the root CA certificate.
*
* @param cert
* The certificate.
* @param pk
* The private key.
*/
@Override
public void setRootCA(X509Certificate cert, PrivateKey pk)
throws SSLCertGenException
{
try
{
this.rootCAX509Cert = cert;
this.rootCAHolder = new JcaX509CertificateHolder(cert);
String pkPEM = "-----BEGIN PRIVATE KEY-----\n" +
Base64.byteArrayToBase64(pk.getEncoded()) +
"\n-----END PRIVATE KEY-----\n";
PEMParser parser = new PEMParser(new StringReader(pkPEM));
this.rootCAPrivateKey = PrivateKeyFactory.createKey((PrivateKeyInfo) parser.readObject());
parser.close();
}
catch (CertificateEncodingException | IOException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Loads externally generated certificate suite of public and private certificates with
* trusted certificate chain if this PEM file is provided.
*
* @param certPemFile
* The PEM file that contains the public certificate signed by well-known authorities
* or self-signed.
* @param privateKeyPemFile
* The PEM file that contains the private certificate signed by well-known authorities
* or self-signed.
* @param certChainPemFile
* The PEM file that contains the intermediate certificates up to the well-known
* authority CA certificate.
*
* @return The certificate suite
*
* @throws SSLCertGenException
* If IO or parse exceptions are thrown
*/
public CertificateSuite loadCertificateSuite(String certPemFile,
String privateKeyPemFile,
String certChainPemFile)
throws SSLCertGenException
{
CertificateSuite suite = new CertificateSuite();
PrivateKey privateKey = null;
X509Certificate publicKey = null;
X509Certificate[] chain = null;
try
{
try (FileReader certReader = new FileReader(certPemFile);
PEMParser certParser = new PEMParser(certReader);
FileReader privateKeyReader = new FileReader(privateKeyPemFile);
PEMParser privateKeyParser = new PEMParser(privateKeyReader);
)
{
PrivateKeyInfo privateKeyInfo = null;
X509CertificateHolder cert = null;
PEMKeyPair pemKeyPair = null;
Object obj;
while((obj = privateKeyParser.readObject()) != null)
{
if (obj instanceof PrivateKeyInfo && privateKeyInfo == null)
{
// private key found
privateKeyInfo = (PrivateKeyInfo) obj;
}
else if (obj instanceof PEMKeyPair && privateKeyInfo == null)
{
// it contains a private and public keys pair
pemKeyPair = (PEMKeyPair) obj;
privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
}
else if (obj instanceof X509CertificateHolder && cert == null)
{// this case ?
//it contains a certificate
cert = (X509CertificateHolder) obj;
}
}
while((obj = certParser.readObject()) != null)
{
if (obj instanceof PrivateKeyInfo && privateKeyInfo == null)
{
// private key found
privateKeyInfo = (PrivateKeyInfo) obj;
}
else if (obj instanceof PEMKeyPair && privateKeyInfo == null)
{
// it contains a private and public keys pair
pemKeyPair = (PEMKeyPair) obj;
privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
}
else if (obj instanceof X509CertificateHolder && cert == null)
{
//it contains a certificate
cert = (X509CertificateHolder) obj;
}
}
if (privateKeyInfo == null)
{
throw new SSLCertGenException("Private key was not found!");
}
if (cert == null)
{
throw new SSLCertGenException("Certificate was not found!");
}
privateKey = new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
publicKey = convertCertificate(cert);
List<X509Certificate> chainList = new LinkedList<>();
if (certChainPemFile != null && !certChainPemFile.isEmpty())
{
// chain pem file provided
try(FileReader certChainReader = new FileReader(certChainPemFile);
PEMParser certChainParser = new PEMParser(certChainReader);)
{
while((obj = certChainParser.readObject()) != null)
{
if (obj instanceof X509CertificateHolder)
{
chainList.add(convertCertificate((X509CertificateHolder) obj));
}
}
}
}
// chainList can be a full chain
if (!chainList.isEmpty() && chainList.get(0).equals(publicKey))
{
chainList.remove(0);
}
chain = chainList.toArray(new X509Certificate[chainList.size()]);
}
suite.publicKey = publicKey;
suite.privateKey = privateKey;
suite.chain = chain;
return suite;
}
catch (IOException ex)
{
throw new SSLCertGenException(ex);
}
}
/**
* Generate a self-signed certificate. This may be used as a root CA.
* <p>
* The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
* password will be returned by this API.
* <p>
* The public certificate will be saved in the specified {@code certStore}.
*
* @param alias
* The certificate alias, used to store the private key and certificate.
* @param certificateAuthority
* Flag indicating if the generated self-signed certificate will be used as the root
* CA.
* @param validity
* The certificate validity, in years.
* @param commonName
* The certificate's common name (CN).
* @param fieldMap
* A map with additional subject attributes.
* @param certStore
* The store where to save the certificate.
* @param certKeyStore
* The store where to save the private key.
*
* @return The password used to encrypt the private key in the store.
*
* @throws SSLCertGenException
* If the root CA is not yet generated or the certificate could not be generated.
*/
@Override
public String generateSelfSignedCertificate(String alias,
boolean certificateAuthority,
int validity,
String commonName,
Map<String, String> fieldMap,
KeyStore certStore,
KeyStore certKeyStore)
throws SSLCertGenException
{
X500Name subject = buildSubject(fieldMap, commonName);
AsymmetricCipherKeyPair subjectKeyPair = generateKeyPair();
AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;
X509CertificateHolder certHolder = generateCertificate(validity,
subject, subjectKeyPair,
null, issuerKeyPair.getPrivate(),
certificateAuthority, null);
X509Certificate x509 = convertCertificate(certHolder);
try
{
x509.verify(x509.getPublicKey());
}
catch (Exception e)
{
throw new SSLCertGenException(e);
}
if (certificateAuthority)
{
// save these, for later usage
this.rootCAHolder = certHolder;
this.rootCAPrivateKey = subjectKeyPair.getPrivate();
this.rootCAX509Cert = x509;
}
// save the certificate to the store
return saveRootCA(alias, certStore, certKeyStore);
}
/**
* Save the root CA into the provided certificate and private key stores to be accessed by the
* given alias.
*
* @param alias
* The certificate alias
* @param certStore
* The certificate store
* @param certKeyStore
* The certificate private key store
*
* @return The protected private key password.
*
* @throws SSLCertGenException
*
*/
public String saveRootCA(String alias,
KeyStore certStore,
KeyStore certKeyStore)
throws SSLCertGenException
{
// save the certificate to the store
try
{
certStore.setCertificateEntry(alias, this.rootCAX509Cert);
}
catch (KeyStoreException e)
{
throw new SSLCertGenException(e);
}
// save the private key to the keystore
try
{
String keyPassword = RandomWordGenerator.create();
Certificate[] chain = getFullChainFromRoot();
saveKeyEntry(certKeyStore,
new AsymmetricCipherKeyPair(
PublicKeyFactory.createKey(this.rootCAHolder.getSubjectPublicKeyInfo()),
this.rootCAPrivateKey),
alias,
keyPassword.toCharArray(),
chain);
return keyPassword;
}
catch (IOException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Build a full chain from the root certificate up to the well-known authority.
*
* @return Certificate chain
*/
public Certificate[] getFullChainFromRoot()
{
Certificate[] chain;
if (this.certChain.isEmpty())
{
chain = new Certificate[] { this.rootCAX509Cert };
}
else
{
List<Certificate> list;
// check the first certificate in this chain (full chain?)
if (certChain.get(0).equals(this.rootCAX509Cert))
{
list = this.certChain;
}
else
{
list = new LinkedList<Certificate>();
list.add(this.rootCAX509Cert);
list.addAll(this.certChain);
}
chain = list.toArray(new Certificate[list.size()]);
}
return chain;
}
/**
* Get the map with the mandatory subject attributes. The key is the string representation of
* this attribute, while the value will be the associated description.
* <p>
* This includes the "Organization Unit (OU)", "Organization (O)", "Locality (city, etc) (L)",
* "State or Province Name (ST)" and "Country Code (C)" fields.
* @return See above.
*/
@Override
public Map<String, String> getMandatorySubjectFields()
{
ASN1ObjectIdentifier[] keys =
{
BCStyle.OU,
BCStyle.O,
BCStyle.L,
BCStyle.ST,
BCStyle.C,
};
String[] descr =
{
"Organization Unit",
"Organization",
"Locality (city, etc)",
"State or Province Name",
"Country Code"
};
Map<String, String> mandatoryKeys = new LinkedHashMap<>();
for (int i = 0; i < keys.length; i++)
{
String nm = BCStyle.INSTANCE.oidToDisplayName(keys[i]).toUpperCase();
mandatoryKeys.put(nm, descr[i]);
}
return mandatoryKeys;
}
/**
* Generate a certificate and sign it with the private key for the specified issuer.
*
* @param validity
* The certificate validity, in years.
* @param subject
* X500-style subject name.
* @param subjectKeyPair
* The subject's asymmetric private-public key pair.
* @param issuer
* The issuer's Bouncy Castle-style certificate. If <code>null</code>, it will be
* self-signed.
* @param issuerPrivateKey
* The issuer's private key, to sign the new certificate.
* @param certificateAuthority
* Flag indicating if this certificate should be marked as a CA.
* @param usages
* Extended usages for this certificate.
*
* @return The generated Bouncy Castle-style certificate.
*
* @throws SSLCertGenException
* In case of problems during the certificate generation.
*/
private X509CertificateHolder generateCertificate(int validity,
X500Name subject,
AsymmetricCipherKeyPair subjectKeyPair,
X509CertificateHolder issuer,
AsymmetricKeyParameter issuerPrivateKey,
boolean certificateAuthority,
KeyPurposeId[] usages)
throws SSLCertGenException
{
Calendar cnotBefore = new GregorianCalendar();
Calendar cnotAfter = new GregorianCalendar();
cnotAfter.add(Calendar.YEAR, validity);
Date notBefore = new Date(cnotBefore.getTimeInMillis());
Date notAfter = new Date(cnotAfter.getTimeInMillis());
X500Name issuerSubject = (issuer == null) ? subject : issuer.getIssuer();
BigInteger subjectSerial = new BigInteger(srnd.nextInt(100), srnd);
X509v3CertificateBuilder builder;
ContentSigner signer;
try
{
SubjectPublicKeyInfo publicInfo =
SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(subjectKeyPair.getPublic());
PrivateKeyInfo privateInfo =
PrivateKeyInfoFactory.createPrivateKeyInfo(issuerPrivateKey);
builder = new X509v3CertificateBuilder(issuerSubject, subjectSerial,
notBefore, notAfter,
subject, publicInfo);
JcaContentSignerBuilder jcaSigner = new JcaContentSignerBuilder("SHA256WithRSA");
jcaSigner.setProvider(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME));
jcaSigner.setSecureRandom(srnd);
signer = jcaSigner.build(BouncyCastleProvider.getPrivateKey(privateInfo));
AuthorityKeyIdentifier ext1 =
(issuer == null) ? extUtils.createAuthorityKeyIdentifier(publicInfo)
: extUtils.createAuthorityKeyIdentifier(issuer);
SubjectKeyIdentifier ext2 = extUtils.createSubjectKeyIdentifier(publicInfo);
BasicConstraints ext3 = new BasicConstraints(certificateAuthority);
String cn = IETFUtils.valueToString(subject.getRDNs(BCStyle.CN)[0].getFirst().getValue());
GeneralNames ext4 = new GeneralNames(new GeneralName(GeneralName.dNSName, cn));
builder.addExtension(Extension.authorityKeyIdentifier, false, ext1);
builder.addExtension(Extension.subjectKeyIdentifier, false, ext2);
builder.addExtension(Extension.basicConstraints, true, ext3);
builder.addExtension(Extension.subjectAlternativeName, false, ext4);
if (usages != null && usages.length > 0)
{
builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(usages));
}
return builder.build(signer);
}
catch (OperatorCreationException | IOException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Generate an asymmetric key pair using the RSA algorithm.
*
* @return See above.
*/
private AsymmetricCipherKeyPair generateKeyPair()
{
// certainty (last parameter) is passed to the BigInteger.isProbablePrime(certainty)
// and represents the number of Miller-Rabin iterations. A large value works OK for small
// numbers, but with large numbers (>1024 bits) BigInteger.isProbablePrime defaults to a
// 2-round Miller-Rabin primality test.
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(exponent, srnd,
keyStrength, 80);
RSAKeyPairGenerator gen = new RSAKeyPairGenerator();
gen.init(params);
return gen.generateKeyPair();
}
/**
* Encrypt and save the private key in the specified store.
*
* @param store
* The key store where to save the private key.
* @param keyPair
* The asymmetric key pair, from which the private key will be extracted.
* @param alias
* The alias used to save the private key in the store.
* @param keyentryPassword
* The password to encrypt the private key.
* @param chain
* The certificate chain.
*
* @throws SSLCertGenException
*/
private void saveKeyEntry(KeyStore store,
AsymmetricCipherKeyPair keyPair,
String alias,
char[] keyentryPassword,
Certificate[] chain)
throws SSLCertGenException
{
try
{
PrivateKeyInfo privateInfo =
PrivateKeyInfoFactory.createPrivateKeyInfo(keyPair.getPrivate());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateInfo.getEncoded());
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey key = kf.generatePrivate(keySpec);
store.setKeyEntry(alias, key, keyentryPassword, chain);
}
catch (IOException |
NoSuchAlgorithmException |
InvalidKeySpecException |
KeyStoreException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Using the passed field map and the Common Name, build the X500-style distinguished name.
*
* @param fieldMap
* A map with the subject fields.
* @param commonName
* The common name.
*
* @return X500-style subject.
*/
private X500Name buildSubject(Map<String, String> fieldMap, String commonName)
{
X500NameBuilder builder = new X500NameBuilder();
for (String key : fieldMap.keySet())
{
String val = fieldMap.get(key);
builder.addRDN(BCStyle.INSTANCE.attrNameToOID(key), val);
}
builder.addRDN(BCStyle.CN, commonName);
return builder.build();
}
/**
* Generate a X509-style certificate from the Bouncy Castle-style X509-holder instance.
*
* @param cert
* The Bouncy Castle-style certificate.
*
* @return The X509 certificate.
*
* @throws SSLCertGenException
* If the certificate could not be converted.
*/
private X509Certificate convertCertificate(X509CertificateHolder cert)
throws SSLCertGenException
{
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
converter.setProvider(BouncyCastleProvider.PROVIDER_NAME);
try
{
return converter.getCertificate(cert);
}
catch (CertificateException e)
{
throw new SSLCertGenException(e);
}
}
/**
* Sets the trusted certificate chain.
*
* @param chain
* The root certificate chain up to the well-known authority
*/
@Override
public void setCertificateChain(Certificate[] chain)
{
certChain.clear();
for (Certificate cert : chain)
{
certChain.add(cert);
}
}
}