Author: Paolo Lulli <paolo@lulli.net>
Certs generation OK
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab241fa93af1101365158e2bc65fc7e6fe4d2dba --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<project> + <modelVersion>4.0.0</modelVersion> + <groupId>net.lulli</groupId> + <artifactId>cert-signer</artifactId> + <version>0.0.1</version> + <repositories> + <repository> + <id>code.lulli.net</id> + <url>https://code.lulli.net/maven</url> + </repository> + </repositories> + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>17</source> + <target>17</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.3.0</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <minimizeJar>false</minimizeJar> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <manifestEntries> + <Main-Class>net.lulli.nojs.server.ServerRunner</Main-Class> + <Build-Number>1.0</Build-Number> + </manifestEntries> + </transformer> + </transformers> + <filters> + <!-- avoid issue shading BouncyCastle signed jars --> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + <extensions> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh</artifactId> + <version>3.5.3</version> + </extension> + </extensions> + </build> + <distributionManagement> + <repository> + <id>${repository.id}</id> + <name>${repository.name}</name> + <url>${repository.url}</url> + </repository> + <site> + <id>${repository.id}</id> + <name>${repository.name}</name> + <url>${repository.site.url}</url> + </site> + </distributionManagement> + <!-- ****************************************** --> + <dependencies> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15to18</artifactId> + <version>1.77</version> + </dependency> + <dependency> + <groupId>org.json</groupId> + <artifactId>json</artifactId> + <version>20231013</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.2</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/script/createCertificate b/script/createCertificate new file mode 100755 index 0000000000000000000000000000000000000000..da806beb402b8d3abd9f45eee050e7da0b5eb886 --- /dev/null +++ b/script/createCertificate @@ -0,0 +1,6 @@ +#! /bin/bash -x + +cd $(dirname $0) +source ~/.vault-credentials.cfg + +java -cp ../target/cert-signer-0.0.1.jar net.lulli.certsigner.Client "$@" diff --git a/script/initializeCA b/script/initializeCA new file mode 100755 index 0000000000000000000000000000000000000000..b330ed0d95c24ab9449e29514bcc3b7d5378b88c --- /dev/null +++ b/script/initializeCA @@ -0,0 +1,6 @@ +#! /bin/bash -x + +cd $(dirname $0) +source ~/.vault-credentials.cfg + +java -cp ../target/cert-signer-0.0.1.jar net.lulli.certsigner.CaInitializer "$@" diff --git a/src/main/java/net/lulli/certsigner/CaInitializer.java b/src/main/java/net/lulli/certsigner/CaInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..a78d1d64c98f9b5baf07abd49ab85a5340b186c4 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/CaInitializer.java @@ -0,0 +1,19 @@ +package net.lulli.certsigner; + +import net.lulli.certsigner.ca.CaManager; +import net.lulli.certsigner.strategy.vault.VaultCaSetup; + +public class CaInitializer { + public static void main(String[] args) { + if(args.length != 1){ + throw new IllegalStateException("Missing organizationName parameter"); + } + var serviceName = args[0]; + initializeCa(serviceName); + } + + private static void initializeCa(String serviceName) { + CaManager caManager = new CaManager(new VaultCaSetup(serviceName)); + caManager.initialize(); + } +} diff --git a/src/main/java/net/lulli/certsigner/Client.java b/src/main/java/net/lulli/certsigner/Client.java new file mode 100644 index 0000000000000000000000000000000000000000..5b546123eff489dceafca927a855ad2a585f3fa8 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/Client.java @@ -0,0 +1,103 @@ +package net.lulli.certsigner; + +import net.lulli.certsigner.service.CertificateSigningService; +import net.lulli.certsigner.util.CSRManager; +import net.lulli.certsigner.util.Serde; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.util.Base64; +import java.util.Objects; + +public class Client { + private static final String CERTIFICATES_HOME = System.getenv("HOME") + "/.config/tlscerts"; + private final String serviceName; + private final String clientName; + + public Client(String serviceName, String clientName) { + Objects.requireNonNull(serviceName); + Objects.requireNonNull(clientName); + + this.serviceName = serviceName; + this.clientName = clientName; + + init(); + } + + private void init() { + var home = new File(getCertificatesPath()); + if (!home.exists()) { + home.mkdirs(); + } + } + + private String getCertificatesPath() { + return CERTIFICATES_HOME + "/" + this.serviceName + "/" + this.clientName; + } + + public String createCsr() { + try { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keypair = keyGen.genKeyPair(); + + Serde.toFile(keypair.getPrivate(), getCertificatesPath() + "/" + this.clientName + ".key"); + Serde.toFile(keypair.getPublic(), getCertificatesPath() + "/" + this.clientName + ".key.pub"); + + var subject = "CN=" + this.clientName + ", O=" + this.serviceName + ""; + + var csrManager = CSRManager.with(subject, keypair.getPrivate(), keypair.getPublic()); + var csrFileName = getCertificatesPath() + "/" + this.clientName + ".csr"; + saveCsrAs(csrManager.pem(), csrFileName); + return csrManager.pem(); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + private void saveCsrAs(String content, String filename) { + System.out.println("Writing content to file: " + filename); + try { + Files.write(Paths.get(filename), content.getBytes()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + public String requestCertificate(String serviceName, String clientName) { + try { + var client = new Client(serviceName, clientName); + + var csr = client.createCsr(); + var base64Csr = Base64.getEncoder().encodeToString(csr.getBytes()); + var certificateSigningService = new CertificateSigningService(serviceName); + var certificate = certificateSigningService.sign(clientName, base64Csr); + + Files.write(Paths.get(CERTIFICATES_HOME + "/" + serviceName + "/" + clientName + "/" + clientName + ".pem"), + certificate.getBytes()); + return certificate; + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + public static void main(String[] args) { + if (args.length != 2){ + throw new IllegalStateException("Missing parameters"); + } + + var serviceName = args[0]; + var clientName = args[1]; + + var client = new Client(serviceName, clientName); + var certificate = client.requestCertificate(serviceName, clientName); + + System.out.printf("Certificate data:\n%s", certificate); + } +} \ No newline at end of file diff --git a/src/main/java/net/lulli/certsigner/Settings.java b/src/main/java/net/lulli/certsigner/Settings.java new file mode 100644 index 0000000000000000000000000000000000000000..b39eed3673d44b3e579c9642fd33c444b969a350 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/Settings.java @@ -0,0 +1,7 @@ +package net.lulli.certsigner; + +public interface Settings { + static final String BC_PROVIDER = "BC"; + static final String KEY_ALGORITHM = "RSA"; + static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; +} diff --git a/src/main/java/net/lulli/certsigner/ca/CaData.java b/src/main/java/net/lulli/certsigner/ca/CaData.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6f12bd7a695df48e44d1dee34169084699a47f --- /dev/null +++ b/src/main/java/net/lulli/certsigner/ca/CaData.java @@ -0,0 +1,50 @@ +package net.lulli.certsigner.ca; + +import net.lulli.certsigner.util.PemUtil; +import net.lulli.certsigner.Settings; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.util.Objects; + +public class CaData { + private final String rootSubject; + private String pemCertificate; + private String pemPrivateKey; + + public static CaData withSubject(String rootSubject) { + return new CaData(rootSubject); + } + + private CaData(String rootSubject) { + Objects.requireNonNull(rootSubject); + + this.rootSubject = rootSubject; + initializeCa(); + } + + private void initializeCa() { + try { + Security.addProvider(new BouncyCastleProvider()); + + var keyPairGenerator = KeyPairGenerator.getInstance(Settings.KEY_ALGORITHM, Settings.BC_PROVIDER); + keyPairGenerator.initialize(2048); + + var rootKeyPair = keyPairGenerator.generateKeyPair(); + var rootCert = CertificateIssue.rootCertificate(rootKeyPair.getPublic(), rootKeyPair.getPrivate(), rootSubject); + + this.pemCertificate = PemUtil.toString(rootCert); + this.pemPrivateKey = PemUtil.toString(rootKeyPair.getPrivate()); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public String certificate() { + return pemCertificate; + } + + public String privateKey() { + return pemPrivateKey; + } +} \ No newline at end of file diff --git a/src/main/java/net/lulli/certsigner/ca/CaManager.java b/src/main/java/net/lulli/certsigner/ca/CaManager.java new file mode 100644 index 0000000000000000000000000000000000000000..be14c1fdb5fc5d7f581582c96eca3bba8782d87d --- /dev/null +++ b/src/main/java/net/lulli/certsigner/ca/CaManager.java @@ -0,0 +1,18 @@ +package net.lulli.certsigner.ca; + +import net.lulli.certsigner.strategy.CASetupStrategy; + +import java.util.Objects; + +public class CaManager { + private final CASetupStrategy caSetup; + + public CaManager(CASetupStrategy caSetup) { + Objects.requireNonNull(caSetup); + this.caSetup = caSetup; + } + + public void initialize() { + caSetup.initialize(); + } +} diff --git a/src/main/java/net/lulli/certsigner/ca/CaServer.java b/src/main/java/net/lulli/certsigner/ca/CaServer.java new file mode 100644 index 0000000000000000000000000000000000000000..17149fdd49f76b61b920e01df47b58879bee194b --- /dev/null +++ b/src/main/java/net/lulli/certsigner/ca/CaServer.java @@ -0,0 +1,69 @@ +package net.lulli.certsigner.ca; + +import net.lulli.certsigner.util.FileCAUtils; +import net.lulli.certsigner.util.Serde; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; + +public class CaServer { + private static final String CA_PATH = System.getenv("HOME") + "/.config/tlscerts/CA"; + private static final String CERTIFICATES_PATH = System.getenv("HOME") + "/.config/tlscerts"; + private final String serviceName; + + public CaServer(String serviceName) { + Objects.requireNonNull(serviceName); + + this.serviceName = serviceName; + + init(); + } + + private String getServiceCaDir() { + return CA_PATH + "/" + this.serviceName; + } + + private String getClientCertsDir(String clientName) { + return CERTIFICATES_PATH + "/" + this.serviceName + "/" + clientName; + } + + private String getCSRPath(String clientName) { + return CERTIFICATES_PATH + "/" + this.serviceName + "/" + clientName + "/" + clientName + ".csr"; + } + + private void init() { + var home = new File(getServiceCaDir()); + if (!home.exists()) { + home.mkdirs(); + } + } + + public String getCsr(String clientName) { + try { + var caServer = new CaServer(this.serviceName); + return Files.readString(Paths.get(getCSRPath(clientName))); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public void initializeCa(String rootSubject, String rootKeyStorePass) { + FileCAUtils.initializeCa(rootSubject, getServiceCaDir(), rootKeyStorePass); + } + + public String createCert(String rootKeyStorePass, String rootSubject, String clientName) { + + var csrContent = getCsr(clientName); + var pkcs10CertificationRequest = Serde.pemToCsr(csrContent); + + return FileCAUtils.signCertificateWithRootKeystore(getServiceCaDir() + "/root-cert.p12", + rootKeyStorePass, + pkcs10CertificationRequest, + rootSubject, + getClientCertsDir(clientName), + clientName + ); + } +} diff --git a/src/main/java/net/lulli/certsigner/ca/CertificateData.java b/src/main/java/net/lulli/certsigner/ca/CertificateData.java new file mode 100644 index 0000000000000000000000000000000000000000..7e9827554a5f5b82b2739d66f64fa5f1baed1d94 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/ca/CertificateData.java @@ -0,0 +1,60 @@ +package net.lulli.certsigner.ca; + +import net.lulli.certsigner.util.Serde; +import net.lulli.certsigner.util.PemUtil; +import net.lulli.certsigner.Settings; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.Objects; + +public class CertificateData { + + private final String certificateSubject; + private String pemCertificate; + + private CertificateData(String certificateSubject) { + Objects.requireNonNull(certificateSubject); + + this.certificateSubject = certificateSubject; + } + + public static CertificateData withSubject(String rootSubject) { + return new CertificateData(rootSubject); + } + + public String certificate() { + return pemCertificate; + } + + + public void sign( + String pemPrivateKey, + String pemRootCertificate, + PKCS10CertificationRequest csr, + String rootSubject + ) { + try { + Security.addProvider(new BouncyCastleProvider()); + PrivateKey caPrivateKey = Serde.readPKCS8PrivateKey(pemPrivateKey); + X509Certificate rootCert = Serde.readX509Certificate(pemRootCertificate); + PublicKey caPublicKey = rootCert.getPublicKey(); + + X509Certificate issuedCert = CertificateIssue.clientCertificate( + caPrivateKey, + rootSubject, + rootCert, + csr); + + issuedCert.verify(caPublicKey, Settings.BC_PROVIDER); + this.pemCertificate = PemUtil.toString(issuedCert); + + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/main/java/net/lulli/certsigner/ca/CertificateIssue.java b/src/main/java/net/lulli/certsigner/ca/CertificateIssue.java new file mode 100644 index 0000000000000000000000000000000000000000..87af9aba93f3f626b46099e6dc23632c7de45ee6 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/ca/CertificateIssue.java @@ -0,0 +1,88 @@ +package net.lulli.certsigner.ca; + +import net.lulli.certsigner.Settings; +import net.lulli.certsigner.util.ValidityUtil; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +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.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +public class CertificateIssue { + private CertificateIssue(){} + public static X509Certificate rootCertificate( + PublicKey publicKey, + PrivateKey privateKey, + String subject) + throws Exception { + BigInteger rootSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); + + X500Name rootCertIssuer = new X500Name(subject); + X500Name rootCertSubject = rootCertIssuer; + ContentSigner rootCertContentSigner = new JcaContentSignerBuilder(Settings.SIGNATURE_ALGORITHM).setProvider(Settings.BC_PROVIDER).build(privateKey); + X509v3CertificateBuilder rootCertBuilder = + new JcaX509v3CertificateBuilder(rootCertIssuer, rootSerialNum, ValidityUtil.yesterday(), ValidityUtil.plusYears(1), rootCertSubject, publicKey); + + JcaX509ExtensionUtils rootCertExtUtils = new JcaX509ExtensionUtils(); + rootCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); + rootCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, rootCertExtUtils.createSubjectKeyIdentifier(publicKey)); + + X509CertificateHolder rootCertHolder = rootCertBuilder.build(rootCertContentSigner); + return new JcaX509CertificateConverter().setProvider(Settings.BC_PROVIDER).getCertificate(rootCertHolder); + } + + + + public static X509Certificate clientCertificate( + PrivateKey privateKey, + String certificateSubject, + X509Certificate rootCert, + PKCS10CertificationRequest csr + ) { + try { + + X500Name issuedCertSubject = new X500Name(certificateSubject); + BigInteger issuedCertSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); + + //PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(issuedCertSubject, issuedCertKeyPair.getPublic()); + JcaContentSignerBuilder csrBuilder = new JcaContentSignerBuilder(Settings.SIGNATURE_ALGORITHM).setProvider(Settings.BC_PROVIDER); + + ContentSigner csrContentSigner = csrBuilder.build(privateKey); + + X509v3CertificateBuilder issuedCertBuilder = new X509v3CertificateBuilder(new X500Name("CN=root-cert"), issuedCertSerialNum, ValidityUtil.yesterday(), ValidityUtil.plusYears(1), csr.getSubject(), csr.getSubjectPublicKeyInfo()); + + JcaX509ExtensionUtils issuedCertExtUtils = new JcaX509ExtensionUtils(); + issuedCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + issuedCertBuilder.addExtension(Extension.authorityKeyIdentifier, false, issuedCertExtUtils.createAuthorityKeyIdentifier(rootCert)); + issuedCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, issuedCertExtUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo())); + 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); + return new JcaX509CertificateConverter().setProvider(Settings.BC_PROVIDER).getCertificate(issuedCertHolder); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/main/java/net/lulli/certsigner/network/VaultLocal.java b/src/main/java/net/lulli/certsigner/network/VaultLocal.java new file mode 100644 index 0000000000000000000000000000000000000000..2d1dbb8cee19a5a80957953a511d171460187fa3 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/network/VaultLocal.java @@ -0,0 +1,78 @@ +package net.lulli.certsigner.network; + +import org.json.JSONObject; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class VaultLocal { + private final String endpoint; + private final String token; + + public VaultLocal(String endpoint, String token) { + Objects.requireNonNull(token); + Objects.requireNonNull(endpoint); + + this.endpoint = endpoint; + this.token = token; + } + + public boolean storeSecret(String secretName, Map<String, String> secretMap) { + var url = String.format("%s/%s", endpoint, secretName); + + var containerJson = new JSONObject(); + containerJson.put("options", new ArrayList<String>()); + containerJson.put("version", 0); + containerJson.put("data", secretMap); + + try { + postToVault(url, containerJson.toString(), token); + } catch (Exception ignored) { + return false; + } + return true; + } + + private static JSONObject getWithHeader(String url, String headerName, String headerValue) { + try { + var client = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder(URI.create(url)).header("accept", "application/json") + .header(headerName, headerValue).build(); + + HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + return new JSONObject(response.body()); + } catch (Exception e) { + System.out.println(e.getMessage()); + return null; + } + } + + public Optional<String> retrieveSecret(String secretName) { + var url = String.format("%s/%s", endpoint, secretName); + var json = getWithHeader(url, "X-Vault-Token", token); + + if (null != json) { + return Optional.of(json.toString()); + } + + return Optional.empty(); + } + + public static String postToVault(String url, String payload, String token) + throws Exception { + + var client = HttpClient.newBuilder().build(); + var request = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(payload)) + .header("X-Vault-Token", token) + .uri(URI.create(url)).build(); + + var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + return response.body().toString(); + } +} \ No newline at end of file diff --git a/src/main/java/net/lulli/certsigner/service/CertificateSigningService.java b/src/main/java/net/lulli/certsigner/service/CertificateSigningService.java new file mode 100644 index 0000000000000000000000000000000000000000..238bf9cbe4fe0ca65c2eaae719f73246783b721c --- /dev/null +++ b/src/main/java/net/lulli/certsigner/service/CertificateSigningService.java @@ -0,0 +1,34 @@ +package net.lulli.certsigner.service; + +import net.lulli.certsigner.network.VaultLocal; +import net.lulli.certsigner.strategy.vault.VaultSigningStrategy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class CertificateSigningService { + private static String vaultEndpoint = System.getenv("VAULT_ENDPOINT"); + private static String vaultToken = System.getenv("VAULT_TOKEN"); + public static final String VAULT_CERTIFICATES_PATH = "certificates"; + public static final String VAULT_CSR_PATH = "csr"; + private final String serviceName; + private final VaultLocal vaultLocal; + + public CertificateSigningService(String serviceName) { + Objects.requireNonNull(serviceName); + + this.serviceName = serviceName; + this.vaultLocal = new VaultLocal(vaultEndpoint, vaultToken); + } + + public String sign(String clientName, String base64Csr) { + + Map map = new HashMap<>(); + map.put("csr", base64Csr); + vaultLocal.storeSecret(VAULT_CSR_PATH + "/" + serviceName + "/" + clientName, map); + + var signingStrategy = new VaultSigningStrategy(serviceName); + return signingStrategy.sign(clientName); + } +} diff --git a/src/main/java/net/lulli/certsigner/strategy/CASetupStrategy.java b/src/main/java/net/lulli/certsigner/strategy/CASetupStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..f1554b7aaac3307c7af41949169ca2202c313aaa --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/CASetupStrategy.java @@ -0,0 +1,5 @@ +package net.lulli.certsigner.strategy; + +public interface CASetupStrategy { + public void initialize(); +} diff --git a/src/main/java/net/lulli/certsigner/strategy/SigningStrategy.java b/src/main/java/net/lulli/certsigner/strategy/SigningStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..266728937cc6eb8d69bff5764d2ba53c9304f837 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/SigningStrategy.java @@ -0,0 +1,5 @@ +package net.lulli.certsigner.strategy; + +public interface SigningStrategy { + public String sign(String clientName); +} diff --git a/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCaSetup.java b/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCaSetup.java new file mode 100644 index 0000000000000000000000000000000000000000..c9284a28eb591e571e12a03781861cc5b864a42f --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCaSetup.java @@ -0,0 +1,30 @@ +package net.lulli.certsigner.strategy.file; + +import net.lulli.certsigner.ca.CaServer; +import net.lulli.certsigner.strategy.CASetupStrategy; + +import java.util.Objects; + +public class FileBasedCaSetup implements CASetupStrategy { + private final String organizationName; + private final String rootKeyStorePass; + + public FileBasedCaSetup( + String organizationName, + String rootKeyStorePass + ) { + Objects.requireNonNull(organizationName); + Objects.requireNonNull(rootKeyStorePass); + + this.organizationName = organizationName; + this.rootKeyStorePass = rootKeyStorePass; + } + + @Override + public void initialize() { + var rootSubject = "CN=root-cert, O=" + organizationName; + var caServer = new CaServer(organizationName); + + caServer.initializeCa(rootSubject, rootKeyStorePass); + } +} diff --git a/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCertificateSigning.java b/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCertificateSigning.java new file mode 100644 index 0000000000000000000000000000000000000000..c559f9459c87d33ebe87a53a4367437fee3c6668 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/file/FileBasedCertificateSigning.java @@ -0,0 +1,30 @@ +package net.lulli.certsigner.strategy.file; + +import net.lulli.certsigner.ca.CaServer; +import net.lulli.certsigner.strategy.SigningStrategy; + +import java.util.Objects; + +public class FileBasedCertificateSigning implements SigningStrategy { + private final String organizationName; + private final String rootKeyStorePass; + + public FileBasedCertificateSigning( + String organizationName, + String rootKeyStorePass + ) { + Objects.requireNonNull(organizationName); + Objects.requireNonNull(rootKeyStorePass); + + this.organizationName = organizationName; + this.rootKeyStorePass = rootKeyStorePass; + } + + @Override + public String sign(String clientName) { + var rootSubject = "CN=root-cert, O=" + organizationName; + var caServer = new CaServer(organizationName); + + return caServer.createCert(rootKeyStorePass, rootSubject, clientName); + } +} diff --git a/src/main/java/net/lulli/certsigner/strategy/vault/VaultCaSetup.java b/src/main/java/net/lulli/certsigner/strategy/vault/VaultCaSetup.java new file mode 100644 index 0000000000000000000000000000000000000000..eb25facaf404ff3083d78fb10d4f9c165d552ed1 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/vault/VaultCaSetup.java @@ -0,0 +1,38 @@ +package net.lulli.certsigner.strategy.vault; + +import net.lulli.certsigner.network.VaultLocal; +import net.lulli.certsigner.strategy.CASetupStrategy; +import net.lulli.certsigner.ca.CaData; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Objects; + +public class VaultCaSetup implements CASetupStrategy { + private static String vaultEndpoint = System.getenv("VAULT_ENDPOINT"); + private static String vaultToken = System.getenv("VAULT_TOKEN"); + + private static final String VAULT_PATH = "cert-auth"; + + private final VaultLocal vaultLocal; + private final String serviceName; + + public VaultCaSetup(String serviceName) { + Objects.requireNonNull(serviceName); + + this.serviceName = serviceName; + this.vaultLocal = new VaultLocal(vaultEndpoint, vaultToken); + } + + @Override + public void initialize() { + var rootSubject = "CN=root-cert, O=" + serviceName; + var data = new HashMap<String, String>(); + + CaData caData = CaData.withSubject(rootSubject); + data.put("certificate", Base64.getEncoder().encodeToString(caData.certificate().getBytes())); + data.put("privateKey", Base64.getEncoder().encodeToString(caData.privateKey().getBytes())); + + vaultLocal.storeSecret(VAULT_PATH + "/" + serviceName, data); + } +} diff --git a/src/main/java/net/lulli/certsigner/strategy/vault/VaultSigningStrategy.java b/src/main/java/net/lulli/certsigner/strategy/vault/VaultSigningStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..7b2f0f3c5985fae5fef7ad20ee12cf82650e0567 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/strategy/vault/VaultSigningStrategy.java @@ -0,0 +1,75 @@ +package net.lulli.certsigner.strategy.vault; + +import net.lulli.certsigner.network.VaultLocal; +import net.lulli.certsigner.service.CertificateSigningService; +import net.lulli.certsigner.strategy.SigningStrategy; +import net.lulli.certsigner.ca.CertificateData; +import net.lulli.certsigner.util.Serde; +import org.json.JSONObject; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Objects; + +public class VaultSigningStrategy implements SigningStrategy { + private final String serviceName; + private static String vaultEndpoint = System.getenv("VAULT_ENDPOINT"); + private static String vaultToken = System.getenv("VAULT_TOKEN"); + private static final String VAULT_PATH = "cert-auth"; + private final VaultLocal vaultLocal; + + + public VaultSigningStrategy(String serviceName) { + Objects.requireNonNull(serviceName); + this.serviceName = serviceName; + + this.vaultLocal = new VaultLocal(vaultEndpoint, vaultToken); + } + + @Override + public String sign(String clientName) { + var rootSubject = "CN=root-cert, O=" + serviceName; + + return createCert(rootSubject, clientName); + } + + public String createCert(String rootSubject, String clientName) { + var optionalCsr = vaultLocal.retrieveSecret( + CertificateSigningService.VAULT_CSR_PATH + "/" + serviceName + "/" + clientName); + if (!optionalCsr.isPresent()) { + throw new IllegalStateException("Could not find CSR in vault"); + } + + var csrContent = optionalCsr.get(); + var csrPayload = getPayload(csrContent); + var pem = new String(Base64.getDecoder().decode(csrPayload.getString("csr"))); + var pkcs10CertificationRequest = Serde.pemToCsr(pem); + + var certificateData = CertificateData.withSubject(clientName); + + vaultLocal.retrieveSecret(VAULT_PATH + "/" + serviceName) + .ifPresentOrElse(secret -> { + var payload = getPayload(secret); + certificateData.sign( + new String(Base64.getDecoder().decode(payload.getString("privateKey"))), + new String(Base64.getDecoder().decode(payload.getString("certificate"))), + pkcs10CertificationRequest, + rootSubject + ); + }, () -> { + throw new IllegalStateException("Could not find secret in vault"); + }); + + var map = new HashMap<String, String>(); + map.put("certificate", certificateData.certificate()); + vaultLocal.storeSecret(CertificateSigningService.VAULT_CERTIFICATES_PATH + "/" + serviceName + "/" + clientName, map); + return certificateData.certificate(); + } + + private static JSONObject getPayload(String secret) { + var secretData = new JSONObject(secret); + var stage2Secret = (JSONObject) secretData.get("data"); + var payload = (JSONObject) stage2Secret.get("data"); + return payload; + } +} \ No newline at end of file diff --git a/src/main/java/net/lulli/certsigner/util/CSRManager.java b/src/main/java/net/lulli/certsigner/util/CSRManager.java new file mode 100644 index 0000000000000000000000000000000000000000..f8be74ff4137b23781605fc55a42969a04a6eba4 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/CSRManager.java @@ -0,0 +1,63 @@ +package net.lulli.certsigner.util; + +import net.lulli.certsigner.Settings; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +import javax.security.auth.x500.X500Principal; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Objects; + +@Deprecated //Future versions should use org.bouncycastle.pkcs.PKCS10CertificationRequest +public class CSRManager { + private final PKCS10CertificationRequest csr; + private final X500Principal x500Principal; + private final PrivateKey privateKey; + private final PublicKey publicKey; + + private CSRManager(X500Principal x500Principal, PrivateKey privateKey, PublicKey publicKey) throws Exception { + this.x500Principal = x500Principal; + this.privateKey = privateKey; + this.publicKey = publicKey; + + this.csr = new PKCS10CertificationRequest( + Settings.SIGNATURE_ALGORITHM, + //"SHA1withRSA", + x500Principal, + publicKey, + null, + privateKey + ); + } + + + public static CSRManager with(String subject, PrivateKey privateKey, PublicKey publicKey) throws Exception { + Objects.requireNonNull(subject); + Objects.requireNonNull(privateKey); + Objects.requireNonNull(publicKey); + + var principal = new X500Principal(subject); + return new CSRManager(principal, privateKey, publicKey); + } + + public String pem() { + try { + var pemObject = new PemObject("CERTIFICATE REQUEST", csr.getEncoded()); + var str = new StringWriter(); + var pemWriter = new PemWriter(str); + pemWriter.writeObject(pemObject); + pemWriter.close(); + str.close(); + return str.toString(); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public PKCS10CertificationRequest certificateSigningRequest() { + return this.csr; + } +} diff --git a/src/main/java/net/lulli/certsigner/util/FileCAUtils.java b/src/main/java/net/lulli/certsigner/util/FileCAUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4d14386efbc031f32726c0eed22b9a15af6fc94c --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/FileCAUtils.java @@ -0,0 +1,131 @@ +package net.lulli.certsigner.util; + +import net.lulli.certsigner.Settings; +import net.lulli.certsigner.ca.CertificateIssue; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.util.encoders.Base64; + +import java.io.*; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class FileCAUtils { + + public static void initializeCa( + String rootSubject, + String dirName, + String keystorePassword + ) { + try { + Security.addProvider(new BouncyCastleProvider()); + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Settings.KEY_ALGORITHM, Settings.BC_PROVIDER); + keyPairGenerator.initialize(2048); + KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); + + X509Certificate rootCert = CertificateIssue.rootCertificate(rootKeyPair.getPublic(), rootKeyPair.getPrivate(), rootSubject); + + certToPemFile(rootCert, dirName + "/root-cert.pem"); + keyPairToKeystore(rootKeyPair, rootCert, "root-cert", dirName, "PKCS12", keystorePassword); + String pemCertificate = PemUtil.toString(rootCert); + + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static String signCertificateWithRootKeystore( + String rootKeystoreFileName, + String rootKeystorePassword, + PKCS10CertificationRequest csr, + String rootSubject, + String certificateTargetDir, + String commonName + ) { + try { + System.out.println(certificateTargetDir); + Security.addProvider(new BouncyCastleProvider()); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Settings.KEY_ALGORITHM, Settings.BC_PROVIDER); + + KeyStore ks = KeyStore.getInstance("pkcs12"); + System.out.println("rootKeystoreFileName:" + rootKeystoreFileName); + ks.load(new FileInputStream(rootKeystoreFileName), rootKeystorePassword.toCharArray()); + + X509Certificate rootCert = (X509Certificate) ks.getCertificate("root-cert"); + + KeyPair issuedCertKeyPair = keyPairGenerator.generateKeyPair(); + X509Certificate issuedCert = CertificateIssue.clientCertificate( + issuedCertKeyPair.getPrivate(), rootSubject, rootCert, csr); + + issuedCert.verify(issuedCertKeyPair.getPublic(), Settings.BC_PROVIDER); + + certToPemFile(issuedCert, certificateTargetDir + "/" + commonName + ".pem"); + keyPairToKeystore(issuedCertKeyPair, issuedCert, commonName, certificateTargetDir, "PKCS12", "pass"); + + Serde.toFile(issuedCertKeyPair.getPrivate(), certificateTargetDir + "/" + commonName + ".key"); + + return PemUtil.toString(issuedCert); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + + public static void signCertificate( + String rootCaPublicKeyFileName, + String rootCertFileName, + PKCS10CertificationRequest csr, + String rootSubject, + String dirName, + String commonName + ) { + try { + Security.addProvider(new BouncyCastleProvider()); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Settings.KEY_ALGORITHM, Settings.BC_PROVIDER); + + PublicKey caPublicKey = Serde.publicKeyFromFile(rootCaPublicKeyFileName); + X509Certificate rootCert = Serde.certificateFromFile(rootCertFileName); + + KeyPair issuedCertKeyPair = keyPairGenerator.generateKeyPair(); + X509Certificate issuedCert = CertificateIssue.clientCertificate( + issuedCertKeyPair.getPrivate(), + rootSubject, + rootCert, + csr); + + issuedCert.verify(caPublicKey, Settings.BC_PROVIDER); + + //writeCertToFileBase64Encoded(issuedCert, commonName + ".cer"); + //exportKeyPairToKeystoreFile(issuedCertKeyPair, issuedCert, commonName, commonName + ".p12", "PKCS12", "pass"); + Serde.toFile(issuedCertKeyPair.getPrivate(), dirName + "/" + commonName + ".key"); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + + + + + static void keyPairToKeystore(KeyPair keyPair, Certificate certificate, String alias, String dirName, String storeType, String storePass) { + try { + KeyStore sslKeyStore = KeyStore.getInstance(storeType, Settings.BC_PROVIDER); + sslKeyStore.load(null, null); + sslKeyStore.setKeyEntry(alias, keyPair.getPrivate(), null, new java.security.cert.Certificate[]{certificate}); + FileOutputStream keyStoreOs = new FileOutputStream(dirName + "/" + alias + ".p12"); + sslKeyStore.store(keyStoreOs, storePass.toCharArray()); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + static void certToPemFile(Certificate certificate, String fileName) throws Exception { + FileOutputStream certificateOut = new FileOutputStream(fileName); + certificateOut.write("-----BEGIN CERTIFICATE-----\n".getBytes()); + certificateOut.write(Base64.encode(certificate.getEncoded())); + certificateOut.write("-----END CERTIFICATE-----\n".getBytes()); + certificateOut.close(); + } +} diff --git a/src/main/java/net/lulli/certsigner/util/Keys.java b/src/main/java/net/lulli/certsigner/util/Keys.java new file mode 100644 index 0000000000000000000000000000000000000000..9b7215fad319f58ce57c72a7617d1ba2e5a6cea5 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/Keys.java @@ -0,0 +1,43 @@ +package net.lulli.certsigner.util; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class Keys { + private Keys(){} + public static RSAPublicKey readX509PublicKey(File file) throws Exception { + String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); + + String publicKeyPEM = key + .replace("-----BEGIN PUBLIC KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END PUBLIC KEY-----", ""); + + byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + return (RSAPublicKey) keyFactory.generatePublic(keySpec); + } + public RSAPrivateKey readPKCS8PrivateKey(File file) throws Exception { + String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); + + String privateKeyPEM = key + .replace("-----BEGIN PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END PRIVATE KEY-----", ""); + + byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + } +} diff --git a/src/main/java/net/lulli/certsigner/util/PemUtil.java b/src/main/java/net/lulli/certsigner/util/PemUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..8eb459226300288d8e06c34061faa5b7137a53d2 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/PemUtil.java @@ -0,0 +1,54 @@ +package net.lulli.certsigner.util; + +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +public class PemUtil { + private PemUtil(){} + public static String toString(PKCS10CertificationRequest csr) { + try { + var pemObject = new PemObject("CERTIFICATE REQUEST", csr.getEncoded()); + var str = new StringWriter(); + var pemWriter = new PemWriter(str); + pemWriter.writeObject(pemObject); + pemWriter.close(); + str.close(); + return str.toString(); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static String toString(PrivateKey privateKey) { + try { + var pemObject = new PemObject("RSA PRIVATE KEY", privateKey.getEncoded()); + var str = new StringWriter(); + var pemWriter = new PemWriter(str); + pemWriter.writeObject(pemObject); + pemWriter.close(); + str.close(); + return str.toString(); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static String toString(X509Certificate x509Certificate) { + try { + var pemObject = new PemObject("CERTIFICATE", x509Certificate.getEncoded()); + var str = new StringWriter(); + var pemWriter = new PemWriter(str); + pemWriter.writeObject(pemObject); + pemWriter.close(); + str.close(); + return str.toString(); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/main/java/net/lulli/certsigner/util/Serde.java b/src/main/java/net/lulli/certsigner/util/Serde.java new file mode 100644 index 0000000000000000000000000000000000000000..c71c52fcdc413d66b62811a7e99c343906ecec29 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/Serde.java @@ -0,0 +1,155 @@ +package net.lulli.certsigner.util; + +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class Serde { + private Serde() { + } + + public static void toFile(PublicKey key, String fileName) { + try { + var pem = toPem(key); + Files.write(Paths.get(fileName), pem.getBytes()); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static void toFile(PrivateKey key, String fileName) { + try { + var pem = toPem(key); + Files.write(Paths.get(fileName), pem.getBytes()); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static X509Certificate certificateFromFile(String fileName) { + try { + var keyContent = Files.readString(Paths.get(fileName)); + return readX509Certificate(keyContent); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static PublicKey publicKeyFromFile(String fileName) { + try { + var keyContent = Files.readString(Paths.get(fileName)); + return readX509PublicKey(keyContent); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static PrivateKey privateKeyFromFile(String fileName) { + try { + var keyContent = Files.readString(Paths.get(fileName)); + return readPKCS8PrivateKey(keyContent); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage()); + } + } + + public static String toPem(PrivateKey privateKey) { + try { + Writer out = new StringWriter(); + out.write("-----BEGIN RSA PRIVATE KEY-----\n"); + writeBase64(out, privateKey); + out.write("-----END RSA PRIVATE KEY-----\n"); + + return out.toString(); + } catch (Exception e) { + return null; + } + } + + public static String toPem(PublicKey publicKey) { + try { + Writer out = new StringWriter(); + out.write("-----BEGIN RSA PUBLIC KEY-----\n"); + writeBase64(out, publicKey); + out.write("-----END RSA PUBLIC KEY-----\n"); + + return out.toString(); + } catch (Exception e) { + return null; + } + } + + static private void writeBase64(Writer out, Key key) throws java.io.IOException { + byte[] buf = key.getEncoded(); + out.write(Base64.getEncoder().encodeToString(buf)); + out.write("\n"); + } + + + public static X509Certificate readX509Certificate(String certificate) throws Exception { + InputStream targetStream = new ByteArrayInputStream(certificate.getBytes()); + return (X509Certificate) CertificateFactory + .getInstance("X509") + .generateCertificate(targetStream); + } + + public static RSAPublicKey readX509PublicKey(String key) throws Exception { + String publicKeyPEM = key + .replace("-----BEGIN PUBLIC KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END PUBLIC KEY-----", ""); + + byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + return (RSAPublicKey) keyFactory.generatePublic(keySpec); + } + + public static RSAPrivateKey readPKCS8PrivateKey(String key) throws Exception { + String privateKeyPEM = key + .replace("-----BEGIN RSA PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END RSA PRIVATE KEY-----", ""); + + byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + } + + public static PKCS10CertificationRequest pemToCsr(String pem) { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + PKCS10CertificationRequest csr = null; + ByteArrayInputStream pemStream = null; + try { + pemStream = new ByteArrayInputStream(pem.getBytes("UTF-8")); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + + Reader pemReader = new BufferedReader(new InputStreamReader(pemStream)); + PEMParser pemParser = new PEMParser(pemReader); + + try { + Object parsedObj = pemParser.readObject(); + if (parsedObj instanceof PKCS10CertificationRequest) { + csr = (PKCS10CertificationRequest) parsedObj; + } + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + return csr; + } +} diff --git a/src/main/java/net/lulli/certsigner/util/ValidityUtil.java b/src/main/java/net/lulli/certsigner/util/ValidityUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d8adf2e51bec4430d04ad00cecf2fa4bdfa6d443 --- /dev/null +++ b/src/main/java/net/lulli/certsigner/util/ValidityUtil.java @@ -0,0 +1,25 @@ +package net.lulli.certsigner.util; + +import java.util.Calendar; +import java.util.Date; + +public class ValidityUtil { + private ValidityUtil(){} + + public static Date yesterday(){ + var calendar = Calendar.getInstance(); + calendar.getInstance().add(Calendar.DATE, -1); + return calendar.getTime(); + } + + public static Date plusYears(int years){ + var calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, years); + return calendar.getTime(); + } + public static Date plusDays(int days){ + var calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, days); + return calendar.getTime(); + } +} diff --git a/src/test/java/net/lulli/certsigner/FileBasedCaTest.java b/src/test/java/net/lulli/certsigner/FileBasedCaTest.java new file mode 100644 index 0000000000000000000000000000000000000000..62fb947be1adfeddc1cfd1bb99f070d515dedb38 --- /dev/null +++ b/src/test/java/net/lulli/certsigner/FileBasedCaTest.java @@ -0,0 +1,28 @@ +package net.lulli.certsigner; + +import net.lulli.certsigner.ca.CaServer; +import org.junit.Test; + + +public class FileBasedCaTest { + + @Test + public void testCertificates(){ + var organizationName = "randomorganization"; + var rootKeyStorePass = "pass"; + var rootSubject = "CN=root-cert, O=" + organizationName; + var caServer = new CaServer(organizationName); + + //1 + caServer.initializeCa(rootSubject, rootKeyStorePass); + + var clientName = "user@"+organizationName; + + //2 + var client = new Client(organizationName, clientName); + client.createCsr(); + + //3 + caServer.createCert(rootKeyStorePass, rootSubject, clientName); + } +}