diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-03-20 13:23:07 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-03-20 14:05:29 +0100 |
commit | d6ba1b913fbcfa69387ddb6390b1bfa057753e2a (patch) | |
tree | 4dfa6c6e6f08e15a706ac711b581c351431c5282 /vespa-athenz | |
parent | 1cfd08c3c3eb48dce0bf59ea71592438aeaefe96 (diff) |
Add getters for basic constraints and subject alternative names
Diffstat (limited to 'vespa-athenz')
7 files changed, 175 insertions, 17 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BasicConstraintsExtension.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BasicConstraintsExtension.java new file mode 100644 index 00000000000..008268dbfe0 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BasicConstraintsExtension.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; + +/** + * @author bjorncs + */ +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/Extension.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Extension.java new file mode 100644 index 00000000000..18403669c4d --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Extension.java @@ -0,0 +1,22 @@ +// 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.ASN1ObjectIdentifier; + +/** + * @author bjorncs + */ +public enum Extension { + BASIC_CONSTRAINS(org.bouncycastle.asn1.x509.Extension.basicConstraints), + SUBJECT_ALTERNATIVE_NAMES(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName); + + final ASN1ObjectIdentifier extensionOId; + + Extension(ASN1ObjectIdentifier extensionOId) { + this.extensionOId = extensionOId; + } + + public String getOId() { + return extensionOId.getId(); + } +} 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 7a43b5a1fd5..061a70872f4 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,13 +1,25 @@ // 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.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; import javax.security.auth.x500.X500Principal; -import java.io.IOException; -import java.io.StringReader; -import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; /** * @author bjorncs @@ -27,4 +39,52 @@ public class Pkcs10Csr { public X500Principal getSubject() { return new X500Principal(csr.getSubject().toString()); } + + public List<String> getSubjectAlternativeNames() { + return getExtensions() + .map(extensions -> GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName)) + .filter(Objects::nonNull) + .flatMap(generalNames -> Arrays.stream(generalNames.getNames())) + .map(Pkcs10Csr::toString) + .collect(toList()); + } + + /** + * @return true if CA certificate + */ + public boolean getBasicConstraints() { + return getExtensions() + .map(BasicConstraints::fromExtensions) + .findAny() + .map(BasicConstraints::isCA) + .orElse(false); + } + + public List<String> getExtensionOIds() { + return getExtensions() + .flatMap(extensions -> Arrays.stream(extensions.getExtensionOIDs())) + .map(ASN1ObjectIdentifier::getId) + .collect(toList()); + + } + + private Stream<Extensions> getExtensions() { + return Arrays + .stream(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) + .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 877f05f998a..9c2cd9d4d9f 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.athenz.tls; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; @@ -28,6 +29,7 @@ public class Pkcs10CsrBuilder { private final KeyPair keyPair; private final List<String> subjectAlternativeNames = new ArrayList<>(); private final SignatureAlgorithm signatureAlgorithm; + private BasicConstraintsExtension basicConstraintsExtension; private Pkcs10CsrBuilder(X500Principal subject, KeyPair keyPair, @@ -48,20 +50,30 @@ public class Pkcs10CsrBuilder { return this; } + public Pkcs10CsrBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) { + this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate); + return this; + } + public Pkcs10Csr build() { try { PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); - + ExtensionsGenerator extGen = new ExtensionsGenerator(); + if (basicConstraintsExtension != null) { + extGen.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)); - ExtensionsGenerator extGen = new ExtensionsGenerator(); extGen.addExtension(Extension.subjectAlternativeName, false, generalNames); - requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); } + requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm.getAlgorithmName()) .setProvider(BouncyCastleProviderHolder.getInstance()) .build(keyPair.getPrivate()); 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 e60592db2f5..ba22af13fd2 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 @@ -7,7 +7,6 @@ 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; @@ -143,12 +142,4 @@ public class X509CertificateBuilder { } } - 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 index 4c4399cd2cb..3e1bd3eb31c 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 @@ -9,6 +9,7 @@ import org.bouncycastle.util.io.pem.PemObject; import javax.naming.NamingException; import javax.naming.ldap.LdapName; +import javax.security.auth.x500.X500Principal; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; @@ -59,8 +60,12 @@ public class X509CertificateUtils { } public static List<String> getCommonNames(X509Certificate certificate) { + return getCommonNames(certificate.getSubjectX500Principal()); + } + + public static List<String> getCommonNames(X500Principal subject) { try { - String subjectPrincipal = certificate.getSubjectX500Principal().getName(); + String subjectPrincipal = subject.getName(); return new LdapName(subjectPrincipal).getRdns().stream() .filter(rdn -> rdn.getType().equalsIgnoreCase("cn")) .map(rdn -> rdn.getValue().toString()) 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 new file mode 100644 index 00000000000..9586906668d --- /dev/null +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java @@ -0,0 +1,54 @@ +package com.yahoo.vespa.athenz.tls; + +import org.junit.Test; + +import javax.security.auth.x500.X500Principal; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author bjorncs + */ +public class Pkcs10CsrTest { + + @Test + 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"; + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA) + .addSubjectAlternativeName(san1) + .addSubjectAlternativeName(san2) + .build(); + assertEquals(Arrays.asList(san1, san2), csr.getSubjectAlternativeNames()); + } + + @Test + public void can_read_basic_constraints() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA) + .setBasicConstraints(true, true) + .build(); + assertTrue(csr.getBasicConstraints()); + } + + @Test + public void can_read_extensions() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA256_WITH_RSA) + .addSubjectAlternativeName("san") + .setBasicConstraints(true, true) + .build(); + List<String> expected = Arrays.asList(Extension.BASIC_CONSTRAINS.getOId(), Extension.SUBJECT_ALTERNATIVE_NAMES.getOId()); + List<String> actual = csr.getExtensionOIds(); + assertEquals(expected, actual); + } + +}
\ No newline at end of file |