Add X509Certificate generator for samples
Issue gh-1558
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id "org.springframework.boot" version "3.2.2"
|
||||
id "io.spring.dependency-management" version "1.1.0"
|
||||
id "java"
|
||||
}
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:1.77"
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:1.77"
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 sample;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
*/
|
||||
final class BouncyCastleUtils {
|
||||
private static final String SHA256_RSA_SIGNATURE_ALGORITHM = "SHA256withRSA";
|
||||
private static final Date DEFAULT_START_DATE;
|
||||
private static final Date DEFAULT_END_DATE;
|
||||
static final String BC_PROVIDER = "BC";
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
// Setup default certificate start date to yesterday and end date for 1 year validity
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, -1);
|
||||
DEFAULT_START_DATE = calendar.getTime();
|
||||
calendar.add(Calendar.YEAR, 1);
|
||||
DEFAULT_END_DATE = calendar.getTime();
|
||||
}
|
||||
|
||||
private BouncyCastleUtils() {
|
||||
}
|
||||
|
||||
static KeyPair generateRSAKeyPair() {
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BC_PROVIDER);
|
||||
keyPairGenerator.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4));
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
static X509Certificate createTrustAnchorCertificate(KeyPair keyPair, String distinguishedName) throws Exception {
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
subject,
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
keyPair.getPublic());
|
||||
|
||||
// Add Extensions
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
// A BasicConstraints to mark root certificate as CA certificate
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(true))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(keyPair.getPrivate());
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
return converter.getCertificate(certBuilder.build(signer));
|
||||
}
|
||||
|
||||
static X509Certificate createCACertificate(X509Certificate signerCert, PrivateKey signerKey,
|
||||
PublicKey certKey, String distinguishedName) throws Exception {
|
||||
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
signerCert.getSubjectX500Principal(),
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
certKey);
|
||||
|
||||
// Add Extensions
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
// A BasicConstraints to mark as CA certificate and how many CA certificates can follow it in the chain
|
||||
// (with 0 meaning the chain ends with the next certificate in the chain).
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(0))
|
||||
// KeyUsage specifies what the public key in the certificate can be used for.
|
||||
// In this case, it can be used for signing other certificates and/or
|
||||
// signing Certificate Revocation Lists (CRLs).
|
||||
.addExtension(Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(signerKey);
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
return converter.getCertificate(certBuilder.build(signer));
|
||||
}
|
||||
|
||||
static X509Certificate createEndEntityCertificate(X509Certificate signerCert, PrivateKey signerKey,
|
||||
PublicKey certKey, String distinguishedName) throws Exception {
|
||||
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
signerCert.getSubjectX500Principal(),
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
certKey);
|
||||
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(false))
|
||||
.addExtension(Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.digitalSignature))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(signerKey);
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
return converter.getCertificate(certBuilder.build(signer));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 sample;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import static sample.BouncyCastleUtils.BC_PROVIDER;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class X509CertificateGeneratorApplication implements CommandLineRunner {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(X509CertificateGeneratorApplication.class, args);
|
||||
}
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
String baseDistinguishedName = "OU=Spring Samples, O=Spring, C=US";
|
||||
|
||||
// Generate the Root certificate (Trust Anchor or most-trusted CA) and keystore file
|
||||
String commonName = "spring-samples-trusted-ca";
|
||||
String rootCommonName = commonName;
|
||||
String distinguishedName = "CN=" + commonName + ", " + baseDistinguishedName;
|
||||
KeyPair rootKeyPair = BouncyCastleUtils.generateRSAKeyPair();
|
||||
X509Certificate rootCertificate = BouncyCastleUtils.createTrustAnchorCertificate(rootKeyPair, distinguishedName);
|
||||
writeCertificatePEMEncoded(rootCertificate, "./samples/x509-certificate-generator/generated/" + commonName + ".pem");
|
||||
createKeystoreFile(rootKeyPair, new Certificate[] {rootCertificate}, commonName,
|
||||
null, "./samples/x509-certificate-generator/generated/" + commonName + "-keystore.p12");
|
||||
TrustedCertificateHolder[] rootTrustedCertificate = { new TrustedCertificateHolder(rootCertificate, rootCommonName) };
|
||||
|
||||
// Generate the CA (intermediary) certificate and keystore file
|
||||
commonName = "spring-samples-ca";
|
||||
String caCommonName = commonName;
|
||||
distinguishedName = "CN=" + commonName + ", " + baseDistinguishedName;
|
||||
KeyPair caKeyPair = BouncyCastleUtils.generateRSAKeyPair();
|
||||
X509Certificate caCertificate = BouncyCastleUtils.createCACertificate(
|
||||
rootCertificate, rootKeyPair.getPrivate(), caKeyPair.getPublic(), distinguishedName);
|
||||
writeCertificatePEMEncoded(caCertificate, "./samples/x509-certificate-generator/generated/" + commonName + ".pem");
|
||||
createKeystoreFile(caKeyPair, new Certificate[] {caCertificate, rootCertificate}, commonName,
|
||||
rootTrustedCertificate, "./samples/x509-certificate-generator/generated/" + commonName + "-keystore.p12");
|
||||
TrustedCertificateHolder[] caTrustedCertificate = { new TrustedCertificateHolder(caCertificate, caCommonName) };
|
||||
|
||||
// Generate the certificate and keystore file for the demo-client sample
|
||||
commonName = "demo-client-sample";
|
||||
distinguishedName = "CN=" + commonName + ", " + baseDistinguishedName;
|
||||
KeyPair demoClientKeyPair = BouncyCastleUtils.generateRSAKeyPair();
|
||||
X509Certificate demoClientCertificate = BouncyCastleUtils.createEndEntityCertificate(
|
||||
caCertificate, caKeyPair.getPrivate(), demoClientKeyPair.getPublic(), distinguishedName);
|
||||
demoClientCertificate.verify(caCertificate.getPublicKey(), BC_PROVIDER);
|
||||
createKeystoreFile(demoClientKeyPair, new Certificate[] {demoClientCertificate, caCertificate, rootCertificate}, commonName,
|
||||
caTrustedCertificate, "./samples/demo-client/src/main/resources/keystore.p12");
|
||||
|
||||
// Generate the certificate and keystore file for the messages-resource sample
|
||||
commonName = "messages-resource-sample";
|
||||
distinguishedName = "CN=" + commonName + ", " + baseDistinguishedName;
|
||||
KeyPair messagesResourceKeyPair = BouncyCastleUtils.generateRSAKeyPair();
|
||||
X509Certificate messagesResourceCertificate = BouncyCastleUtils.createEndEntityCertificate(
|
||||
caCertificate, caKeyPair.getPrivate(), messagesResourceKeyPair.getPublic(), distinguishedName);
|
||||
messagesResourceCertificate.verify(caCertificate.getPublicKey(), BC_PROVIDER);
|
||||
createKeystoreFile(messagesResourceKeyPair, new Certificate[] {messagesResourceCertificate, caCertificate, rootCertificate}, commonName,
|
||||
caTrustedCertificate, "./samples/messages-resource/src/main/resources/keystore.p12");
|
||||
|
||||
// Generate the certificate and keystore file for the demo-authorizationserver sample
|
||||
commonName = "demo-authorizationserver-sample";
|
||||
distinguishedName = "CN=" + commonName + ", " + baseDistinguishedName;
|
||||
KeyPair demoAuthorizationServerKeyPair = BouncyCastleUtils.generateRSAKeyPair();
|
||||
X509Certificate demoAuthorizationServerCertificate = BouncyCastleUtils.createEndEntityCertificate(
|
||||
caCertificate, caKeyPair.getPrivate(), demoAuthorizationServerKeyPair.getPublic(), distinguishedName);
|
||||
demoAuthorizationServerCertificate.verify(caCertificate.getPublicKey(), BC_PROVIDER);
|
||||
createKeystoreFile(demoAuthorizationServerKeyPair, new Certificate[] {demoAuthorizationServerCertificate, caCertificate, rootCertificate}, commonName,
|
||||
caTrustedCertificate, "./samples/demo-authorizationserver/src/main/resources/keystore.p12");
|
||||
}
|
||||
|
||||
private static void createKeystoreFile(KeyPair keyPair, Certificate[] certificateChain, String alias,
|
||||
TrustedCertificateHolder[] trustedCertificates, String fileName) throws Exception {
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12", BC_PROVIDER);
|
||||
keyStore.load(null, null);
|
||||
keyStore.setKeyEntry(alias, keyPair.getPrivate(), "password".toCharArray(), certificateChain);
|
||||
if (trustedCertificates != null && trustedCertificates.length > 0) {
|
||||
for (TrustedCertificateHolder trustedCertificate : trustedCertificates) {
|
||||
keyStore.setCertificateEntry(trustedCertificate.alias, trustedCertificate.certificate);
|
||||
}
|
||||
}
|
||||
Path path = Paths.get(fileName);
|
||||
Path parent = path.getParent();
|
||||
if (parent != null && Files.notExists(parent)) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(fileName);
|
||||
keyStore.store(fos, "password".toCharArray());
|
||||
}
|
||||
|
||||
private static void writeCertificatePEMEncoded(Certificate certificate, String fileName) throws Exception {
|
||||
StringWriter sw = new StringWriter();
|
||||
try (JcaPEMWriter jpw = new JcaPEMWriter(sw)) {
|
||||
jpw.writeObject(certificate);
|
||||
}
|
||||
String pem = sw.toString();
|
||||
Path path = Paths.get(fileName);
|
||||
Path parent = path.getParent();
|
||||
if (parent != null && Files.notExists(parent)) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
Files.write(path, pem.getBytes());
|
||||
}
|
||||
|
||||
private record TrustedCertificateHolder(Certificate certificate, String alias) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
main:
|
||||
web-application-type: none
|
||||
Reference in New Issue
Block a user