diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-06-14 12:50:12 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-06-15 13:34:32 +0200 |
commit | bdeeac4080ae3e36a6c1979d8fa39355f92d251c (patch) | |
tree | 0261500cdf589a3f1c464dd11ee777ad7a76c268 /vespa-athenz | |
parent | 88fd17919d10638cccd8efa71ed1916a855f88eb (diff) |
Add method to ZtsClient to retrieve identity certificate
Diffstat (limited to 'vespa-athenz')
18 files changed, 323 insertions, 57 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java index dd97b20055d..f8654bbaa68 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java @@ -10,12 +10,15 @@ import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.api.ZToken; +import com.yahoo.vespa.athenz.client.zts.bindings.IdentityResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceIdentityCredentials; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRefreshInformation; +import com.yahoo.vespa.athenz.client.zts.bindings.IdentityRefreshRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRegisterInformation; import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.RoleTokenResponseEntity; +import com.yahoo.vespa.athenz.client.zts.utils.IdentityCsrGenerator; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder; @@ -127,6 +130,27 @@ public class DefaultZtsClient implements ZtsClient { } @Override + public Identity getServiceIdentity(AthenzService identity, String keyId, Pkcs10Csr csr) { + URI uri = ztsUrl.resolve(String.format("instance/%s/%s/refresh", identity.getDomainName(), identity.getName())); + HttpUriRequest request = RequestBuilder.post() + .setUri(uri) + .setEntity(toJsonStringEntity(new IdentityRefreshRequestEntity(csr, keyId))) + .build(); + return withClient(client -> { + try (CloseableHttpResponse response = client.execute(request)) { + IdentityResponseEntity entity = readEntity(response, IdentityResponseEntity.class); + return new Identity(entity.certificate(), entity.caCertificateBundle()); + } + }); + } + + @Override + public Identity getServiceIdentity(AthenzService identity, String keyId, KeyPair keyPair, String dnsSuffix) { + Pkcs10Csr csr = new IdentityCsrGenerator(dnsSuffix).generateIdentityCsr(identity, keyPair); + return getServiceIdentity(identity, keyId, csr); + } + + @Override public ZToken getRoleToken(AthenzDomain domain) { return getRoleToken(domain, null); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/Identity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/Identity.java new file mode 100644 index 00000000000..455f3c06d1d --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/Identity.java @@ -0,0 +1,29 @@ +// 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.client.zts; + +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * The identity of a service + * + * @author bjorncs + */ +public class Identity { + + private final X509Certificate certificate; + private final List<X509Certificate> caCertificates; + + public Identity(X509Certificate certificate, List<X509Certificate> caCertificates) { + this.certificate = certificate; + this.caCertificates = caCertificates; + } + + public X509Certificate certificate() { + return certificate; + } + + public List<X509Certificate> caCertificates() { + return caCertificates; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java index 87a204095d9..9502deca1c0 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java @@ -43,6 +43,25 @@ public interface ZtsClient extends AutoCloseable { Pkcs10Csr csr); /** + * Get service identity + * + * @return A x509 certificate with CA certificates + */ + Identity getServiceIdentity(AthenzService identity, + String keyId, + Pkcs10Csr csr); + + /** + * Get service identity + * + * @return A x509 certificate with CA certificates + */ + Identity getServiceIdentity(AthenzService identity, + String keyId, + KeyPair keyPair, + String dnsSuffix); + + /** * Fetch a role token for the target domain * * @param domain Target domain diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java new file mode 100644 index 00000000000..47ae9cd2d3f --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java @@ -0,0 +1,24 @@ +// 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.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.Pkcs10CsrSerializer; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; + +/** + * @author bjorncs + */ +public class IdentityRefreshRequestEntity { + + @JsonProperty("csr") @JsonSerialize(using = Pkcs10CsrSerializer.class) + private final Pkcs10Csr csr; + + @JsonProperty("keyId") + private final String keyId; + + public IdentityRefreshRequestEntity(Pkcs10Csr csr, String keyId) { + this.csr = csr; + this.keyId = keyId; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java new file mode 100644 index 00000000000..7bd04362599 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java @@ -0,0 +1,40 @@ +// 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.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateListDeserializer; + +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Identity response entity + * + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class IdentityResponseEntity { + + private final X509Certificate certificate; + private final List<X509Certificate> caCertificateBundle; + + @JsonCreator + public IdentityResponseEntity( + @JsonProperty("certificate") @JsonDeserialize(using = X509CertificateDeserializer.class) X509Certificate certificate, + @JsonProperty("caCertBundle") @JsonDeserialize(using = X509CertificateListDeserializer.class) List<X509Certificate> caCertificateBundle) { + this.certificate = certificate; + this.caCertificateBundle = caCertificateBundle; + } + + public X509Certificate certificate() { + return certificate; + } + + public List<X509Certificate> caCertificateBundle() { + return caCertificateBundle; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java index 5c265f14813..0ab697a1c4c 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java @@ -4,13 +4,9 @@ package com.yahoo.vespa.athenz.client.zts.bindings; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; 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.X509CertificateUtils; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer; -import java.io.IOException; import java.security.cert.X509Certificate; /** @@ -39,11 +35,4 @@ public class InstanceIdentityCredentials { return serviceToken; } - public static class X509CertificateDeserializer extends JsonDeserializer<X509Certificate> { - @Override - public X509Certificate deserialize(JsonParser parser, DeserializationContext context) throws IOException { - return X509CertificateUtils.fromPem(parser.getValueAsString()); - } - } - } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java index 6c956ddb410..b842ef43500 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java @@ -4,8 +4,9 @@ package com.yahoo.vespa.athenz.client.zts.bindings; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.Pkcs10CsrSerializer; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; /** * @author bjorncs @@ -15,13 +16,14 @@ import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; public class InstanceRefreshInformation { @JsonProperty("csr") - private final String csr; + @JsonSerialize(using = Pkcs10CsrSerializer.class) + private final Pkcs10Csr csr; @JsonProperty("token") private final boolean requestServiceToken; public InstanceRefreshInformation(Pkcs10Csr csr, boolean requestServiceToken) { - this.csr = Pkcs10CsrUtils.toPem(csr); + this.csr = csr; this.requestServiceToken = requestServiceToken; } } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java index 9c56e5a60d6..f329ffbbd2c 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java @@ -8,8 +8,8 @@ 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.client.zts.bindings.serializers.Pkcs10CsrSerializer; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; -import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; import java.io.IOException; import java.time.Duration; @@ -20,7 +20,7 @@ import java.time.Duration; @JsonIgnoreProperties(ignoreUnknown = true) public class RoleCertificateRequestEntity { @JsonProperty("csr") - @JsonSerialize(using = CsrSerializer.class) + @JsonSerialize(using = Pkcs10CsrSerializer.class) public final Pkcs10Csr csr; @JsonProperty("expiryTime") @@ -33,15 +33,6 @@ public class RoleCertificateRequestEntity { this.expiryTime = expiryTime; } - public static class CsrSerializer extends JsonSerializer<Pkcs10Csr> { - @Override - public void serialize(Pkcs10Csr csr, - JsonGenerator jsonGenerator, - SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeString(Pkcs10CsrUtils.toPem(csr)); - } - } - public static class ExpirySerializer extends JsonSerializer<Duration> { @Override public void serialize(Duration duration, diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java index 1b4bd463392..e80f5626843 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java @@ -4,13 +4,9 @@ package com.yahoo.vespa.athenz.client.zts.bindings; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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.X509CertificateUtils; +import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer; -import java.io.IOException; import java.security.cert.X509Certificate; import java.time.Instant; @@ -28,11 +24,4 @@ public class RoleCertificateResponseEntity { this.certificate = certificate; this.expiry = expiry; } - - public static class X509CertificateDeserializer extends JsonDeserializer<X509Certificate> { - @Override - public X509Certificate deserialize(JsonParser parser, DeserializationContext context) throws IOException { - return X509CertificateUtils.fromPem(parser.getValueAsString()); - } - } } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java new file mode 100644 index 00000000000..24825792953 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java @@ -0,0 +1,20 @@ +// 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.client.zts.bindings.serializers; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrUtils; + +import java.io.IOException; + +/** + * @author bjorncs + */ +public class Pkcs10CsrSerializer extends JsonSerializer<Pkcs10Csr> { + @Override + public void serialize(Pkcs10Csr csr, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(Pkcs10CsrUtils.toPem(csr)); + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java new file mode 100644 index 00000000000..5dd6ceb16b4 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java @@ -0,0 +1,21 @@ +// 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.client.zts.bindings.serializers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.yahoo.vespa.athenz.tls.X509CertificateUtils; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +/** + * @author bjorncs + */ +public class X509CertificateDeserializer extends JsonDeserializer<X509Certificate> { + @Override + public X509Certificate deserialize(JsonParser parser, DeserializationContext context) throws IOException { + return X509CertificateUtils.fromPem(parser.getValueAsString()); + } +} + diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.java new file mode 100644 index 00000000000..c496031c116 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.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.client.zts.bindings.serializers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.yahoo.vespa.athenz.tls.X509CertificateUtils; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * @author bjorncs + */ +public class X509CertificateListDeserializer extends JsonDeserializer<List<X509Certificate>> { + + @Override + public List<X509Certificate> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { + return X509CertificateUtils.certificateListFromPem(parser.getValueAsString()); + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java new file mode 100644 index 00000000000..4c442617494 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.vespa.athenz.client.zts.bindings.serializers; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/IdentityCsrGenerator.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/IdentityCsrGenerator.java new file mode 100644 index 00000000000..2f152fafba8 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/IdentityCsrGenerator.java @@ -0,0 +1,37 @@ +// 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.client.zts.utils; + +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.client.zts.ZtsClient; +import com.yahoo.vespa.athenz.tls.Pkcs10Csr; +import com.yahoo.vespa.athenz.tls.Pkcs10CsrBuilder; + +import javax.security.auth.x500.X500Principal; +import java.security.KeyPair; + +import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA; + +/** + * Generates a {@link Pkcs10Csr} instance for use with {@link ZtsClient#getServiceIdentity(AthenzService, String, Pkcs10Csr)} + * + * @author bjorncs + */ +public class IdentityCsrGenerator { + + private final String dnsSuffix; + + public IdentityCsrGenerator(String dnsSuffix) { + this.dnsSuffix = dnsSuffix; + } + + public Pkcs10Csr generateIdentityCsr(AthenzService identity, KeyPair keypair) { + return Pkcs10CsrBuilder.fromKeypair(new X500Principal("CN=" + identity.getFullName()), keypair, SHA256_WITH_RSA) + .addSubjectAlternativeName(String.format( + "%s.%s.%s", + identity.getName(), + identity.getDomainName().replace(".", "-"), + dnsSuffix)) + .build(); + } + +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/package-info.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/package-info.java new file mode 100644 index 00000000000..baca71bc187 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/utils/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.vespa.athenz.client.zts.utils; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java index 6ba094ff275..d96ed17765c 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/tls/X509CertificateUtils.java @@ -21,6 +21,7 @@ import java.io.UncheckedIOException; import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -36,16 +37,22 @@ public class 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); + return toX509Certificate(parser.readObject()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (CertificateException e) { + throw new RuntimeException(e); + } + } + + public static List<X509Certificate> certificateListFromPem(String pem) { + try (PEMParser parser = new PEMParser(new StringReader(pem))) { + List<X509Certificate> list = new ArrayList<>(); + Object pemObject; + while ((pemObject = parser.readObject()) != null) { + list.add(toX509Certificate(pemObject)); } - throw new IllegalArgumentException("Invalid type of PEM object: " + pemObject); + return list; } catch (IOException e) { throw new UncheckedIOException(e); } catch (CertificateException e) { @@ -53,6 +60,18 @@ public class X509CertificateUtils { } } + private static X509Certificate toX509Certificate(Object pemObject) throws CertificateException { + 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); + } + public static String toPem(X509Certificate certificate) { try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); @@ -65,6 +84,20 @@ public class X509CertificateUtils { } } + public static String toPem(List<X509Certificate> certificates) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + for (X509Certificate certificate : certificates) { + 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> getSubjectCommonNames(X509Certificate certificate) { return getCommonNames(certificate.getSubjectX500Principal()); } 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 64f15408313..2a9b54f9e9e 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 @@ -24,7 +24,10 @@ class TestUtils { } static X509Certificate createCertificate(KeyPair keyPair) { - X500Principal subject = new X500Principal("CN=mysubject"); + return createCertificate(keyPair, new X500Principal("CN=mysubject")); + } + + static X509Certificate createCertificate(KeyPair keyPair, X500Principal subject) { return X509CertificateBuilder .fromKeypair( keyPair, subject, Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA256_WITH_RSA, 1) diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java index 718c0e88972..4039bf36a5f 100644 --- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/tls/X509CertificateUtilsTest.java @@ -7,6 +7,7 @@ import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.List; import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME; @@ -24,15 +25,7 @@ public class X509CertificateUtilsTest { 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(); + X509Certificate cert = TestUtils.createCertificate(keypair, subject); assertEquals(subject, cert.getSubjectX500Principal()); String pem = X509CertificateUtils.toPem(cert); assertThat(pem, containsString("BEGIN CERTIFICATE")); @@ -41,6 +34,20 @@ public class X509CertificateUtilsTest { assertEquals(subject, deserializedCert.getSubjectX500Principal()); } + @Test + public void can_deserialize_serialized_pem_certificate_list() { + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 2048); + X500Principal subject1 = new X500Principal("CN=myservice"); + X509Certificate cert1 = TestUtils.createCertificate(keypair, subject1); + X500Principal subject2 = new X500Principal("CN=myservice"); + X509Certificate cert2 = TestUtils.createCertificate(keypair, subject2); + List<X509Certificate> certificateList = Arrays.asList(cert1, cert2); + String pem = X509CertificateUtils.toPem(certificateList); + List<X509Certificate> deserializedCertificateList = X509CertificateUtils.certificateListFromPem(pem); + assertEquals(2, certificateList.size()); + assertEquals(subject1, deserializedCertificateList.get(0).getSubjectX500Principal()); + assertEquals(subject2, deserializedCertificateList.get(1).getSubjectX500Principal()); + } @Test public void can_list_subject_alternative_names() { |