-
-
Save vivekkr12/c74f7ee08593a8c606ed96f4b62a208a to your computer and use it in GitHub Desktop.
import org.bouncycastle.asn1.ASN1Encodable; | |
import org.bouncycastle.asn1.DERSequence; | |
import org.bouncycastle.asn1.x500.X500Name; | |
import org.bouncycastle.asn1.x509.BasicConstraints; | |
import org.bouncycastle.asn1.x509.Extension; | |
import org.bouncycastle.asn1.x509.GeneralName; | |
import org.bouncycastle.asn1.x509.KeyUsage; | |
import org.bouncycastle.cert.X509CertificateHolder; | |
import org.bouncycastle.cert.X509v3CertificateBuilder; | |
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; | |
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; | |
import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
import org.bouncycastle.operator.ContentSigner; | |
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; | |
import org.bouncycastle.pkcs.PKCS10CertificationRequest; | |
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; | |
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; | |
import org.bouncycastle.util.encoders.Base64; | |
import java.io.FileOutputStream; | |
import java.math.BigInteger; | |
import java.security.*; | |
import java.security.cert.Certificate; | |
import java.security.cert.X509Certificate; | |
import java.util.Calendar; | |
import java.util.Date; | |
public class BouncyCastleCertificateGenerator { | |
private static final String BC_PROVIDER = "BC"; | |
private static final String KEY_ALGORITHM = "RSA"; | |
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; | |
public static void main(String[] args) throws Exception{ | |
// Add the BouncyCastle Provider | |
Security.addProvider(new BouncyCastleProvider()); | |
// Initialize a new KeyPair generator | |
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM, BC_PROVIDER); | |
keyPairGenerator.initialize(2048); | |
// Setup start date to yesterday and end date for 1 year validity | |
Calendar calendar = Calendar.getInstance(); | |
calendar.add(Calendar.DATE, -1); | |
Date startDate = calendar.getTime(); | |
calendar.add(Calendar.YEAR, 1); | |
Date endDate = calendar.getTime(); | |
// First step is to create a root certificate | |
// First Generate a KeyPair, | |
// then a random serial number | |
// then generate a certificate using the KeyPair | |
KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); | |
BigInteger rootSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); | |
// Issued By and Issued To same for root certificate | |
X500Name rootCertIssuer = new X500Name("CN=root-cert"); | |
X500Name rootCertSubject = rootCertIssuer; | |
ContentSigner rootCertContentSigner = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER).build(rootKeyPair.getPrivate()); | |
X509v3CertificateBuilder rootCertBuilder = new JcaX509v3CertificateBuilder(rootCertIssuer, rootSerialNum, startDate, endDate, rootCertSubject, rootKeyPair.getPublic()); | |
// Add Extensions | |
// A BasicConstraint to mark root certificate as CA certificate | |
JcaX509ExtensionUtils rootCertExtUtils = new JcaX509ExtensionUtils(); | |
rootCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); | |
rootCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, rootCertExtUtils.createSubjectKeyIdentifier(rootKeyPair.getPublic())); | |
// Create a cert holder and export to X509Certificate | |
X509CertificateHolder rootCertHolder = rootCertBuilder.build(rootCertContentSigner); | |
X509Certificate rootCert = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(rootCertHolder); | |
writeCertToFileBase64Encoded(rootCert, "root-cert.cer"); | |
exportKeyPairToKeystoreFile(rootKeyPair, rootCert, "root-cert", "root-cert.pfx", "PKCS12", "pass"); | |
// Generate a new KeyPair and sign it using the Root Cert Private Key | |
// by generating a CSR (Certificate Signing Request) | |
X500Name issuedCertSubject = new X500Name("CN=issued-cert"); | |
BigInteger issuedCertSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); | |
KeyPair issuedCertKeyPair = keyPairGenerator.generateKeyPair(); | |
PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(issuedCertSubject, issuedCertKeyPair.getPublic()); | |
JcaContentSignerBuilder csrBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER); | |
// Sign the new KeyPair with the root cert Private Key | |
ContentSigner csrContentSigner = csrBuilder.build(rootKeyPair.getPrivate()); | |
PKCS10CertificationRequest csr = p10Builder.build(csrContentSigner); | |
// Use the Signed KeyPair and CSR to generate an issued Certificate | |
// Here serial number is randomly generated. In general, CAs use | |
// a sequence to generate Serial number and avoid collisions | |
X509v3CertificateBuilder issuedCertBuilder = new X509v3CertificateBuilder(rootCertIssuer, issuedCertSerialNum, startDate, endDate, csr.getSubject(), csr.getSubjectPublicKeyInfo()); | |
JcaX509ExtensionUtils issuedCertExtUtils = new JcaX509ExtensionUtils(); | |
// Add Extensions | |
// Use BasicConstraints to say that this Cert is not a CA | |
issuedCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); | |
// Add Issuer cert identifier as Extension | |
issuedCertBuilder.addExtension(Extension.authorityKeyIdentifier, false, issuedCertExtUtils.createAuthorityKeyIdentifier(rootCert)); | |
issuedCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, issuedCertExtUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo())); | |
// Add intended key usage extension if needed | |
issuedCertBuilder.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.keyEncipherment)); | |
// Add DNS name is cert is to used for SSL | |
issuedCertBuilder.addExtension(Extension.subjectAlternativeName, false, new DERSequence(new ASN1Encodable[] { | |
new GeneralName(GeneralName.dNSName, "mydomain.local"), | |
new GeneralName(GeneralName.iPAddress, "127.0.0.1") | |
})); | |
X509CertificateHolder issuedCertHolder = issuedCertBuilder.build(csrContentSigner); | |
X509Certificate issuedCert = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(issuedCertHolder); | |
// Verify the issued cert signature against the root (issuer) cert | |
issuedCert.verify(rootCert.getPublicKey(), BC_PROVIDER); | |
writeCertToFileBase64Encoded(issuedCert, "issued-cert.cer"); | |
exportKeyPairToKeystoreFile(issuedCertKeyPair, issuedCert, "issued-cert", "issued-cert.pfx", "PKCS12", "pass"); | |
} | |
static void exportKeyPairToKeystoreFile(KeyPair keyPair, Certificate certificate, String alias, String fileName, String storeType, String storePass) throws Exception { | |
KeyStore sslKeyStore = KeyStore.getInstance(storeType, BC_PROVIDER); | |
sslKeyStore.load(null, null); | |
sslKeyStore.setKeyEntry(alias, keyPair.getPrivate(),null, new Certificate[]{certificate}); | |
FileOutputStream keyStoreOs = new FileOutputStream(fileName); | |
sslKeyStore.store(keyStoreOs, storePass.toCharArray()); | |
} | |
static void writeCertToFileBase64Encoded(Certificate certificate, String fileName) throws Exception { | |
FileOutputStream certificateOut = new FileOutputStream(fileName); | |
certificateOut.write("-----BEGIN CERTIFICATE-----".getBytes()); | |
certificateOut.write(Base64.encode(certificate.getEncoded())); | |
certificateOut.write("-----END CERTIFICATE-----".getBytes()); | |
certificateOut.close(); | |
} | |
} |
awesome;)
Very nice gist. Thank you.
Some notes regarding the CSR.
The CSR must be signed by the requesting subject:
// Sign the CSR w/ the request subject key pair
ContentSigner csrContentSigner = csrBuilder.build(issuedCertKeyPair.getPrivate());
So you can validate the CSR signature before issuance using:
boolean isValid = csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build(csr.getSubjectPublicKeyInfo())));
Excellent !!! Thank you for share it.
I tried to read the generated file by your code. when trying to read the file as follows.
CertificateFactory fac = CertificateFactory.getInstance("X509");
FileInputStream is = new FileInputStream(".\\root-cert.cer"); // or issued-cert.cer same error.
X509Certificate cert = (X509Certificate) fac.generateCertificate(is);
System.out.println("cert: "+cert);
java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Incomplete data
at java.base/sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:115)
I suggest updating writeCertToFileBase64Encoded to produce the correct certificate file.
static void writeCertToFileBase64Encoded(Certificate certificate, String fileName) throws Exception {
StringWriter sw = new StringWriter();
try (JcaPEMWriter jpw = new JcaPEMWriter(sw)) {
jpw.writeObject(certificate);
} catch (IOException e) {
throw new RuntimeException(e);
}
String pem = sw.toString();
Path path = Paths.get(fileName);
byte[] strToBytes = pem.getBytes();
Files.write(path, strToBytes);
}
It works !!!
Is there any way to maintain the Certification Chain in this? I am unable to see the Certification Path when generating the certificate like this.
Can you tell me what Java version and bouncycastle .jar or dependency you used?
I'm struggling to find a library that includes all the packages used here.
@jonlindy I wrote it in Java 8 and whatever the latest version of bouncy castle was available at that time which I believe was 1.58. Depending on where you live, you may also need to update your jdk policy files for all things crypto to work. This is a link to the complete project - https://github.com/vivekkr12/java-cryptography-demos/blob/master/pom.xml
Very very useful. Thanks