summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2018-03-21 11:37:21 +0100
committerGitHub <noreply@github.com>2018-03-21 11:37:21 +0100
commitde021c008d317b75b59e1d76624cbcb14fe18f27 (patch)
tree80e0003130e28500d2b7746aa165eeac548094cb
parentdb9aff7fd0015b37b9cb034c6c798e90f231fa5f (diff)
parent8d38ad85f091a88abb8269bae8372ca0444dcabf (diff)
Merge pull request #5386 from vespa-engine/bjorncs/certificate-builder
Bjorncs/certificate builder
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java56
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java13
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java102
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java4
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java17
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java4
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/CkmsKeyProvider.java7
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java76
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java13
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java51
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java21
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java36
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java29
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/AthenzCredentialsService.java54
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtils.java113
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/InstanceIdentity.java3
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/BasicConstraintsExtension.java14
-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/Extension.java22
-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/KeyStoreType.java4
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java41
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java90
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java89
-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.java145
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java78
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/AthenzIdentities.java18
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtilsTest.java28
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/AthenzSslContextBuilderTest.java3
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyStoreBuilderTest.java3
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyUtilsTest.java22
-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/Pkcs10CsrTest.java55
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java29
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/TestUtils.java50
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java58
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java36
40 files changed, 1007 insertions, 498 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 376dd2ed4ac..3091321c47a 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,32 +8,18 @@ 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;
/**
@@ -44,7 +30,6 @@ 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
@@ -89,29 +74,20 @@ public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurato
return keyProvider.getKeyPair(zoneConfig.secretVersion());
}
- 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());
+ private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config) {
+ X500Principal subject = new X500Principal("CN="+ config.loadBalancerAddress());
Instant now = Instant.now();
- 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));
+ 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();
}
}
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 25733bf0075..cfef2bc0e33 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,12 +7,9 @@ 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 org.bouncycastle.openssl.jcajce.JcaPEMWriter;
-import org.bouncycastle.util.io.pem.PemObject;
+import com.yahoo.vespa.athenz.tls.X509CertificateUtils;
import java.io.IOException;
-import java.io.StringWriter;
-import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
/**
@@ -56,13 +53,7 @@ public class CertificateSerializedPayload {
@Override
public void serialize(
X509Certificate certificate, JsonGenerator gen, SerializerProvider serializers) throws IOException {
- 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);
- }
+ gen.writeString(X509CertificateUtils.toPem(certificate));
}
}
}
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 f6f6bb1dbca..7b4a599d5dd 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,41 +6,23 @@ 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 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 javax.security.auth.x500.X500Principal;
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.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
+import java.time.Instant;
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;
@@ -56,16 +38,13 @@ public class CertificateSigner {
private static final Logger log = Logger.getLogger(CertificateSigner.class.getName());
- static final String SIGNER_ALGORITHM = "SHA256withRSA";
+ static final SignatureAlgorithm SIGNER_ALGORITHM = SignatureAlgorithm.SHA256_WITH_RSA;
static final Duration CERTIFICATE_EXPIRATION = Duration.ofDays(30);
- 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 static final List<Extension> ILLEGAL_EXTENSIONS = ImmutableList.of(
+ Extension.BASIC_CONSTRAINS, Extension.SUBJECT_ALTERNATIVE_NAMES);
private final PrivateKey caPrivateKey;
- private final X500Name issuer;
+ private final X500Principal issuer;
private final Clock clock;
@Inject
@@ -78,7 +57,7 @@ public class CertificateSigner {
CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) {
this.caPrivateKey = caPrivateKey;
- this.issuer = new X500Name("CN=" + loadBalancerAddress);
+ this.issuer = new X500Principal("CN=" + loadBalancerAddress);
this.clock = clock;
}
@@ -89,46 +68,28 @@ public class CertificateSigner {
* <li>CSR does not contain any any of the extensions in {@code ILLEGAL_EXTENSIONS}</li>
* </ul>
*/
- 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));
+ X509Certificate generateX509Certificate(Pkcs10Csr csr, String remoteHostname) {
+ verifyCertificateCommonName(csr.getSubject(), remoteHostname);
+ verifyCertificateExtensions(csr);
+ Instant now = clock.instant();
try {
- 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));
+ return X509CertificateBuilder.fromCsr(csr, issuer, now, now.plus(CERTIFICATE_EXPIRATION), caPrivateKey, SIGNER_ALGORITHM, now.toEpochMilli())
+ .setBasicConstraints(true, false)
+ .build();
} catch (Exception ex) {
log.log(LogLevel.ERROR, "Failed to generate X509 Certificate", ex);
- throw new RuntimeException("Failed to generate X509 Certificate");
+ throw new RuntimeException("Failed to generate X509 Certificate", ex);
}
}
- 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) {
+ static void verifyCertificateCommonName(X500Principal subject, String remoteHostname) {
+ List<String> commonNames = X509CertificateUtils.getCommonNames(subject);
+ if (commonNames.size() != 1) {
throw new IllegalArgumentException("Only 1 common name should be set");
}
- String actualCommonName = DERUTF8String.getInstance(attributesAndValues.get(0).getValue()).getString();
+ String actualCommonName = commonNames.get(0);
if (! actualCommonName.equals(remoteHostname)) {
throw new IllegalArgumentException("Remote hostname " + remoteHostname +
" does not match common name " + actualCommonName);
@@ -136,15 +97,12 @@ public class CertificateSigner {
}
@SuppressWarnings("unchecked")
- 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)
+ static void verifyCertificateExtensions(Pkcs10Csr csr) {
+ List<String> extensionOIds = csr.getExtensionOIds();
+ List<String> illegalExt = ILLEGAL_EXTENSIONS.stream()
+ .map(Extension::getOId)
+ .filter(extensionOIds::contains)
.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 0c6199efdcb..1dd452866a5 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 org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import com.yahoo.vespa.athenz.tls.Pkcs10Csr;
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();
- PKCS10CertificationRequest csr = csrPayload.csr;
+ Pkcs10Csr 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 f56214513aa..375a4c3e17d 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,11 +7,10 @@ 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 org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import com.yahoo.vespa.athenz.tls.Pkcs10Csr;
+import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils;
import java.io.IOException;
-import java.io.StringReader;
/**
* Contains PEM formatted Certificate Signing Request (CSR)
@@ -20,11 +19,11 @@ import java.io.StringReader;
*/
public class CsrSerializedPayload {
- @JsonProperty("csr") public final PKCS10CertificationRequest csr;
+ @JsonProperty("csr") public final Pkcs10Csr csr;
@JsonCreator
public CsrSerializedPayload(@JsonProperty("csr") @JsonDeserialize(using = CertificateRequestDeserializer.class)
- PKCS10CertificationRequest csr) {
+ Pkcs10Csr csr) {
this.csr = csr;
}
@@ -50,13 +49,11 @@ public class CsrSerializedPayload {
'}';
}
- public static class CertificateRequestDeserializer extends JsonDeserializer<PKCS10CertificationRequest> {
+ public static class CertificateRequestDeserializer extends JsonDeserializer<Pkcs10Csr> {
@Override
- public PKCS10CertificationRequest deserialize(
+ public Pkcs10Csr deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
- try (PEMParser pemParser = new PEMParser(new StringReader(jsonParser.getValueAsString()))) {
- return (PKCS10CertificationRequest) pemParser.readObject();
- }
+ return Pkcs10CsrUtils.fromPem(jsonParser.getValueAsString());
}
}
}
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 ca5c776bf3c..35b483affae 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 Crypto.loadX509Certificate(pemEncoded);
+ return X509CertificateUtils.fromPem(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 2f2cd5a8495..41aee29d761 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,10 +58,9 @@ public class CkmsKeyProvider implements KeyProvider {
}
}
- // TODO: Consider moving to cryptoutils
private KeyPair readKeyPair(int version) {
- PrivateKey privateKey = Crypto.loadPrivateKey(ckms.getSecret(secretName, version));
- PublicKey publicKey = Crypto.extractPublicKey(privateKey);
+ PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(ckms.getSecret(secretName, version));
+ PublicKey publicKey = KeyUtils.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 594bbf77fce..6c624eb1da0 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,23 +2,16 @@
package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca;
import com.yahoo.test.ManualClock;
-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 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.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;
@@ -35,8 +28,6 @@ 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";
@@ -47,22 +38,21 @@ public class CertificateSignerTest {
@Test
public void test_signing() throws Exception {
- ExtensionsGenerator extGen = new ExtensionsGenerator();
- String subject = "C=NO,OU=Vespa,CN=" + requestersHostname;
- PKCS10CertificationRequest request = makeRequest(subject, extGen.generate());
+ String subject = String.format("CN=%s,OU=Vespa,C=NO", requestersHostname);
+ Pkcs10Csr csr = createCsrBuilder(subject).build();
- X509Certificate certificate = signer.generateX509Certificate(request, requestersHostname);
- assertCertificate(certificate, subject, Collections.singleton(Extension.basicConstraints.getId()));
+ X509Certificate certificate = signer.generateX509Certificate(csr, requestersHostname);
+ assertCertificate(certificate, subject, Collections.singleton(Extension.BASIC_CONSTRAINS.getOId()));
}
@Test
public void common_name_test() throws Exception {
CertificateSigner.verifyCertificateCommonName(
- new X500Name("CN=" + requestersHostname), requestersHostname);
+ new X500Principal("CN=" + requestersHostname), requestersHostname);
CertificateSigner.verifyCertificateCommonName(
- new X500Name("C=NO,OU=Vespa,CN=" + requestersHostname), requestersHostname);
+ new X500Principal("C=NO,OU=Vespa,CN=" + requestersHostname), requestersHostname);
CertificateSigner.verifyCertificateCommonName(
- new X500Name("C=NO+OU=org,CN=" + requestersHostname), requestersHostname);
+ new X500Principal("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");
@@ -72,26 +62,15 @@ public class CertificateSignerTest {
@Test(expected = IllegalArgumentException.class)
public void extensions_test_subject_alternative_names() throws Exception {
- 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);
+ Pkcs10Csr csr = createCsrBuilder("OU=Vespa")
+ .addSubjectAlternativeName("some.other.domain.tld")
+ .build();
+ CertificateSigner.verifyCertificateExtensions(csr);
}
private void assertCertificateCommonNameException(String subject, String expectedMessage) {
try {
- CertificateSigner.verifyCertificateCommonName(new X500Name(subject), requestersHostname);
+ CertificateSigner.verifyCertificateCommonName(new X500Principal(subject), requestersHostname);
fail("Expected to fail");
} catch (IllegalArgumentException e) {
assertEquals(expectedMessage, e.getMessage());
@@ -103,8 +82,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, certificate.getSigAlgName());
- assertEquals(expectedSubjectName, certificate.getSubjectDN().getName());
+ assertEquals(CertificateSigner.SIGNER_ALGORITHM.getAlgorithmName(), certificate.getSigAlgName());
+ assertEquals(new X500Principal(expectedSubjectName), certificate.getSubjectX500Principal());
assertEquals("CN=" + cfgServerHostname, certificate.getIssuerX500Principal().getName());
Set<String> extensions = Stream.of(certificate.getNonCriticalExtensionOIDs(),
@@ -116,20 +95,11 @@ public class CertificateSignerTest {
certificate.verify(caKeyPair.getPublic());
}
- 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 Pkcs10CsrBuilder createCsrBuilder(String subject) {
+ return Pkcs10CsrBuilder.fromKeypair(new X500Principal(subject), caKeyPair, CertificateSigner.SIGNER_ALGORITHM);
}
private static KeyPair getKeyPair() {
- try {
- return KeyPairGenerator.getInstance("RSA").genKeyPair();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ return KeyUtils.generateKeypair(KeyAlgorithm.RSA);
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java
index 110dbe9c9b3..bdd4a8c0b9d 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java
@@ -7,11 +7,9 @@ import com.yahoo.vespa.hosted.node.admin.component.Environment;
import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresher;
import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
-import java.security.Security;
import java.util.Collections;
import java.util.Optional;
@@ -28,8 +26,6 @@ public class SslConfigServerApiImpl implements ConfigServerApi {
private final Optional<ConfigServerKeyStoreRefresher> keyStoreRefresher;
public SslConfigServerApiImpl(Environment environment) {
- Security.addProvider(new BouncyCastleProvider());
-
this.environment = environment;
// At this point we don't know the state of the keystore, it may not exist at all, or the keystore
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java
index e7148754fde..94bff3c9633 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java
@@ -7,13 +7,9 @@ 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 org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.openssl.PEMParser;
+import com.yahoo.vespa.athenz.tls.X509CertificateUtils;
import java.io.IOException;
-import java.io.StringReader;
-import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
@@ -58,12 +54,7 @@ public class CertificateSerializedPayload {
@Override
public X509Certificate deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
- try (PEMParser pemParser = new PEMParser(new StringReader(jsonParser.getValueAsString()))) {
- X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) pemParser.readObject();
- return new JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to deserialize X509Certificate", e);
- }
+ return X509CertificateUtils.fromPem(jsonParser.getValueAsString());
}
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java
index a9db96c2a77..beb1acd9e5a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java
@@ -3,24 +3,19 @@ package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
import com.yahoo.log.LogLevel;
import com.yahoo.net.HostName;
+import com.yahoo.vespa.athenz.tls.KeyAlgorithm;
import com.yahoo.vespa.athenz.tls.KeyStoreBuilder;
+import com.yahoo.vespa.athenz.tls.KeyUtils;
+import com.yahoo.vespa.athenz.tls.Pkcs10Csr;
+import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder;
+import com.yahoo.vespa.athenz.tls.SignatureAlgorithm;
import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
-import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
-
-import java.io.IOException;
+
+import javax.security.auth.x500.X500Principal;
import java.security.KeyPair;
-import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Clock;
import java.util.concurrent.Executors;
@@ -40,7 +35,7 @@ public class ConfigServerKeyStoreRefresher {
private static final Logger logger = Logger.getLogger(ConfigServerKeyStoreRefresher.class.getName());
private static final String KEY_STORE_ALIAS = "alias";
static final long MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY = 3600;
- static final String SIGNER_ALGORITHM = "SHA256withRSA";
+ static final SignatureAlgorithm SIGNER_ALGORITHM = SignatureAlgorithm.SHA256_WITH_RSA;
static final String CONFIG_SERVER_CERTIFICATE_SIGNING_PATH = "/athenz/v1/provider/sign";
private final ScheduledExecutorService executor;
@@ -101,12 +96,11 @@ public class ConfigServerKeyStoreRefresher {
} while (!executor.isTerminated());
}
- public boolean refreshKeyStoreIfNeeded() throws
- IOException, NoSuchAlgorithmException, OperatorCreationException, CertificateException, KeyStoreException, NoSuchProviderException {
+ public boolean refreshKeyStoreIfNeeded() {
if (!shouldRefreshCertificate()) return false;
KeyPair keyPair = generateKeyPair();
- PKCS10CertificationRequest csr = generateCsr(keyPair, hostname);
+ Pkcs10Csr csr = generateCsr(keyPair, hostname);
X509Certificate certificate = sendCsr(csr);
storeCertificate(keyPair, certificate);
@@ -141,8 +135,7 @@ public class ConfigServerKeyStoreRefresher {
* well before the certificate actually expires so that we have enough time to retry without
* overloading config server.
*/
- private long getSecondsUntilCertificateShouldBeRefreshed()
- throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException, KeyStoreException, IOException {
+ private long getSecondsUntilCertificateShouldBeRefreshed() throws KeyStoreException{
X509Certificate cert = getConfigServerCertificate();
long notBefore = cert.getNotBefore().getTime() / 1000;
long notAfter = cert.getNotAfter().getTime() / 1000;
@@ -152,12 +145,11 @@ public class ConfigServerKeyStoreRefresher {
return Math.max(0, notBefore + thirdOfLifetime - now);
}
- X509Certificate getConfigServerCertificate() throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException, KeyStoreException, IOException {
+ X509Certificate getConfigServerCertificate() throws KeyStoreException {
return (X509Certificate) keyStoreOptions.loadKeyStore().getCertificate(KEY_STORE_ALIAS);
}
- private void storeCertificate(KeyPair keyPair, X509Certificate certificate)
- throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchProviderException {
+ private void storeCertificate(KeyPair keyPair, X509Certificate certificate) {
keyStoreOptions.path.getParent().toFile().mkdirs();
KeyStore keyStore = KeyStoreBuilder.withType(keyStoreOptions.keyStoreType)
@@ -167,7 +159,7 @@ public class ConfigServerKeyStoreRefresher {
keyStoreOptions.storeKeyStore(keyStore);
}
- private X509Certificate sendCsr(PKCS10CertificationRequest csr) {
+ private X509Certificate sendCsr(Pkcs10Csr csr) {
CertificateSerializedPayload certificateSerializedPayload = configServerApi.post(
CONFIG_SERVER_CERTIFICATE_SIGNING_PATH,
new CsrSerializedPayload(csr),
@@ -176,17 +168,12 @@ public class ConfigServerKeyStoreRefresher {
return certificateSerializedPayload.certificate;
}
- static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
- KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
- rsa.initialize(2048);
- return rsa.genKeyPair();
+ static KeyPair generateKeyPair() {
+ return KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
}
- private static PKCS10CertificationRequest generateCsr(KeyPair keyPair, String commonName)
- throws NoSuchAlgorithmException, OperatorCreationException {
- ContentSigner signer = new JcaContentSignerBuilder(SIGNER_ALGORITHM).build(keyPair.getPrivate());
-
- return new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=" + commonName), keyPair.getPublic())
- .build(signer);
+ private static Pkcs10Csr generateCsr(KeyPair keyPair, String commonName) {
+ return Pkcs10CsrBuilder.fromKeypair(new X500Principal("CN=" + commonName), keyPair, SIGNER_ALGORITHM)
+ .build();
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java
index aa83fdc9e22..34b190b546c 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java
@@ -7,12 +7,10 @@ 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 org.bouncycastle.openssl.jcajce.JcaPEMWriter;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
-import org.bouncycastle.util.io.pem.PemObject;
+import com.yahoo.vespa.athenz.tls.Pkcs10Csr;
+import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils;
import java.io.IOException;
-import java.io.StringWriter;
/**
* Contains PEM formatted Certificate Signing Request (CSR)
@@ -23,10 +21,10 @@ import java.io.StringWriter;
public class CsrSerializedPayload {
@JsonProperty("csr") @JsonSerialize(using = CertificateRequestSerializer.class)
- public final PKCS10CertificationRequest csr;
+ public final Pkcs10Csr csr;
@JsonCreator
- public CsrSerializedPayload(@JsonProperty("csr") PKCS10CertificationRequest csr) {
+ public CsrSerializedPayload(@JsonProperty("csr") Pkcs10Csr csr) {
this.csr = csr;
}
@@ -52,15 +50,10 @@ public class CsrSerializedPayload {
'}';
}
- public static class CertificateRequestSerializer extends JsonSerializer<PKCS10CertificationRequest> {
+ public static class CertificateRequestSerializer extends JsonSerializer<Pkcs10Csr> {
@Override
- public void serialize(
- PKCS10CertificationRequest csr, JsonGenerator gen, SerializerProvider serializers) throws IOException {
- try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
- pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getEncoded()));
- pemWriter.flush();
- gen.writeString(stringWriter.toString());
- }
+ public void serialize(Pkcs10Csr csr, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeString(Pkcs10CsrUtils.toPem(csr));
}
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java
index 85684ea3bd4..69aaad210a4 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java
@@ -2,24 +2,19 @@
package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.athenz.tls.X509CertificateBuilder;
import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
-import org.bouncycastle.asn1.x500.X500Name;
-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.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import java.math.BigInteger;
+import javax.security.auth.x500.X500Principal;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Duration;
-import java.util.Date;
+import java.time.Instant;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -141,22 +136,13 @@ public class ConfigServerKeyStoreRefresherTest {
.thenThrow(exception);
}
- private X509Certificate makeCertificate(int serial) throws Exception {
- try {
- KeyPair keyPair = ConfigServerKeyStoreRefresher.generateKeyPair();
- X500Name subject = new X500Name("CN=" + commonName);
- Date notBefore = Date.from(clock.instant());
- Date notAfter = Date.from(clock.instant().plus(certificateExpiration));
-
- JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(subject,
- BigInteger.valueOf(serial), notBefore, notAfter, subject, keyPair.getPublic());
- ContentSigner sigGen = new JcaContentSignerBuilder(ConfigServerKeyStoreRefresher.SIGNER_ALGORITHM)
- .build(keyPair.getPrivate());
- return new JcaX509CertificateConverter()
- .setProvider(new BouncyCastleProvider())
- .getCertificate(certGen.build(sigGen));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ private X509Certificate makeCertificate(int serial) {
+ KeyPair keyPair = ConfigServerKeyStoreRefresher.generateKeyPair();
+ X500Principal subject = new X500Principal("CN=" + commonName);
+ Instant notBefore = clock.instant();
+ Instant notAfter = clock.instant().plus(certificateExpiration);
+
+ return X509CertificateBuilder.fromKeypair(keyPair, subject, notBefore, notAfter, ConfigServerKeyStoreRefresher.SIGNER_ALGORITHM, serial)
+ .build();
}
} \ No newline at end of file
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
index 892a4f266ae..62ac722759a 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
@@ -1,7 +1,6 @@
// 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.identity;
-import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
import com.yahoo.vespa.athenz.api.AthenzDomain;
@@ -9,19 +8,25 @@ import com.yahoo.vespa.athenz.api.AthenzIdentityCertificate;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.tls.AthenzSslContextBuilder;
import com.yahoo.vespa.athenz.tls.KeyStoreType;
+import com.yahoo.vespa.athenz.tls.KeyUtils;
+import com.yahoo.vespa.athenz.tls.X509CertificateUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import static java.util.stream.Collectors.joining;
+
/**
* @author mortent
*/
@@ -63,13 +68,21 @@ public class SiaIdentityProvider extends AbstractComponent implements AthenzIden
}
private SSLContext createIdentitySslContext() {
- X509Certificate certificate = Crypto.loadX509Certificate(Paths.get(path, "certs", String.format("%s.%s.cert.pem", getDomain(),getService())).toFile());
- PrivateKey privateKey = Crypto.loadPrivateKey(Paths.get(path, "keys", String.format("%s.%s.key.pem", getDomain(),getService())).toFile());
+ try {
+ String certPem = Files.lines(Paths.get(path, "certs", String.format("%s.%s.cert.pem", getDomain(), getService())))
+ .collect(joining());
+ X509Certificate certificate = X509CertificateUtils.fromPem(certPem);
+ String keyPem = Files.lines(Paths.get(path, "keys", String.format("%s.%s.key.pem", getDomain(), getService())))
+ .collect(joining());
+ PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(keyPem);
- return new AthenzSslContextBuilder()
- .withTrustStore(new File(trustStorePath), KeyStoreType.JKS)
- .withIdentityCertificate(new AthenzIdentityCertificate(certificate, privateKey))
- .build();
+ return new AthenzSslContextBuilder()
+ .withTrustStore(new File(trustStorePath), KeyStoreType.JKS)
+ .withIdentityCertificate(new AthenzIdentityCertificate(certificate, privateKey))
+ .build();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
private void reloadSslContext() {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/AthenzCredentialsService.java
index b9fb7e94782..e73cd2f2bdd 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/AthenzCredentialsService.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/AthenzCredentialsService.java
@@ -3,8 +3,14 @@ package com.yahoo.vespa.athenz.identityprovider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.container.core.identity.IdentityConfig;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+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 com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils;
+import com.yahoo.vespa.athenz.tls.SignatureAlgorithm;
+import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.KeyPair;
@@ -34,20 +40,20 @@ class AthenzCredentialsService {
}
AthenzCredentials registerInstance() {
- KeyPair keyPair = CryptoUtils.createKeyPair();
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
String rawDocument = identityDocumentService.getSignedIdentityDocument();
SignedIdentityDocument document = parseSignedIdentityDocument(rawDocument);
- PKCS10CertificationRequest csr = CryptoUtils.createCSR(identityConfig.domain(),
- identityConfig.service(),
- document.dnsSuffix,
- document.providerUniqueId,
- keyPair);
+ Pkcs10Csr csr = createCSR(identityConfig.domain(),
+ identityConfig.service(),
+ document.dnsSuffix,
+ document.providerUniqueId,
+ keyPair);
InstanceRegisterInformation instanceRegisterInformation =
new InstanceRegisterInformation(document.providerService,
identityConfig.domain(),
identityConfig.service(),
rawDocument,
- CryptoUtils.toPem(csr));
+ Pkcs10CsrUtils.toPem(csr));
InstanceIdentity instanceIdentity = athenzService.sendInstanceRegisterRequest(instanceRegisterInformation,
document.ztsEndpoint);
return toAthenzCredentials(instanceIdentity, keyPair, document);
@@ -55,13 +61,13 @@ class AthenzCredentialsService {
AthenzCredentials updateCredentials(AthenzCredentials currentCredentials) {
SignedIdentityDocument document = currentCredentials.getIdentityDocument();
- KeyPair newKeyPair = CryptoUtils.createKeyPair();
- PKCS10CertificationRequest csr = CryptoUtils.createCSR(identityConfig.domain(),
- identityConfig.service(),
- document.dnsSuffix,
- document.providerUniqueId,
- newKeyPair);
- InstanceRefreshInformation refreshInfo = new InstanceRefreshInformation(CryptoUtils.toPem(csr));
+ KeyPair newKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
+ Pkcs10Csr csr = createCSR(identityConfig.domain(),
+ identityConfig.service(),
+ document.dnsSuffix,
+ document.providerUniqueId,
+ newKeyPair);
+ InstanceRefreshInformation refreshInfo = new InstanceRefreshInformation(Pkcs10CsrUtils.toPem(csr));
InstanceIdentity instanceIdentity =
athenzService.sendInstanceRefreshRequest(document.providerService,
identityConfig.domain(),
@@ -90,4 +96,22 @@ class AthenzCredentialsService {
}
}
+ private static Pkcs10Csr createCSR(String identityDomain,
+ String identityService,
+ String dnsSuffix,
+ String providerUniqueId,
+ KeyPair keyPair) {
+ X500Principal subject = new X500Principal(String.format("CN=%s.%s", identityDomain, identityService));
+ // Add SAN dnsname <service>.<domain-with-dashes>.<provider-dnsname-suffix>
+ // and SAN dnsname <provider-unique-instance-id>.instanceid.athenz.<provider-dnsname-suffix>
+ return Pkcs10CsrBuilder.fromKeypair(subject, keyPair, SignatureAlgorithm.SHA256_WITH_RSA)
+ .addSubjectAlternativeName(String.format("%s.%s.%s",
+ identityService,
+ identityDomain.replace(".", "-"),
+ dnsSuffix))
+ .addSubjectAlternativeName(String.format("%s.instanceid.athenz.%s",
+ providerUniqueId,
+ dnsSuffix))
+ .build();
+ }
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtils.java
deleted file mode 100644
index 6e74d3bc8b1..00000000000
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtils.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.athenz.identityprovider;
-
-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.cert.X509CertificateHolder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
-import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
-import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
-import org.bouncycastle.util.io.pem.PemObject;
-
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.io.UncheckedIOException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-/**
- * @author bjorncs
- */
-class CryptoUtils {
-
- private static final BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
-
- private CryptoUtils() {}
-
- static KeyPair createKeyPair() {
- try {
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
- return kpg.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- static PKCS10CertificationRequest createCSR(String identityDomain,
- String identityService,
- String dnsSuffix,
- String providerUniqueId,
- KeyPair keyPair) {
- try {
- // Add SAN dnsname <service>.<domain-with-dashes>.<provider-dnsname-suffix>
- // and SAN dnsname <provider-unique-instance-id>.instanceid.athenz.<provider-dnsname-suffix>
- GeneralNames subjectAltNames = new GeneralNames(new GeneralName[]{
- new GeneralName(GeneralName.dNSName, String.format("%s.%s.%s",
- identityService,
- identityDomain.replace(".", "-"),
- dnsSuffix)),
- new GeneralName(GeneralName.dNSName, String.format("%s.instanceid.athenz.%s",
- providerUniqueId,
- dnsSuffix))
- });
-
- ExtensionsGenerator extGen = new ExtensionsGenerator();
- extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
-
- X500Principal subject = new X500Principal(
- String.format("CN=%s.%s", identityDomain, identityService));
-
- PKCS10CertificationRequestBuilder requestBuilder =
- new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic());
- requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
- return requestBuilder.build(new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()));
- } catch (OperatorCreationException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- static String toPem(PKCS10CertificationRequest csr) {
- try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
- pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getEncoded()));
- pemWriter.flush();
- return stringWriter.toString();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- static X509Certificate parseCertificate(String pemEncodedCertificate) {
- try (PEMParser parser = new PEMParser(new StringReader(pemEncodedCertificate))) {
- Object pemObject = parser.readObject();
- if (pemObject instanceof X509Certificate) {
- return (X509Certificate) pemObject;
- }
- if (pemObject instanceof X509CertificateHolder) {
- return new JcaX509CertificateConverter()
- .setProvider(bouncyCastleProvider)
- .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);
- }
- }
-}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/InstanceIdentity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/InstanceIdentity.java
index b90ce56ca7e..899ff7a95d7 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/InstanceIdentity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/InstanceIdentity.java
@@ -8,6 +8,7 @@ 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.X509CertificateUtils;
import java.io.IOException;
import java.security.cert.X509Certificate;
@@ -41,7 +42,7 @@ public class InstanceIdentity {
public static class X509CertificateDeserializer extends JsonDeserializer<X509Certificate> {
@Override
public X509Certificate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
- return CryptoUtils.parseCertificate(parser.getValueAsString());
+ return X509CertificateUtils.fromPem(parser.getValueAsString());
}
}
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/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/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/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/KeyStoreType.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyStoreType.java
index a5c35549540..6c08a60ff5b 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyStoreType.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyStoreType.java
@@ -17,10 +17,8 @@ public enum KeyStoreType {
}
},
PKCS12 {
- private final BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
-
KeyStore createKeystore() throws KeyStoreException {
- return KeyStore.getInstance("PKCS12", bouncyCastleProvider);
+ return KeyStore.getInstance("PKCS12", BouncyCastleProviderHolder.getInstance());
}
};
abstract KeyStore createKeystore() throws GeneralSecurityException;
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..f49e1324ba5
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/KeyUtils.java
@@ -0,0 +1,41 @@
+// 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 com.yahoo.athenz.auth.util.Crypto;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * @author bjorncs
+ */
+public class KeyUtils {
+ private KeyUtils() {}
+
+ public static KeyPair generateKeypair(KeyAlgorithm algorithm, int keySize) {
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm.getAlgorithmName());
+ if (keySize != -1) {
+ keyGen.initialize(keySize);
+ }
+ return keyGen.genKeyPair();
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static KeyPair generateKeypair(KeyAlgorithm algorithm) {
+ return generateKeypair(algorithm, -1);
+ }
+
+ public static PublicKey extractPublicKey(PrivateKey privateKey) {
+ return Crypto.extractPublicKey(privateKey);
+ }
+
+ public static PrivateKey fromPemEncodedPrivateKey(String pem) {
+ return Crypto.loadPrivateKey(pem);
+ }
+}
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..d9cd3141f19
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10Csr.java
@@ -0,0 +1,90 @@
+// 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;
+
+import javax.security.auth.x500.X500Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @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());
+ }
+
+ public List<String> getSubjectAlternativeNames() {
+ return getExtensions()
+ .map(extensions -> GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName))
+ .map(generalNames -> Arrays.stream(generalNames.getNames())
+ .map(Pkcs10Csr::toString)
+ .collect(toList()))
+ .orElse(emptyList());
+ }
+
+ /**
+ * @return If basic constraints extension is present: returns true if CA cert, false otherwise. Returns empty if the extension is not present.
+ */
+ public Optional<Boolean> getBasicConstraints() {
+ return getExtensions()
+ .map(BasicConstraints::fromExtensions)
+ .map(BasicConstraints::isCA);
+ }
+
+ public List<String> getExtensionOIds() {
+ return getExtensions()
+ .map(extensions -> Arrays.stream(extensions.getExtensionOIDs())
+ .map(ASN1ObjectIdentifier::getId)
+ .collect(toList()))
+ .orElse(emptyList());
+
+ }
+
+ private Optional<Extensions> getExtensions() {
+ return Optional.of(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest))
+ .filter(attributes -> attributes.length > 0)
+ .map(attributes -> attributes[0])
+ .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
new file mode 100644
index 00000000000..9c2cd9d4d9f
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrBuilder.java
@@ -0,0 +1,89 @@
+// 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.BasicConstraints;
+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 BasicConstraintsExtension basicConstraintsExtension;
+
+ 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 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));
+ 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..ba22af13fd2
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilder.java
@@ -0,0 +1,145 @@
+// 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.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);
+ }
+ }
+
+}
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..3e1bd3eb31c
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java
@@ -0,0 +1,78 @@
+// 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 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;
+import java.io.UncheckedIOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @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);
+ }
+ }
+
+ public static List<String> getCommonNames(X509Certificate certificate) {
+ return getCommonNames(certificate.getSubjectX500Principal());
+ }
+
+ public static List<String> getCommonNames(X500Principal subject) {
+ try {
+ String subjectPrincipal = subject.getName();
+ return new LdapName(subjectPrincipal).getRdns().stream()
+ .filter(rdn -> rdn.getType().equalsIgnoreCase("cn"))
+ .map(rdn -> rdn.getValue().toString())
+ .collect(toList());
+ } catch (NamingException e) {
+ throw new IllegalArgumentException("Invalid CN: " + e, e);
+ }
+
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/AthenzIdentities.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/AthenzIdentities.java
index fd34263e387..ad2428cc292 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/AthenzIdentities.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/AthenzIdentities.java
@@ -5,10 +5,10 @@ import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.api.AthenzUser;
+import com.yahoo.vespa.athenz.tls.X509CertificateUtils;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapName;
import java.security.cert.X509Certificate;
+import java.util.List;
/**
* @author bjorncs
@@ -53,15 +53,11 @@ public class AthenzIdentities {
}
private static String getCommonName(X509Certificate certificate) {
- try {
- String subjectPrincipal = certificate.getSubjectX500Principal().getName();
- return new LdapName(subjectPrincipal).getRdns().stream()
- .filter(rdn -> rdn.getType().equalsIgnoreCase("cn"))
- .map(rdn -> rdn.getValue().toString())
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException("Could not find CN in certificate: " + subjectPrincipal));
- } catch (NamingException e) {
- throw new IllegalArgumentException("Invalid CN: " + e, e);
+ List<String> commonNames = X509CertificateUtils.getCommonNames(certificate);
+ if (commonNames.size() != 1) {
+ String subjectName = certificate.getSubjectX500Principal().getName();
+ throw new IllegalArgumentException("Expected single CN in certificate's subject: " + subjectName);
}
+ return commonNames.get(0);
}
}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtilsTest.java
deleted file mode 100644
index 353c5d3c504..00000000000
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/CryptoUtilsTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.identityprovider;
-
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.security.KeyPair;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author bjorncs
- */
-public class CryptoUtilsTest {
-
- @Test
- public void certificate_signing_request_is_correct_and_can_be_serialized_to_pem() throws IOException {
- KeyPair keyPair = CryptoUtils.createKeyPair();
- PKCS10CertificationRequest csr = CryptoUtils.createCSR(
- "identity-domain", "identity-service", "vespa.cloud.com", "unique.instance.id", keyPair);
- String pem = CryptoUtils.toPem(csr);
- assertThat(pem, containsString("BEGIN CERTIFICATE REQUEST"));
- assertThat(pem, containsString("END CERTIFICATE REQUEST"));
- }
-
-}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/AthenzSslContextBuilderTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/AthenzSslContextBuilderTest.java
index 20ac8791863..cf1b2168dc8 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/AthenzSslContextBuilderTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/AthenzSslContextBuilderTest.java
@@ -10,7 +10,6 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import static com.yahoo.vespa.athenz.tls.TestUtils.createCertificate;
-import static com.yahoo.vespa.athenz.tls.TestUtils.createKeyPair;
import static com.yahoo.vespa.athenz.tls.TestUtils.createKeystore;
import static com.yahoo.vespa.athenz.tls.TestUtils.createKeystoreFile;
@@ -48,7 +47,7 @@ public class AthenzSslContextBuilderTest {
@Test
public void can_build_sslcontext_with_keystore_from_private_key_and_certificate() throws Exception {
- KeyPair keyPair = createKeyPair();
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048);
X509Certificate certificate = createCertificate(keyPair);
new AthenzSslContextBuilder()
.withKeyStore(keyPair.getPrivate(), certificate)
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyStoreBuilderTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyStoreBuilderTest.java
index 1b6fa8bcbf1..6060f6f3521 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyStoreBuilderTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyStoreBuilderTest.java
@@ -9,7 +9,6 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import static com.yahoo.vespa.athenz.tls.TestUtils.createCertificate;
-import static com.yahoo.vespa.athenz.tls.TestUtils.createKeyPair;
import static com.yahoo.vespa.athenz.tls.TestUtils.createKeystoreFile;
/**
@@ -24,7 +23,7 @@ public class KeyStoreBuilderTest {
@Test
public void can_create_jks_keystore_from_privatekey_and_certificate() throws Exception {
- KeyPair keyPair = createKeyPair();
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 4096);
X509Certificate certificate = createCertificate(keyPair);
KeyStoreBuilder.withType(KeyStoreType.JKS)
.withKeyEntry("key", keyPair.getPrivate(), certificate)
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyUtilsTest.java
new file mode 100644
index 00000000000..a8730a31838
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/KeyUtilsTest.java
@@ -0,0 +1,22 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author bjorncs
+ */
+public class KeyUtilsTest {
+
+ @Test
+ public void can_extract_public_key_from_private() {
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
+ PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate());
+ assertNotNull(publicKey);
+ }
+
+} \ No newline at end of file
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/Pkcs10CsrTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java
new file mode 100644
index 00000000000..bb2e80ba705
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrTest.java
@@ -0,0 +1,55 @@
+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().isPresent());
+ assertTrue(csr.getBasicConstraints().get());
+ }
+
+ @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
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..5b5a57f1fcc
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/Pkcs10CsrUtilsTest.java
@@ -0,0 +1,29 @@
+package com.yahoo.vespa.athenz.tls;
+
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyPair;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @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();
+ String pem = Pkcs10CsrUtils.toPem(csr);
+ Pkcs10Csr deserializedCsr = Pkcs10CsrUtils.fromPem(pem);
+ assertThat(pem, containsString("BEGIN CERTIFICATE REQUEST"));
+ assertThat(pem, containsString("END CERTIFICATE REQUEST"));
+ assertEquals(subject, deserializedCsr.getSubject());
+ }
+
+} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/TestUtils.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/TestUtils.java
index 54601c04514..64f15408313 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/TestUtils.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/TestUtils.java
@@ -1,23 +1,13 @@
// 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 com.yahoo.athenz.auth.util.Crypto;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.pkcs.PKCS10CertificationRequest;
-
-import java.io.BufferedOutputStream;
+import javax.security.auth.x500.X500Principal;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
import java.security.KeyPair;
-import java.security.KeyPairGenerator;
import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import static com.yahoo.vespa.athenz.tls.KeyStoreUtils.writeKeyStoreToFile;
@@ -26,32 +16,22 @@ import static com.yahoo.vespa.athenz.tls.KeyStoreUtils.writeKeyStoreToFile;
*/
class TestUtils {
- static KeyStore createKeystore(KeyStoreType type, char[] password)
- throws GeneralSecurityException, IOException, OperatorCreationException {
- KeyPair keyPair = createKeyPair();
- KeyStore keystore = type.createKeystore();
- keystore.load(null);
- keystore.setKeyEntry("entry-name", keyPair.getPrivate(), password, new Certificate[]{createCertificate(keyPair)});
- return keystore;
- }
-
- static X509Certificate createCertificate(KeyPair keyPair)
- throws OperatorCreationException, IOException {
- String x500Principal = "CN=mysubject";
- PKCS10CertificationRequest csr =
- Crypto.getPKCS10CertRequest(
- Crypto.generateX509CSR(keyPair.getPrivate(), x500Principal, null));
- return Crypto.generateX509Certificate(csr, keyPair.getPrivate(), new X500Name(x500Principal), 3600, false);
+ static KeyStore createKeystore(KeyStoreType type, char[] password) {
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 4096);
+ return KeyStoreBuilder.withType(type)
+ .withKeyEntry("entry-name", keyPair.getPrivate(), password, createCertificate(keyPair))
+ .build();
}
- static KeyPair createKeyPair() throws NoSuchAlgorithmException {
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
- keyGen.initialize(4096);
- return keyGen.genKeyPair();
+ static X509Certificate createCertificate(KeyPair keyPair) {
+ X500Principal subject = new X500Principal("CN=mysubject");
+ return X509CertificateBuilder
+ .fromKeypair(
+ keyPair, subject, Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA256_WITH_RSA, 1)
+ .build();
}
- static void createKeystoreFile(File file, KeyStoreType type, char[] password)
- throws IOException, GeneralSecurityException, OperatorCreationException {
+ static void createKeystoreFile(File file, KeyStoreType type, char[] password) {
writeKeyStoreToFile(createKeystore(type, password), file, password);
}
}
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..81ff4fdb208
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateBuilderTest.java
@@ -0,0 +1,58 @@
+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 org.junit.Assert.assertEquals;
+
+/**
+ * @author bjorncs
+ */
+public class X509CertificateBuilderTest {
+
+ @Test
+ public void can_build_self_signed_certificate() throws NoSuchAlgorithmException {
+ 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)
+ .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