summaryrefslogtreecommitdiffstats
path: root/vespa-athenz
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2018-03-19 14:55:17 +0100
committerBjørn Christian Seime <bjorncs@oath.com>2018-03-19 16:03:30 +0100
commite4047a959051838b358909290b1540ca5f533ea1 (patch)
tree837eb592ec8f6abaea77ad3f70dde074ea5546c9 /vespa-athenz
parent518d8a9a8bac25f96228dac74a2d74e65cc7e460 (diff)
Add builders and helpers for x509 certificate and csr
Diffstat (limited to 'vespa-athenz')
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BouncyCastleProviderHolder.java14
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyAlgorithm.java19
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java23
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java30
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java77
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtils.java38
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SignatureAlgorithm.java19
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java154
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java55
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilderTest.java27
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java24
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java59
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java36
13 files changed, 575 insertions, 0 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BouncyCastleProviderHolder.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BouncyCastleProviderHolder.java
new file mode 100644
index 00000000000..03049961dc0
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BouncyCastleProviderHolder.java
@@ -0,0 +1,14 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * @author bjorncs
+ */
+class BouncyCastleProviderHolder {
+
+ private static final BouncyCastleProvider bcProvider = new BouncyCastleProvider();
+
+ static BouncyCastleProvider getInstance() { return bcProvider; }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyAlgorithm.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyAlgorithm.java
new file mode 100644
index 00000000000..4c4198adaac
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyAlgorithm.java
@@ -0,0 +1,19 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+/**
+ * @author bjorncs
+ */
+public enum KeyAlgorithm {
+ RSA("RSA");
+
+ private final String algorithmName;
+
+ KeyAlgorithm(String algorithmName) {
+ this.algorithmName = algorithmName;
+ }
+
+ String getAlgorithmName() {
+ return algorithmName;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java
new file mode 100644
index 00000000000..9182544675c
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java
@@ -0,0 +1,23 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+/**
+ * @author bjorncs
+ */
+public class KeyUtils {
+ private KeyUtils() {}
+
+ public static KeyPair generateKeypair(KeyAlgorithm algorithm, int keySize) {
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm.getAlgorithmName());
+ keyGen.initialize(keySize);
+ return keyGen.genKeyPair();
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java
new file mode 100644
index 00000000000..7a43b5a1fd5
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java
@@ -0,0 +1,30 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+
+/**
+ * @author bjorncs
+ */
+public class Pkcs10Csr {
+
+ private final PKCS10CertificationRequest csr;
+
+ Pkcs10Csr(PKCS10CertificationRequest csr) {
+ this.csr = csr;
+ }
+
+ PKCS10CertificationRequest getBcCsr() {
+ return csr;
+ }
+
+ public X500Principal getSubject() {
+ return new X500Principal(csr.getSubject().toString());
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java
new file mode 100644
index 00000000000..877f05f998a
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java
@@ -0,0 +1,77 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+public class Pkcs10CsrBuilder {
+
+ private final X500Principal subject;
+ private final KeyPair keyPair;
+ private final List<String> subjectAlternativeNames = new ArrayList<>();
+ private final SignatureAlgorithm signatureAlgorithm;
+
+ private Pkcs10CsrBuilder(X500Principal subject,
+ KeyPair keyPair,
+ SignatureAlgorithm signatureAlgorithm) {
+ this.subject = subject;
+ this.keyPair = keyPair;
+ this.signatureAlgorithm = signatureAlgorithm;
+ }
+
+ public static Pkcs10CsrBuilder fromKeypair(X500Principal subject,
+ KeyPair keyPair,
+ SignatureAlgorithm signatureAlgorithm) {
+ return new Pkcs10CsrBuilder(subject, keyPair, signatureAlgorithm);
+ }
+
+ public Pkcs10CsrBuilder addSubjectAlternativeName(String san) {
+ this.subjectAlternativeNames.add(san);
+ return this;
+ }
+
+ public Pkcs10Csr build() {
+ try {
+ PKCS10CertificationRequestBuilder requestBuilder =
+ new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic());
+
+ if (!subjectAlternativeNames.isEmpty()) {
+ GeneralNames generalNames = new GeneralNames(
+ subjectAlternativeNames.stream()
+ .map(san -> new GeneralName(GeneralName.dNSName, san))
+ .toArray(GeneralName[]::new));
+ ExtensionsGenerator extGen = new ExtensionsGenerator();
+ extGen.addExtension(Extension.subjectAlternativeName, false, generalNames);
+ requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
+ }
+ ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm.getAlgorithmName())
+ .setProvider(BouncyCastleProviderHolder.getInstance())
+ .build(keyPair.getPrivate());
+ return new Pkcs10Csr(requestBuilder.build(contentSigner));
+ } catch (OperatorCreationException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ }
+
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtils.java
new file mode 100644
index 00000000000..2289c9ac0ee
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtils.java
@@ -0,0 +1,38 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.util.io.pem.PemObject;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+
+/**
+ * @author bjorncs
+ */
+public class Pkcs10CsrUtils {
+
+ private Pkcs10CsrUtils() {}
+
+ public static Pkcs10Csr fromPem(String pem) {
+ try (PEMParser pemParser = new PEMParser(new StringReader(pem))) {
+ return new Pkcs10Csr((PKCS10CertificationRequest) pemParser.readObject());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static String toPem(Pkcs10Csr csr) {
+ try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
+ pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getBcCsr().getEncoded()));
+ pemWriter.flush();
+ return stringWriter.toString();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SignatureAlgorithm.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SignatureAlgorithm.java
new file mode 100644
index 00000000000..3d85a12f714
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SignatureAlgorithm.java
@@ -0,0 +1,19 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+/**
+ * @author bjorncs
+ */
+public enum SignatureAlgorithm {
+ SHA256_WITH_RSA("SHA256withRSA");
+
+ private final String algorithmName;
+
+ SignatureAlgorithm(String algorithmName) {
+ this.algorithmName = algorithmName;
+ }
+
+ public String getAlgorithmName() {
+ return algorithmName;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java
new file mode 100644
index 00000000000..e60592db2f5
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java
@@ -0,0 +1,154 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.sql.Date;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+public class X509CertificateBuilder {
+
+ private final long serialNumber;
+ private final SignatureAlgorithm signingAlgorithm;
+ private final PrivateKey caPrivateKey;
+ private final Instant notBefore;
+ private final Instant notAfter;
+ private final List<String> subjectAlternativeNames = new ArrayList<>();
+ private final X500Principal issuer;
+ private final X500Principal subject;
+ private final PublicKey certPublicKey;
+ private BasicConstraintsExtension basicConstraintsExtension;
+
+ private X509CertificateBuilder(X500Principal issuer,
+ X500Principal subject,
+ Instant notBefore,
+ Instant notAfter,
+ PublicKey certPublicKey,
+ PrivateKey caPrivateKey,
+ SignatureAlgorithm signingAlgorithm,
+ long serialNumber) {
+ this.issuer = issuer;
+ this.subject = subject;
+ this.notBefore = notBefore;
+ this.notAfter = notAfter;
+ this.certPublicKey = certPublicKey;
+ this.caPrivateKey = caPrivateKey;
+ this.signingAlgorithm = signingAlgorithm;
+ this.serialNumber = serialNumber;
+ }
+
+ public static X509CertificateBuilder fromCsr(Pkcs10Csr csr,
+ X500Principal caIssuer,
+ Instant notBefore,
+ Instant notAfter,
+ PrivateKey caPrivateKey,
+ SignatureAlgorithm signingAlgorithm,
+ long serialNumber) {
+ try {
+ PKCS10CertificationRequest bcCsr = csr.getBcCsr();
+ PublicKey publicKey = new JcaPKCS10CertificationRequest(bcCsr).getPublicKey();
+ return new X509CertificateBuilder(caIssuer,
+ new X500Principal(bcCsr.getSubject().getEncoded()),
+ notBefore,
+ notAfter,
+ publicKey,
+ caPrivateKey,
+ signingAlgorithm,
+ serialNumber);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static X509CertificateBuilder fromKeypair(KeyPair keyPair,
+ X500Principal subject,
+ Instant notBefore,
+ Instant notAfter,
+ SignatureAlgorithm signingAlgorithm,
+ long serialNumber) {
+ return new X509CertificateBuilder(subject,
+ subject,
+ notBefore,
+ notAfter,
+ keyPair.getPublic(),
+ keyPair.getPrivate(),
+ signingAlgorithm,
+ serialNumber);
+ }
+
+ public X509CertificateBuilder addSubjectAlternativeName(String san) {
+ this.subjectAlternativeNames.add(san);
+ return this;
+ }
+
+ public X509CertificateBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) {
+ this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate);
+ return this;
+ }
+
+ public X509Certificate build() {
+ try {
+ JcaX509v3CertificateBuilder jcaCertBuilder = new JcaX509v3CertificateBuilder(
+ issuer, BigInteger.valueOf(serialNumber), Date.from(notBefore), Date.from(notAfter), subject, certPublicKey);
+ if (basicConstraintsExtension != null) {
+ jcaCertBuilder.addExtension(
+ Extension.basicConstraints,
+ basicConstraintsExtension.isCritical,
+ new BasicConstraints(basicConstraintsExtension.isCertAuthorityCertificate));
+ }
+ if (!subjectAlternativeNames.isEmpty()) {
+ GeneralNames generalNames = new GeneralNames(
+ subjectAlternativeNames.stream()
+ .map(san -> new GeneralName(GeneralName.dNSName, san))
+ .toArray(GeneralName[]::new));
+ jcaCertBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames);
+ }
+ ContentSigner contentSigner = new JcaContentSignerBuilder(signingAlgorithm.getAlgorithmName())
+ .setProvider(BouncyCastleProviderHolder.getInstance())
+ .build(caPrivateKey);
+ return new JcaX509CertificateConverter()
+ .setProvider(BouncyCastleProviderHolder.getInstance())
+ .getCertificate(jcaCertBuilder.build(contentSigner));
+ } catch (OperatorException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static class BasicConstraintsExtension {
+ final boolean isCritical, isCertAuthorityCertificate;
+
+ BasicConstraintsExtension(boolean isCritical, boolean isCertAuthorityCertificate) {
+ this.isCritical = isCritical;
+ this.isCertAuthorityCertificate = isCertAuthorityCertificate;
+ }
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java
new file mode 100644
index 00000000000..072c6d41fda
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java
@@ -0,0 +1,55 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.tls;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.util.io.pem.PemObject;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author bjorncs
+ */
+public class X509CertificateUtils {
+
+ private X509CertificateUtils() {}
+
+ public static X509Certificate fromPem(String pem) {
+ try (PEMParser parser = new PEMParser(new StringReader(pem))) {
+ Object pemObject = parser.readObject();
+ if (pemObject instanceof X509Certificate) {
+ return (X509Certificate) pemObject;
+ }
+ if (pemObject instanceof X509CertificateHolder) {
+ return new JcaX509CertificateConverter()
+ .setProvider(BouncyCastleProviderHolder.getInstance())
+ .getCertificate((X509CertificateHolder) pemObject);
+ }
+ throw new IllegalArgumentException("Invalid type of PEM object: " + pemObject);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } catch (CertificateException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String toPem(X509Certificate certificate) {
+ try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
+ pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded()));
+ pemWriter.flush();
+ return stringWriter.toString();
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilderTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilderTest.java
new file mode 100644
index 00000000000..e3aaba66efe
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilderTest.java
@@ -0,0 +1,27 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+
+import java.security.KeyPair;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author bjorncs
+ */
+public class Pkcs10CsrBuilderTest {
+
+ @Test
+ public void can_build_csr_with_sans() {
+ X500Principal subject = new X500Principal("CN=subject");
+ KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA)
+ .addSubjectAlternativeName("san1.com")
+ .addSubjectAlternativeName("san2.com")
+ .build();
+ assertEquals(subject, csr.getSubject());
+ }
+
+} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java
new file mode 100644
index 00000000000..1927e18eba0
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java
@@ -0,0 +1,24 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyPair;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author bjorncs
+ */
+public class Pkcs10CsrUtilsTest {
+
+ @Test
+ public void can_deserialize_serialized_pem_csr() {
+ X500Principal subject = new X500Principal("CN=subject");
+ KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA).build();
+ Pkcs10Csr deserializedCsr = Pkcs10CsrUtils.fromPem(Pkcs10CsrUtils.toPem(csr));
+ assertEquals(subject, deserializedCsr.getSubject());
+ }
+
+} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java
new file mode 100644
index 00000000000..4a6340ab0d5
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java
@@ -0,0 +1,59 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import static com.yahoo.vespa.athenz.tls.TestUtils.createKeyPair;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bjorncs
+ */
+public class X509CertificateBuilderTest {
+
+ @Test
+ public void can_build_self_signed_certificate() throws NoSuchAlgorithmException {
+ KeyPair keyPair = createKeyPair();
+ X500Principal subject = new X500Principal("CN=myservice");
+ X509Certificate cert =
+ X509CertificateBuilder.fromKeypair(
+ keyPair,
+ subject,
+ Instant.now(),
+ Instant.now().plus(1, ChronoUnit.DAYS),
+ SignatureAlgorithm.SHA256_WITH_RSA,
+ 1)
+ .setBasicConstraints(true, true)
+ .build();
+ assertEquals(subject, cert.getSubjectX500Principal());
+ }
+
+ @Test
+ public void can_build_certificate_from_csr() {
+ X500Principal subject = new X500Principal("CN=subject");
+ X500Principal issuer = new X500Principal("CN=issuer");
+ KeyPair csrKeypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, csrKeypair, SignatureAlgorithm.SHA256_WITH_RSA).build();
+ KeyPair caKeypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ X509Certificate cert = X509CertificateBuilder
+ .fromCsr(
+ csr,
+ issuer,
+ Instant.now(),
+ Instant.now().plus(1, ChronoUnit.DAYS),
+ caKeypair.getPrivate(),
+ SignatureAlgorithm.SHA256_WITH_RSA,
+ 1)
+ .addSubjectAlternativeName("subject1.alt")
+ .addSubjectAlternativeName("subject2.alt")
+ .build();
+ assertEquals(subject, cert.getSubjectX500Principal());
+ }
+
+} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java
new file mode 100644
index 00000000000..3ba011b1efb
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java
@@ -0,0 +1,36 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bjorncs
+ */
+public class X509CertificateUtilsTest {
+ @Test
+ public void can_deserialize_serialized_pem_certificate() {
+ KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ X500Principal subject = new X500Principal("CN=myservice");
+ X509Certificate cert = X509CertificateBuilder
+ .fromKeypair(
+ keypair,
+ subject,
+ Instant.now(),
+ Instant.now().plus(1, ChronoUnit.DAYS),
+ SignatureAlgorithm.SHA256_WITH_RSA,
+ 1)
+ .build();
+ assertEquals(subject, cert.getSubjectX500Principal());
+ X509Certificate deserializedCert = X509CertificateUtils.fromPem(X509CertificateUtils.toPem(cert));
+ assertEquals(subject, deserializedCert.getSubjectX500Principal());
+ }
+
+
+} \ No newline at end of file