cert-signer.git

commit a358da2ed2728470885321325c03460507c202d6

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);
+    }
+}