-
-
Save Bradford1040/defed39237c413f43b993d00d1b84df1 to your computer and use it in GitHub Desktop.
Complete Bouncycastle Certificate and KeyStore example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.jcairns; | |
| import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; | |
| import org.bouncycastle.asn1.x500.X500Name; | |
| import org.bouncycastle.asn1.x509.AlgorithmIdentifier; | |
| import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; | |
| import org.bouncycastle.asn1.x509.BasicConstraints; | |
| import org.bouncycastle.asn1.x509.Extension; | |
| import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; | |
| import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; | |
| import org.bouncycastle.cert.CertIOException; | |
| import org.bouncycastle.cert.X509ExtensionUtils; | |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
| import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; | |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
| import org.bouncycastle.operator.OperatorCreationException; | |
| import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; | |
| import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.FileNotFoundException; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.math.BigInteger; | |
| import java.security.KeyFactory; | |
| import java.security.KeyPair; | |
| import java.security.KeyPairGenerator; | |
| import java.security.KeyStore; | |
| import java.security.KeyStoreException; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.security.PrivateKey; | |
| import java.security.PublicKey; | |
| import java.security.SecureRandom; | |
| import java.security.UnrecoverableEntryException; | |
| import java.security.cert.Certificate; | |
| import java.security.cert.CertificateException; | |
| import java.security.spec.InvalidKeySpecException; | |
| import java.security.spec.PKCS8EncodedKeySpec; | |
| import java.security.spec.X509EncodedKeySpec; | |
| import java.time.Duration; | |
| import java.time.Instant; | |
| import java.util.Arrays; | |
| import java.util.Base64; | |
| import java.util.Date; | |
| public class CertAndKeyStore | |
| { | |
| public static final String HASH_ALGO = "SHA256"; | |
| public static final char[] PASSWORD = "1234".toCharArray(); | |
| public static final String ALIAS = "EXAMPLE"; | |
| public void save(final String path) throws NoSuchAlgorithmException, CertificateException, IOException, OperatorCreationException, KeyStoreException, InvalidKeySpecException, UnrecoverableEntryException { | |
| final var keyStore = loadKeyStore(path); | |
| final var keyPass = "keypass".toCharArray(); | |
| final var cert = keyStore.getCertificate(ALIAS); | |
| if(cert != null) { | |
| System.out.println(getPublicKeyAsString(cert.getPublicKey())); | |
| } | |
| final var entry = keyStore.getEntry(ALIAS, new KeyStore.PasswordProtection(keyPass)); | |
| if(entry != null) { | |
| final var privKey = (KeyStore.PrivateKeyEntry)entry; | |
| System.out.println(getPrivateKeyAsString(privKey.getPrivateKey())); | |
| } else { | |
| createNewCert(keyStore, path); | |
| } | |
| } | |
| public static String getPublicKeyAsString(final PublicKey pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException { | |
| final var fact = KeyFactory.getInstance("RSA"); | |
| final var spec = fact.getKeySpec(pubKey, X509EncodedKeySpec.class); | |
| return new String(Base64.getEncoder().encode(spec.getEncoded())); | |
| } | |
| public static String getPrivateKeyAsString(final PrivateKey privKey) throws NoSuchAlgorithmException, InvalidKeySpecException { | |
| final var fact = KeyFactory.getInstance("RSA"); | |
| final var spec = fact.getKeySpec(privKey, PKCS8EncodedKeySpec.class); | |
| final var pkcs8Key = spec.getEncoded(); | |
| try { | |
| return new String(Base64.getEncoder().encode(pkcs8Key)); | |
| } finally { | |
| Arrays.fill(pkcs8Key, (byte)0); | |
| } | |
| } | |
| private void createNewCert(final KeyStore keyStore, final String path) throws KeyStoreException, CertificateException, IOException, OperatorCreationException, NoSuchAlgorithmException { | |
| final var keyPair = generateKey(); | |
| final var cert = createCert(keyPair, HASH_ALGO, "2ad.com", 180); | |
| final var chain = new Certificate[]{cert}; | |
| final var keyPass = "keypass".toCharArray(); | |
| keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), keyPass, chain); | |
| final var keyFile = new File(path); | |
| keyStore.store(new FileOutputStream(keyFile), PASSWORD); | |
| } | |
| public static KeyStore loadKeyStore(final String path) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { | |
| final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
| final var keyFile = new File(path); | |
| if(keyFile.exists()) { | |
| System.out.println("Load existing store "+keyFile); | |
| keyStore.load(new FileInputStream(keyFile), PASSWORD); | |
| } else { | |
| System.out.println("Create new store "+keyFile); | |
| keyStore.load(null, null); | |
| keyStore.store(new FileOutputStream(keyFile), PASSWORD); | |
| } | |
| return keyStore; | |
| } | |
| public static KeyPair generateKey() throws NoSuchAlgorithmException { | |
| final int size = 2048; | |
| final var generator = KeyPairGenerator.getInstance("RSA"); | |
| final var secureRandom = SecureRandom.getInstance("NativePRNG"/* + "Blocking" */); | |
| // this is expensive! | |
| generator.initialize(size, secureRandom); | |
| return generator.generateKeyPair(); | |
| } | |
| public static Certificate createCert(final KeyPair keyPair, | |
| final String hashAlgo, | |
| final String cn, | |
| final int days) throws OperatorCreationException, CertificateException, CertIOException { | |
| final var now = Instant.now(); | |
| final var notBefore = Date.from(now); | |
| final var notAfter = Date.from(now.plus(Duration.ofDays(days))); | |
| final var privateKey = keyPair.getPrivate(); | |
| final var contentSigner = new JcaContentSignerBuilder(hashAlgo + "with" + privateKey.getAlgorithm()).build(privateKey); | |
| final var x500Name = new X500Name("CN=" + cn); | |
| final var certificateBuilder = new JcaX509v3CertificateBuilder(x500Name, | |
| BigInteger.valueOf(now.toEpochMilli()), | |
| notBefore, | |
| notAfter, | |
| x500Name, | |
| keyPair.getPublic()) | |
| .addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyId(keyPair.getPublic())) | |
| .addExtension(Extension.authorityKeyIdentifier, false, createAuthorityKeyId(keyPair.getPublic())) | |
| .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); | |
| return new JcaX509CertificateConverter() | |
| .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner)); | |
| } | |
| public static SubjectKeyIdentifier createSubjectKeyId(final PublicKey publicKey) throws OperatorCreationException { | |
| final var publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); | |
| final var digCalc = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); | |
| return new X509ExtensionUtils(digCalc).createSubjectKeyIdentifier(publicKeyInfo); | |
| } | |
| public static AuthorityKeyIdentifier createAuthorityKeyId(final PublicKey publicKey) throws OperatorCreationException { | |
| final var publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); | |
| final var digCalc = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); | |
| return new X509ExtensionUtils((digCalc)).createAuthorityKeyIdentifier(publicKeyInfo); | |
| } | |
| public static void main( String[] args ) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, OperatorCreationException, InvalidKeySpecException, UnrecoverableEntryException { | |
| final var certAndKey = new CertAndKeyStore(); | |
| certAndKey.save("keystore.jks"); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |
| <modelVersion>4.0.0</modelVersion> | |
| <groupId>com.jcairns</groupId> | |
| <artifactId>certkeystore</artifactId> | |
| <packaging>jar</packaging> | |
| <version>1.0-SNAPSHOT</version> | |
| <name>certkeystore</name> | |
| <url>http://maven.apache.org</url> | |
| <properties> | |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
| <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |
| <maven.compiler.source>11</maven.compiler.source> | |
| <maven.compiler.target>11</maven.compiler.target> | |
| </properties> | |
| <dependencies> | |
| <dependency> | |
| <groupId>org.bouncycastle</groupId> | |
| <artifactId>bcpkix-jdk15on</artifactId> | |
| <version>1.66</version> | |
| </dependency> | |
| <dependency> | |
| <groupId>junit</groupId> | |
| <artifactId>junit</artifactId> | |
| <version>4.13</version> | |
| <scope>test</scope> | |
| </dependency> | |
| </dependencies> | |
| </project> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment