summaryrefslogtreecommitdiffstats
path: root/vespa-athenz
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2018-04-17 16:12:35 +0200
committerBjørn Christian Seime <bjorncs@oath.com>2018-04-17 16:25:32 +0200
commitb65667463f7add415a7b1240da7a22ae384b1942 (patch)
treec21744a18496b9f07197bcc8624a419243b2251e /vespa-athenz
parentb9f6244b3cf0830ad423b41732e0279285bce7b8 (diff)
Add helper for extracting SANs from certificate
- Model SAN as type SubjectAlternativeName - Add SubjectAlternativeName to csr and certificate builders
Diffstat (limited to 'vespa-athenz')
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java23
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java13
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SubjectAlternativeName.java114
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java13
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java24
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java5
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java25
7 files changed, 188 insertions, 29 deletions
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
index d9cd3141f19..e0029681b23 100644
--- 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
@@ -1,15 +1,11 @@
// 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.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
-import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
@@ -40,12 +36,10 @@ public class Pkcs10Csr {
return new X500Principal(csr.getSubject().toString());
}
- public List<String> getSubjectAlternativeNames() {
+ public List<SubjectAlternativeName> getSubjectAlternativeNames() {
return getExtensions()
.map(extensions -> GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName))
- .map(generalNames -> Arrays.stream(generalNames.getNames())
- .map(Pkcs10Csr::toString)
- .collect(toList()))
+ .map(SubjectAlternativeName::fromGeneralNames)
.orElse(emptyList());
}
@@ -74,17 +68,4 @@ public class Pkcs10Csr {
.map(attribute -> Extensions.getInstance(attribute.getAttrValues().getObjectAt(0)));
}
- private static String toString(GeneralName generalName) {
- ASN1Encodable name = generalName.getName();
- switch (generalName.getTagNo()) {
- case GeneralName.rfc822Name:
- case GeneralName.dNSName:
- case GeneralName.uniformResourceIdentifier:
- return DERIA5String.getInstance(name).getString();
- case GeneralName.directoryName:
- return X500Name.getInstance(name).toString();
- default:
- return name.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
index 9c2cd9d4d9f..29be201fb43 100644
--- 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
@@ -20,6 +20,8 @@ import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
+import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME;
+
/**
* @author bjorncs
*/
@@ -27,7 +29,7 @@ public class Pkcs10CsrBuilder {
private final X500Principal subject;
private final KeyPair keyPair;
- private final List<String> subjectAlternativeNames = new ArrayList<>();
+ private final List<SubjectAlternativeName> subjectAlternativeNames = new ArrayList<>();
private final SignatureAlgorithm signatureAlgorithm;
private BasicConstraintsExtension basicConstraintsExtension;
@@ -45,7 +47,12 @@ public class Pkcs10CsrBuilder {
return new Pkcs10CsrBuilder(subject, keyPair, signatureAlgorithm);
}
- public Pkcs10CsrBuilder addSubjectAlternativeName(String san) {
+ public Pkcs10CsrBuilder addSubjectAlternativeName(String dns) {
+ this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dns));
+ return this;
+ }
+
+ public Pkcs10CsrBuilder addSubjectAlternativeName(SubjectAlternativeName san) {
this.subjectAlternativeNames.add(san);
return this;
}
@@ -69,7 +76,7 @@ public class Pkcs10CsrBuilder {
if (!subjectAlternativeNames.isEmpty()) {
GeneralNames generalNames = new GeneralNames(
subjectAlternativeNames.stream()
- .map(san -> new GeneralName(GeneralName.dNSName, san))
+ .map(SubjectAlternativeName::toGeneralName)
.toArray(GeneralName[]::new));
extGen.addExtension(Extension.subjectAlternativeName, false, generalNames);
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SubjectAlternativeName.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SubjectAlternativeName.java
new file mode 100644
index 00000000000..8b89fc6fe7f
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/SubjectAlternativeName.java
@@ -0,0 +1,114 @@
+// 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.ASN1Encodable;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author bjorncs
+ */
+public class SubjectAlternativeName {
+
+ private final Type type;
+ private final String value;
+
+ public SubjectAlternativeName(Type type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ SubjectAlternativeName(GeneralName bcGeneralName) {
+ this.type = Type.fromTag(bcGeneralName.getTagNo());
+ this.value = getValue(bcGeneralName);
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ GeneralName toGeneralName() {
+ return new GeneralName(type.tag, value);
+ }
+
+ static List<SubjectAlternativeName> fromGeneralNames(GeneralNames generalNames) {
+ return Arrays.stream(generalNames.getNames()).map(SubjectAlternativeName::new).collect(toList());
+ }
+
+ private String getValue(GeneralName bcGeneralName) {
+ ASN1Encodable name = bcGeneralName.getName();
+ switch (bcGeneralName.getTagNo()) {
+ case GeneralName.rfc822Name:
+ case GeneralName.dNSName:
+ case GeneralName.uniformResourceIdentifier:
+ return DERIA5String.getInstance(name).getString();
+ case GeneralName.directoryName:
+ return X500Name.getInstance(name).toString();
+ default:
+ return name.toString();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SubjectAlternativeName{" +
+ "type=" + type +
+ ", value='" + value + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SubjectAlternativeName that = (SubjectAlternativeName) o;
+ return type == that.type &&
+ Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, value);
+ }
+
+ public enum Type {
+ OTHER_NAME(0),
+ RFC822_NAME(1),
+ DNS_NAME(2),
+ X400_ADDRESS(3),
+ DIRECTORY_NAME(4),
+ EDI_PARITY_NAME(5),
+ UNIFORM_RESOURCE_IDENTIFIER(6),
+ IP_ADDRESS(7),
+ REGISTERED_ID(8);
+
+ final int tag;
+
+ Type(int tag) {
+ this.tag = tag;
+ }
+
+ public static Type fromTag(int tag) {
+ return Arrays.stream(Type.values())
+ .filter(type -> type.tag == tag)
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException("Invalid tag: " + tag));
+ }
+
+ public int getTag() {
+ return tag;
+ }
+ }
+}
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
index ba22af13fd2..c27b704f6a3 100644
--- 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
@@ -27,6 +27,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME;
+
/**
* @author bjorncs
*/
@@ -37,7 +39,7 @@ public class X509CertificateBuilder {
private final PrivateKey caPrivateKey;
private final Instant notBefore;
private final Instant notAfter;
- private final List<String> subjectAlternativeNames = new ArrayList<>();
+ private final List<SubjectAlternativeName> subjectAlternativeNames = new ArrayList<>();
private final X500Principal issuer;
private final X500Principal subject;
private final PublicKey certPublicKey;
@@ -102,7 +104,12 @@ public class X509CertificateBuilder {
serialNumber);
}
- public X509CertificateBuilder addSubjectAlternativeName(String san) {
+ public X509CertificateBuilder addSubjectAlternativeName(String dnsName) {
+ this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dnsName));
+ return this;
+ }
+
+ public X509CertificateBuilder addSubjectAlternativeName(SubjectAlternativeName san) {
this.subjectAlternativeNames.add(san);
return this;
}
@@ -125,7 +132,7 @@ public class X509CertificateBuilder {
if (!subjectAlternativeNames.isEmpty()) {
GeneralNames generalNames = new GeneralNames(
subjectAlternativeNames.stream()
- .map(san -> new GeneralName(GeneralName.dNSName, san))
+ .map(SubjectAlternativeName::toGeneralName)
.toArray(GeneralName[]::new));
jcaCertBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames);
}
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
index 3e1bd3eb31c..707666ae3c8 100644
--- 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
@@ -1,6 +1,10 @@
// 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.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.openssl.PEMParser;
@@ -16,9 +20,14 @@ import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import static com.yahoo.vespa.athenz.tls.Extension.SUBJECT_ALTERNATIVE_NAMES;
import static java.util.stream.Collectors.toList;
/**
@@ -75,4 +84,19 @@ public class X509CertificateUtils {
}
}
+
+ public static List<SubjectAlternativeName> getSubjectAlternativeNames(X509Certificate certificate) {
+ try {
+ byte[] extensionValue = certificate.getExtensionValue(SUBJECT_ALTERNATIVE_NAMES.getOId());
+ if (extensionValue == null) return Collections.emptyList();
+ ASN1Encodable asn1Encodable = ASN1Primitive.fromByteArray(extensionValue);
+ if (asn1Encodable instanceof ASN1OctetString) {
+ asn1Encodable = ASN1Primitive.fromByteArray(((ASN1OctetString) asn1Encodable).getOctets());
+ }
+ GeneralNames names = GeneralNames.getInstance(asn1Encodable);
+ return SubjectAlternativeName.fromGeneralNames(names);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java
index bb2e80ba705..ea60511f39c 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java
@@ -7,6 +7,7 @@ import java.security.KeyPair;
import java.util.Arrays;
import java.util.List;
+import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -19,8 +20,8 @@ public class Pkcs10CsrTest {
public void can_read_subject_alternative_names() {
X500Principal subject = new X500Principal("CN=subject");
KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
- String san1 = "san1.com";
- String san2 = "san2.com";
+ SubjectAlternativeName san1 = new SubjectAlternativeName(DNS_NAME, "san1.com");
+ SubjectAlternativeName san2 = new SubjectAlternativeName(DNS_NAME, "san2.com");
Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA)
.addSubjectAlternativeName(san1)
.addSubjectAlternativeName(san2)
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
index 847f49bf537..cc203011c0b 100644
--- 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
@@ -1,5 +1,6 @@
package com.yahoo.vespa.athenz.tls;
+import org.hamcrest.Matchers;
import org.junit.Test;
import javax.security.auth.x500.X500Principal;
@@ -7,8 +8,12 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
+import java.util.List;
+import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -38,4 +43,24 @@ public class X509CertificateUtilsTest {
}
+ @Test
+ public void can_list_subject_alternative_names() {
+ KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
+ X500Principal subject = new X500Principal("CN=myservice");
+ SubjectAlternativeName san = new SubjectAlternativeName(DNS_NAME, "dns-san");
+ X509Certificate cert = X509CertificateBuilder
+ .fromKeypair(
+ keypair,
+ subject,
+ Instant.now(),
+ Instant.now().plus(1, ChronoUnit.DAYS),
+ SignatureAlgorithm.SHA256_WITH_RSA,
+ 1)
+ .addSubjectAlternativeName(san)
+ .build();
+
+ List<SubjectAlternativeName> sans = X509CertificateUtils.getSubjectAlternativeNames(cert);
+ assertThat(sans.size(), is(1));
+ assertThat(sans.get(0), equalTo(san));
+ }
} \ No newline at end of file