From 75201698983e22570805d1e4e697575ebcd7fb99 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Mon, 6 Aug 2018 16:45:22 +0200 Subject: Remove api for issuing self-signed certificates --- .../ca/CertificateSerializedPayload.java | 59 ----------- .../ca/CertificateSigner.java | 115 --------------------- .../ca/CertificateSignerResource.java | 65 ------------ .../ca/CsrSerializedPayload.java | 59 ----------- .../ca/CertificateSignerTest.java | 105 ------------------- .../ca/CsrSerializedPayloadTest.java | 33 ------ 6 files changed, 436 deletions(-) delete mode 100644 athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java delete mode 100644 athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java delete mode 100644 athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java delete mode 100644 athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java delete mode 100644 athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java delete mode 100644 athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayloadTest.java (limited to 'athenz-identity-provider-service') 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 deleted file mode 100644 index cfef2bc0e33..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSerializedPayload.java +++ /dev/null @@ -1,59 +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.hosted.athenz.instanceproviderservice.ca; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.yahoo.vespa.athenz.tls.X509CertificateUtils; - -import java.io.IOException; -import java.security.cert.X509Certificate; - -/** - * Contains PEM formatted signed certificate - * - * @author freva - */ -public class CertificateSerializedPayload { - - @JsonProperty("certificate") @JsonSerialize(using = CertificateSerializer.class) - public final X509Certificate certificate; - - @JsonCreator - public CertificateSerializedPayload(@JsonProperty("certificate") X509Certificate certificate) { - this.certificate = certificate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - CertificateSerializedPayload that = (CertificateSerializedPayload) o; - - return certificate.equals(that.certificate); - } - - @Override - public int hashCode() { - return certificate.hashCode(); - } - - @Override - public String toString() { - return "CertificateSerializedPayload{" + - "certificate='" + certificate + '\'' + - '}'; - } - - public static class CertificateSerializer extends JsonSerializer { - @Override - public void serialize( - X509Certificate certificate, JsonGenerator gen, SerializerProvider serializers) throws IOException { - 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 deleted file mode 100644 index 7b4a599d5dd..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java +++ /dev/null @@ -1,115 +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.hosted.athenz.instanceproviderservice.ca; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Inject; -import com.yahoo.cloud.config.ConfigserverConfig; -import com.yahoo.config.provision.Zone; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.tls.Extension; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.SignatureAlgorithm; -import com.yahoo.vespa.athenz.tls.X509CertificateBuilder; -import com.yahoo.vespa.athenz.tls.X509CertificateUtils; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; - -import javax.security.auth.x500.X500Principal; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.getZoneConfig; - - -/** - * Signs Certificate Signing Reqest from tenant nodes. This certificate will be used - * by nodes to authenticate themselves when performing operations against the config - * server, such as updating node-repository or orchestrator. - * - * @author freva - */ -public class CertificateSigner { - - private static final Logger log = Logger.getLogger(CertificateSigner.class.getName()); - - static final SignatureAlgorithm SIGNER_ALGORITHM = SignatureAlgorithm.SHA256_WITH_RSA; - static final Duration CERTIFICATE_EXPIRATION = Duration.ofDays(30); - private static final List ILLEGAL_EXTENSIONS = ImmutableList.of( - Extension.BASIC_CONSTRAINS, Extension.SUBJECT_ALTERNATIVE_NAMES); - - private final PrivateKey caPrivateKey; - private final X500Principal issuer; - private final Clock clock; - - @Inject - public CertificateSigner(KeyProvider keyProvider, - ConfigserverConfig configserverConfig, - AthenzProviderServiceConfig config, - Zone zone) { - this(getPrivateKey(keyProvider, config, zone), configserverConfig.loadBalancerAddress(), Clock.systemUTC()); - } - - CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) { - this.caPrivateKey = caPrivateKey; - this.issuer = new X500Principal("CN=" + loadBalancerAddress); - this.clock = clock; - } - - /** - * Signs the CSR if: - *
    - *
  • Common Name matches {@code remoteHostname}
  • - *
  • CSR does not contain any any of the extensions in {@code ILLEGAL_EXTENSIONS}
  • - *
- */ - X509Certificate generateX509Certificate(Pkcs10Csr csr, String remoteHostname) { - verifyCertificateCommonName(csr.getSubject(), remoteHostname); - verifyCertificateExtensions(csr); - - Instant now = clock.instant(); - try { - 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", ex); - } - } - - static void verifyCertificateCommonName(X500Principal subject, String remoteHostname) { - List commonNames = X509CertificateUtils.getCommonNames(subject); - if (commonNames.size() != 1) { - throw new IllegalArgumentException("Only 1 common name should be set"); - } - - String actualCommonName = commonNames.get(0); - if (! actualCommonName.equals(remoteHostname)) { - throw new IllegalArgumentException("Remote hostname " + remoteHostname + - " does not match common name " + actualCommonName); - } - } - - @SuppressWarnings("unchecked") - static void verifyCertificateExtensions(Pkcs10Csr csr) { - List extensionOIds = csr.getExtensionOIds(); - List 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)); - } - } - - private static PrivateKey getPrivateKey(KeyProvider keyProvider, AthenzProviderServiceConfig config, Zone zone) { - AthenzProviderServiceConfig.Zones zoneConfig = getZoneConfig(config, zone); - return keyProvider.getPrivateKey(zoneConfig.secretVersion()); - } -} 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 deleted file mode 100644 index 1dd452866a5..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerResource.java +++ /dev/null @@ -1,65 +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.hosted.athenz.instanceproviderservice.ca; - -import com.google.inject.Inject; -import com.yahoo.container.jaxrs.annotation.Component; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.BadRequestException; -import javax.ws.rs.Consumes; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; -import java.util.logging.Logger; - -/** - * @author bjorncs - * @author freva - */ -@Path("/sign") -public class CertificateSignerResource { - - private static final Logger log = Logger.getLogger(CertificateSignerResource.class.getName()); - - private final CertificateSigner certificateSigner; - - @Inject - public CertificateSignerResource(@Component CertificateSigner certificateSigner) { - this.certificateSigner = certificateSigner; - } - - @POST - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - public CertificateSerializedPayload generateCertificate(CsrSerializedPayload csrPayload, - @Context HttpServletRequest req) { - try { - InetAddress addr = InetAddress.getByName(req.getRemoteAddr()); - String remoteHostname = addr.getHostName(); - Pkcs10Csr csr = csrPayload.csr; - log.log(LogLevel.DEBUG, "Certification request from " + remoteHostname + ": " + csr); - X509Certificate certificate = certificateSigner.generateX509Certificate(csr, remoteHostname); - return new CertificateSerializedPayload(certificate); - } catch (IllegalArgumentException e) { - log.log(LogLevel.WARNING, e.getMessage()); - throw new ForbiddenException(e.getMessage(), e); - } catch (RuntimeException e) { - log.log(LogLevel.ERROR, e.getMessage(), e); - throw new InternalServerErrorException(e.getMessage(), e); - } catch (UnknownHostException e) { - String message = "Failed to resolve remote address " + req.getRemoteAddr() + - ", must resolve to match value in Common Name"; - log.log(LogLevel.ERROR, message); - throw new BadRequestException(message); - } - } -} 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 deleted file mode 100644 index 375a4c3e17d..00000000000 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayload.java +++ /dev/null @@ -1,59 +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.hosted.athenz.instanceproviderservice.ca; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; - -import java.io.IOException; - -/** - * Contains PEM formatted Certificate Signing Request (CSR) - * - * @author freva - */ -public class CsrSerializedPayload { - - @JsonProperty("csr") public final Pkcs10Csr csr; - - @JsonCreator - public CsrSerializedPayload(@JsonProperty("csr") @JsonDeserialize(using = CertificateRequestDeserializer.class) - Pkcs10Csr csr) { - this.csr = csr; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - CsrSerializedPayload that = (CsrSerializedPayload) o; - - return csr.equals(that.csr); - } - - @Override - public int hashCode() { - return csr.hashCode(); - } - - @Override - public String toString() { - return "CsrSerializedPayload{" + - "csr='" + csr + '\'' + - '}'; - } - - public static class CertificateRequestDeserializer extends JsonDeserializer { - @Override - public Pkcs10Csr deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - return Pkcs10CsrUtils.fromPem(jsonParser.getValueAsString()); - } - } -} 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 deleted file mode 100644 index 6c624eb1da0..00000000000 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerTest.java +++ /dev/null @@ -1,105 +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.hosted.athenz.instanceproviderservice.ca; - -import com.yahoo.test.ManualClock; -import com.yahoo.vespa.athenz.tls.Extension; -import com.yahoo.vespa.athenz.tls.KeyAlgorithm; -import com.yahoo.vespa.athenz.tls.KeyUtils; -import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder; -import org.junit.Test; - -import javax.security.auth.x500.X500Principal; -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -/** - * @author freva - */ -public class CertificateSignerTest { - - private final long startTime = 1234567890000L; - private final KeyPair caKeyPair = getKeyPair(); - private final String cfgServerHostname = "cfg1.us-north-1.vespa.domain.tld"; - private final ManualClock clock = new ManualClock(Instant.ofEpochMilli(startTime)); - private final CertificateSigner signer = new CertificateSigner(caKeyPair.getPrivate(), cfgServerHostname, clock); - - private final String requestersHostname = "tenant-123.us-north-1.vespa.domain.tld"; - - @Test - public void test_signing() throws Exception { - String subject = String.format("CN=%s,OU=Vespa,C=NO", requestersHostname); - Pkcs10Csr csr = createCsrBuilder(subject).build(); - - 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 X500Principal("CN=" + requestersHostname), requestersHostname); - CertificateSigner.verifyCertificateCommonName( - new X500Principal("C=NO,OU=Vespa,CN=" + requestersHostname), requestersHostname); - CertificateSigner.verifyCertificateCommonName( - 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"); - assertCertificateCommonNameException("CN=evil.hostname.domain.tld", - "Remote hostname tenant-123.us-north-1.vespa.domain.tld does not match common name evil.hostname.domain.tld"); - } - - @Test(expected = IllegalArgumentException.class) - public void extensions_test_subject_alternative_names() throws Exception { - Pkcs10Csr csr = createCsrBuilder("OU=Vespa") - .addSubjectAlternativeName("some.other.domain.tld") - .build(); - CertificateSigner.verifyCertificateExtensions(csr); - } - - private void assertCertificateCommonNameException(String subject, String expectedMessage) { - try { - CertificateSigner.verifyCertificateCommonName(new X500Principal(subject), requestersHostname); - fail("Expected to fail"); - } catch (IllegalArgumentException e) { - assertEquals(expectedMessage, e.getMessage()); - } - } - - private void assertCertificate(X509Certificate certificate, String expectedSubjectName, Set expectedExtensions) throws Exception { - assertEquals(3, certificate.getVersion()); - assertEquals(BigInteger.valueOf(startTime), certificate.getSerialNumber()); - assertEquals(startTime, certificate.getNotBefore().getTime()); - assertEquals(startTime + CertificateSigner.CERTIFICATE_EXPIRATION.toMillis(), certificate.getNotAfter().getTime()); - assertEquals(CertificateSigner.SIGNER_ALGORITHM.getAlgorithmName(), certificate.getSigAlgName()); - assertEquals(new X500Principal(expectedSubjectName), certificate.getSubjectX500Principal()); - assertEquals("CN=" + cfgServerHostname, certificate.getIssuerX500Principal().getName()); - - Set extensions = Stream.of(certificate.getNonCriticalExtensionOIDs(), - certificate.getCriticalExtensionOIDs()) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - assertEquals(expectedExtensions, extensions); - - certificate.verify(caKeyPair.getPublic()); - } - - private Pkcs10CsrBuilder createCsrBuilder(String subject) { - return Pkcs10CsrBuilder.fromKeypair(new X500Principal(subject), caKeyPair, CertificateSigner.SIGNER_ALGORITHM); - } - - private static KeyPair getKeyPair() { - return KeyUtils.generateKeypair(KeyAlgorithm.RSA); - } -} diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayloadTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayloadTest.java deleted file mode 100644 index b12ef70b1dc..00000000000 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CsrSerializedPayloadTest.java +++ /dev/null @@ -1,33 +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.hosted.athenz.instanceproviderservice.ca; - -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils; -import org.junit.Test; - -import java.io.IOException; - -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; - -/** - * @author bjorncs - */ -public class CsrSerializedPayloadTest { - - @Test - public void it_can_be_deserialized() throws IOException { - String serialized = "{\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICVDCCATwCAQAwDzENMAsGA1UEAwwEdGV" + - "zdDCCASIwDQYJKoZIhvcNAQEBBQAD\\nggEPADCCAQoCggEBAL7xra4De9B54yY6lw8Ka/lt7lDEKQRp42RYzpXjHIQXFgr8" + - "\\n+EvJCLEldFoqfOm728KAWQq/8YdFR4hBwOz8Rr8khJKMBCQ2DWvGYz2705nr3j3v\\nsd3RE5i8n8cUdKiHRuOf305xgy" + - "970TFb+s5/tQOfDMDfvC/BdHNhB4pc0P04CVs/\\nzusKvghdSXFVufAuVaY30ZyviqrDVlBZnI158MmRzfINwP70ZYn5wsq" + - "crKzgSUBp\\nH/WjxaklSzGOH8Uk/EKVx0luzAxtTU8jO7MU1+EG8H4E+FI9ijdjftYyko5UAOQO\\nJGiI9/qHJIMVOIcQa" + - "k1PA5+2/0NbtVxihQi/uJcCAwEAAaAAMA0GCSqGSIb3DQEB\\nCwUAA4IBAQAelFvM6PyDFufv9pNmFigNqOO+r8ats9Xak9" + - "JVtGERo9KFcNDAkawD\\nMPzWQeB87oPnB5dlSdkI2J/jIV7/zR9Qoa2qZlKeL4vUIvfMTj5EOmQLn4ofoBwa\\n50D8Ro3D" + - "06Ohb1KE3seOK2FfVybiATpoaICCjb0ibhx4lNsJGZXpw6F2OdTRi8Fb\\n7kfgLiLPCH+UiHDeVnjVVr/PUKeSImgv44mb4" + - "c6EU29MYkM4LxCY9/c4scG7Pq+s\\nuHU5Tepjsnmkdtip5NzS7csPXENEygKyksPHWFFojPrtF6nFkMzzIPUgKbsmm4+H\\" + - "nfJihCYL3pc3+bVYl87TIcdohJ1GYvfw7\\n-----END CERTIFICATE REQUEST-----\\n\"}"; - CsrSerializedPayload csrSerializedPayload = Utils.getMapper().readValue(serialized, CsrSerializedPayload.class); - assertThat(csrSerializedPayload.csr, notNullValue()); - } - -} -- cgit v1.2.3