diff options
author | Harald Musum <musum@oath.com> | 2018-03-21 15:20:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-21 15:20:54 +0100 |
commit | 9406df0701e050b8428cb0ce03cda9a275e4cd97 (patch) | |
tree | 5ea1d3c0e31286ea9b6adc1d1a7f1edbb70346a9 /athenz-identity-provider-service/src | |
parent | a4a3147111bc427f2af081e8572b5618e3e05e7d (diff) |
Revert "Bjorncs/certificate builder"
Diffstat (limited to 'athenz-identity-provider-service/src')
8 files changed, 194 insertions, 85 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java index 3091321c47a..376dd2ed4ac 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java @@ -8,18 +8,32 @@ import com.yahoo.jdisc.http.ssl.SslTrustStoreContext; import com.yahoo.log.LogLevel; import com.yahoo.vespa.athenz.tls.KeyStoreBuilder; import com.yahoo.vespa.athenz.tls.KeyStoreType; -import com.yahoo.vespa.athenz.tls.SignatureAlgorithm; -import com.yahoo.vespa.athenz.tls.X509CertificateBuilder; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; +import org.bouncycastle.asn1.x500.X500Name; +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.X509v3CertificateBuilder; +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.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import javax.security.auth.x500.X500Principal; import java.io.File; +import java.io.IOException; +import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; +import java.util.Date; import java.util.logging.Logger; /** @@ -30,6 +44,7 @@ public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurato private static final Logger log = Logger.getLogger(AthenzSslTrustStoreConfigurator.class.getName()); private static final String CERTIFICATE_ALIAS = "cfgselfsigned"; + private static final Provider provider = new BouncyCastleProvider(); private final KeyStore trustStore; @Inject @@ -74,20 +89,29 @@ public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurato return keyProvider.getKeyPair(zoneConfig.secretVersion()); } - private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config) { - X500Principal subject = new X500Principal("CN="+ config.loadBalancerAddress()); + private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config) + throws IOException, CertificateException, OperatorCreationException { + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate()); + X500Name x500Name = new X500Name("CN="+ config.loadBalancerAddress()); Instant now = Instant.now(); - X509CertificateBuilder builder = X509CertificateBuilder - .fromKeypair( - keyPair, - subject, - now, - now.plus(Duration.ofDays(30)), - SignatureAlgorithm.SHA256_WITH_RSA, - now.toEpochMilli()) - .setBasicConstraints(true, true); - config.zookeeperserver().forEach(server -> builder.addSubjectAlternativeName(server.hostname())); - return builder.build(); + Date notBefore = Date.from(now); + Date notAfter = Date.from(now.plus(Duration.ofDays(30))); + + GeneralNames generalNames = new GeneralNames( + config.zookeeperserver().stream() + .map(server -> new GeneralName(GeneralName.dNSName, server.hostname())) + .toArray(GeneralName[]::new)); + + X509v3CertificateBuilder certificateBuilder = + new JcaX509v3CertificateBuilder( + x500Name, BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, x500Name, keyPair.getPublic() + ) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) + .addExtension(Extension.subjectAlternativeName, false, generalNames); + + return new JcaX509CertificateConverter() + .setProvider(provider) + .getCertificate(certificateBuilder.build(contentSigner)); } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java index cfef2bc0e33..25733bf0075 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java @@ -7,9 +7,12 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.yahoo.vespa.athenz.tls.X509CertificateUtils; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.util.io.pem.PemObject; import java.io.IOException; +import java.io.StringWriter; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; /** @@ -53,7 +56,13 @@ public class CertificateSerializedPayload { @Override public void serialize( X509Certificate certificate, JsonGenerator gen, SerializerProvider serializers) throws IOException { - gen.writeString(X509CertificateUtils.toPem(certificate)); + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); + pemWriter.flush(); + gen.writeString(stringWriter.toString()); + } catch (CertificateEncodingException e) { + throw new RuntimeException("Failed to encode X509Certificate", e); + } } } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java index 7b4a599d5dd..f6f6bb1dbca 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java @@ -6,23 +6,41 @@ import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.provision.Zone; import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.tls.Extension; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.SignatureAlgorithm; -import com.yahoo.vespa.athenz.tls.X509CertificateBuilder; -import com.yahoo.vespa.athenz.tls.X509CertificateUtils; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; - -import javax.security.auth.x500.X500Principal; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.cert.X509v3CertificateBuilder; +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.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; + +import java.math.BigInteger; import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; import java.security.cert.X509Certificate; import java.time.Clock; import java.time.Duration; -import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.getZoneConfig; @@ -38,13 +56,16 @@ public class CertificateSigner { private static final Logger log = Logger.getLogger(CertificateSigner.class.getName()); - static final SignatureAlgorithm SIGNER_ALGORITHM = SignatureAlgorithm.SHA256_WITH_RSA; + static final String SIGNER_ALGORITHM = "SHA256withRSA"; static final Duration CERTIFICATE_EXPIRATION = Duration.ofDays(30); - private static final List<Extension> ILLEGAL_EXTENSIONS = ImmutableList.of( - Extension.BASIC_CONSTRAINS, Extension.SUBJECT_ALTERNATIVE_NAMES); + private static final List<ASN1ObjectIdentifier> ILLEGAL_EXTENSIONS = ImmutableList.of( + Extension.basicConstraints, Extension.subjectAlternativeName); + + private final JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); + private final Provider provider = new BouncyCastleProvider(); private final PrivateKey caPrivateKey; - private final X500Principal issuer; + private final X500Name issuer; private final Clock clock; @Inject @@ -57,7 +78,7 @@ public class CertificateSigner { CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) { this.caPrivateKey = caPrivateKey; - this.issuer = new X500Principal("CN=" + loadBalancerAddress); + this.issuer = new X500Name("CN=" + loadBalancerAddress); this.clock = clock; } @@ -68,28 +89,46 @@ public class CertificateSigner { * <li>CSR does not contain any any of the extensions in {@code ILLEGAL_EXTENSIONS}</li> * </ul> */ - X509Certificate generateX509Certificate(Pkcs10Csr csr, String remoteHostname) { - verifyCertificateCommonName(csr.getSubject(), remoteHostname); - verifyCertificateExtensions(csr); + X509Certificate generateX509Certificate(PKCS10CertificationRequest certReq, String remoteHostname) { + verifyCertificateCommonName(certReq.getSubject(), remoteHostname); + verifyCertificateExtensions(certReq); + + Date notBefore = Date.from(clock.instant()); + Date notAfter = Date.from(clock.instant().plus(CERTIFICATE_EXPIRATION)); - Instant now = clock.instant(); try { - return X509CertificateBuilder.fromCsr(csr, issuer, now, now.plus(CERTIFICATE_EXPIRATION), caPrivateKey, SIGNER_ALGORITHM, now.toEpochMilli()) - .setBasicConstraints(true, false) - .build(); + PublicKey publicKey = new JcaPKCS10CertificationRequest(certReq).getPublicKey(); + X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder( + issuer, BigInteger.valueOf(clock.millis()), notBefore, notAfter, certReq.getSubject(), publicKey) + + // Set Basic Constraints to false + .addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + + ContentSigner caSigner = new JcaContentSignerBuilder(SIGNER_ALGORITHM) + .setProvider(provider) + .build(caPrivateKey); + + return certificateConverter + .setProvider(provider) + .getCertificate(caBuilder.build(caSigner)); } catch (Exception ex) { log.log(LogLevel.ERROR, "Failed to generate X509 Certificate", ex); - throw new RuntimeException("Failed to generate X509 Certificate", ex); + throw new RuntimeException("Failed to generate X509 Certificate"); } } - static void verifyCertificateCommonName(X500Principal subject, String remoteHostname) { - List<String> commonNames = X509CertificateUtils.getCommonNames(subject); - if (commonNames.size() != 1) { + static void verifyCertificateCommonName(X500Name subject, String remoteHostname) { + List<AttributeTypeAndValue> attributesAndValues = Arrays.stream(subject.getRDNs()) + .flatMap(rdn -> rdn.isMultiValued() ? + Stream.of(rdn.getTypesAndValues()) : Stream.of(rdn.getFirst())) + .filter(attr -> attr.getType() == BCStyle.CN) + .collect(Collectors.toList()); + + if (attributesAndValues.size() != 1) { throw new IllegalArgumentException("Only 1 common name should be set"); } - String actualCommonName = commonNames.get(0); + String actualCommonName = DERUTF8String.getInstance(attributesAndValues.get(0).getValue()).getString(); if (! actualCommonName.equals(remoteHostname)) { throw new IllegalArgumentException("Remote hostname " + remoteHostname + " does not match common name " + actualCommonName); @@ -97,12 +136,15 @@ public class CertificateSigner { } @SuppressWarnings("unchecked") - static void verifyCertificateExtensions(Pkcs10Csr csr) { - List<String> extensionOIds = csr.getExtensionOIds(); - List<String> illegalExt = ILLEGAL_EXTENSIONS.stream() - .map(Extension::getOId) - .filter(extensionOIds::contains) + static void verifyCertificateExtensions(PKCS10CertificationRequest request) { + List<String> illegalExt = Arrays + .stream(request.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) + .map(attribute -> Extensions.getInstance(attribute.getAttrValues().getObjectAt(0))) + .flatMap(ext -> Collections.list((Enumeration<ASN1ObjectIdentifier>) ext.oids()).stream()) + .filter(ILLEGAL_EXTENSIONS::contains) + .map(ASN1ObjectIdentifier::getId) .collect(Collectors.toList()); + if (! illegalExt.isEmpty()) { throw new IllegalArgumentException("CSR contains illegal extensions: " + String.join(", ", illegalExt)); } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java index 1dd452866a5..0c6199efdcb 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca; import com.google.inject.Inject; import com.yahoo.container.jaxrs.annotation.Component; import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.BadRequestException; @@ -45,7 +45,7 @@ public class CertificateSignerResource { try { InetAddress addr = InetAddress.getByName(req.getRemoteAddr()); String remoteHostname = addr.getHostName(); - Pkcs10Csr csr = csrPayload.csr; + PKCS10CertificationRequest csr = csrPayload.csr; log.log(LogLevel.DEBUG, "Certification request from " + remoteHostname + ": " + csr); X509Certificate certificate = certificateSigner.generateX509Certificate(csr, remoteHostname); return new CertificateSerializedPayload(certificate); diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java index 375a4c3e17d..f56214513aa 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java @@ -7,10 +7,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; import java.io.IOException; +import java.io.StringReader; /** * Contains PEM formatted Certificate Signing Request (CSR) @@ -19,11 +20,11 @@ import java.io.IOException; */ public class CsrSerializedPayload { - @JsonProperty("csr") public final Pkcs10Csr csr; + @JsonProperty("csr") public final PKCS10CertificationRequest csr; @JsonCreator public CsrSerializedPayload(@JsonProperty("csr") @JsonDeserialize(using = CertificateRequestDeserializer.class) - Pkcs10Csr csr) { + PKCS10CertificationRequest csr) { this.csr = csr; } @@ -49,11 +50,13 @@ public class CsrSerializedPayload { '}'; } - public static class CertificateRequestDeserializer extends JsonDeserializer<Pkcs10Csr> { + public static class CertificateRequestDeserializer extends JsonDeserializer<PKCS10CertificationRequest> { @Override - public Pkcs10Csr deserialize( + public PKCS10CertificationRequest deserialize( JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - return Pkcs10CsrUtils.fromPem(jsonParser.getValueAsString()); + try (PEMParser pemParser = new PEMParser(new StringReader(jsonParser.getValueAsString()))) { + return (PKCS10CertificationRequest) pemParser.readObject(); + } } } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java index 35b483affae..ca5c776bf3c 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java @@ -1,10 +1,10 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl; +import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.athenz.zts.InstanceRefreshRequest; import com.yahoo.athenz.zts.ZTSClient; import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider; -import com.yahoo.vespa.athenz.tls.X509CertificateUtils; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import javax.net.ssl.SSLContext; @@ -34,7 +34,7 @@ public class AthenzCertificateClient { req.setKeyId(Integer.toString(zoneConfig.secretVersion())); String pemEncoded = ztsClient.postInstanceRefreshRequest(zoneConfig.domain(), zoneConfig.serviceName(), req) .getCertificate(); - return X509CertificateUtils.fromPem(pemEncoded); + return Crypto.loadX509Certificate(pemEncoded); } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/CkmsKeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/CkmsKeyProvider.java index 41aee29d761..2f2cd5a8495 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/CkmsKeyProvider.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/CkmsKeyProvider.java @@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl; import com.google.inject.Inject; +import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.config.provision.Zone; import com.yahoo.container.jdisc.Ckms; -import com.yahoo.vespa.athenz.tls.KeyUtils; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; @@ -58,9 +58,10 @@ public class CkmsKeyProvider implements KeyProvider { } } + // TODO: Consider moving to cryptoutils private KeyPair readKeyPair(int version) { - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(ckms.getSecret(secretName, version)); - PublicKey publicKey = KeyUtils.extractPublicKey(privateKey); + PrivateKey privateKey = Crypto.loadPrivateKey(ckms.getSecret(secretName, version)); + PublicKey publicKey = Crypto.extractPublicKey(privateKey); return new KeyPair(publicKey, privateKey); } } diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java index 6c624eb1da0..594bbf77fce 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java @@ -2,16 +2,23 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca; import com.yahoo.test.ManualClock; -import com.yahoo.vespa.athenz.tls.Extension; -import com.yahoo.vespa.athenz.tls.KeyAlgorithm; -import com.yahoo.vespa.athenz.tls.KeyUtils; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +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.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import org.junit.Test; -import javax.security.auth.x500.X500Principal; import java.math.BigInteger; import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Collection; @@ -28,6 +35,8 @@ import static org.junit.Assert.fail; */ public class CertificateSignerTest { + private final KeyPair clientKeyPair = getKeyPair(); + private final long startTime = 1234567890000L; private final KeyPair caKeyPair = getKeyPair(); private final String cfgServerHostname = "cfg1.us-north-1.vespa.domain.tld"; @@ -38,21 +47,22 @@ public class CertificateSignerTest { @Test public void test_signing() throws Exception { - String subject = String.format("CN=%s,OU=Vespa,C=NO", requestersHostname); - Pkcs10Csr csr = createCsrBuilder(subject).build(); + ExtensionsGenerator extGen = new ExtensionsGenerator(); + String subject = "C=NO,OU=Vespa,CN=" + requestersHostname; + PKCS10CertificationRequest request = makeRequest(subject, extGen.generate()); - X509Certificate certificate = signer.generateX509Certificate(csr, requestersHostname); - assertCertificate(certificate, subject, Collections.singleton(Extension.BASIC_CONSTRAINS.getOId())); + X509Certificate certificate = signer.generateX509Certificate(request, requestersHostname); + assertCertificate(certificate, subject, Collections.singleton(Extension.basicConstraints.getId())); } @Test public void common_name_test() throws Exception { CertificateSigner.verifyCertificateCommonName( - new X500Principal("CN=" + requestersHostname), requestersHostname); + new X500Name("CN=" + requestersHostname), requestersHostname); CertificateSigner.verifyCertificateCommonName( - new X500Principal("C=NO,OU=Vespa,CN=" + requestersHostname), requestersHostname); + new X500Name("C=NO,OU=Vespa,CN=" + requestersHostname), requestersHostname); CertificateSigner.verifyCertificateCommonName( - new X500Principal("C=NO+OU=org,CN=" + requestersHostname), requestersHostname); + new X500Name("C=NO+OU=org,CN=" + requestersHostname), requestersHostname); assertCertificateCommonNameException("C=NO", "Only 1 common name should be set"); assertCertificateCommonNameException("C=US+CN=abc123.domain.tld,C=NO+CN=" + requestersHostname, "Only 1 common name should be set"); @@ -62,15 +72,26 @@ public class CertificateSignerTest { @Test(expected = IllegalArgumentException.class) public void extensions_test_subject_alternative_names() throws Exception { - Pkcs10Csr csr = createCsrBuilder("OU=Vespa") - .addSubjectAlternativeName("some.other.domain.tld") - .build(); - CertificateSigner.verifyCertificateExtensions(csr); + ExtensionsGenerator extGen = new ExtensionsGenerator(); + extGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(new GeneralName[] { + new GeneralName(GeneralName.dNSName, "some.other.domain.tld")})); + PKCS10CertificationRequest request = makeRequest("OU=Vespa", extGen.generate()); + + CertificateSigner.verifyCertificateExtensions(request); + } + + @Test + public void extensions_allowed() throws Exception { + ExtensionsGenerator extGen = new ExtensionsGenerator(); + extGen.addExtension(Extension.certificateIssuer, true, new byte[0]); + PKCS10CertificationRequest request = makeRequest("OU=Vespa", extGen.generate()); + + CertificateSigner.verifyCertificateExtensions(request); } private void assertCertificateCommonNameException(String subject, String expectedMessage) { try { - CertificateSigner.verifyCertificateCommonName(new X500Principal(subject), requestersHostname); + CertificateSigner.verifyCertificateCommonName(new X500Name(subject), requestersHostname); fail("Expected to fail"); } catch (IllegalArgumentException e) { assertEquals(expectedMessage, e.getMessage()); @@ -82,8 +103,8 @@ public class CertificateSignerTest { assertEquals(BigInteger.valueOf(startTime), certificate.getSerialNumber()); assertEquals(startTime, certificate.getNotBefore().getTime()); assertEquals(startTime + CertificateSigner.CERTIFICATE_EXPIRATION.toMillis(), certificate.getNotAfter().getTime()); - assertEquals(CertificateSigner.SIGNER_ALGORITHM.getAlgorithmName(), certificate.getSigAlgName()); - assertEquals(new X500Principal(expectedSubjectName), certificate.getSubjectX500Principal()); + assertEquals(CertificateSigner.SIGNER_ALGORITHM, certificate.getSigAlgName()); + assertEquals(expectedSubjectName, certificate.getSubjectDN().getName()); assertEquals("CN=" + cfgServerHostname, certificate.getIssuerX500Principal().getName()); Set<String> extensions = Stream.of(certificate.getNonCriticalExtensionOIDs(), @@ -95,11 +116,20 @@ public class CertificateSignerTest { certificate.verify(caKeyPair.getPublic()); } - private Pkcs10CsrBuilder createCsrBuilder(String subject) { - return Pkcs10CsrBuilder.fromKeypair(new X500Principal(subject), caKeyPair, CertificateSigner.SIGNER_ALGORITHM); + private PKCS10CertificationRequest makeRequest(String subject, Extensions extensions) throws Exception { + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder( + new X500Name(subject), clientKeyPair.getPublic()); + builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions); + + ContentSigner signGen = new JcaContentSignerBuilder(CertificateSigner.SIGNER_ALGORITHM).build(caKeyPair.getPrivate()); + return builder.build(signGen); } private static KeyPair getKeyPair() { - return KeyUtils.generateKeypair(KeyAlgorithm.RSA); + try { + return KeyPairGenerator.getInstance("RSA").genKeyPair(); + } catch (Exception e) { + throw new RuntimeException(e); + } } } |