diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-06-12 18:19:28 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-06-12 18:19:28 +0200 |
commit | a2989608d5ddee1ce6ad870ce2aeab14495d96e9 (patch) | |
tree | 03f15ceedb5afc42092e6dd1b60fbb795cdebab3 | |
parent | d8d316de3028e101140bff177ffa6ce66c17524e (diff) |
Separate generating and validating signature to separate class
- Move signature logic to IdentityDocumentSigner
- Stop using fields from deprecated IdentityDocument to generate signature
7 files changed, 154 insertions, 70 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java index 5fff85f695d..0abbb5a64f5 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java @@ -5,11 +5,11 @@ import com.google.inject.Inject; import com.yahoo.config.provision.Zone; import com.yahoo.net.HostName; import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; +import com.yahoo.vespa.athenz.identityprovider.client.IdentityDocumentSigner; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils; @@ -19,9 +19,7 @@ import com.yahoo.vespa.hosted.provision.node.Allocation; import java.net.URI; import java.security.PrivateKey; -import java.security.Signature; import java.time.Instant; -import java.util.Base64; import java.util.HashSet; import java.util.Set; @@ -33,6 +31,7 @@ import java.util.Set; */ public class IdentityDocumentGenerator { + private final IdentityDocumentSigner signer = new IdentityDocumentSigner(); private final NodeRepository nodeRepository; private final Zone zone; private final KeyProvider keyProvider; @@ -53,16 +52,13 @@ public class IdentityDocumentGenerator { Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname)); try { IdentityDocument identityDocument = generateIdDocument(node, identityType); - String identityDocumentString = Utils.getMapper().writeValueAsString(EntityBindingsMapper.toIdentityDocumentEntity(identityDocument)); - - String encodedIdentityDocument = - Base64.getEncoder().encodeToString(identityDocumentString.getBytes()); - Signature sigGenerator = Signature.getInstance("SHA512withRSA"); PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); - sigGenerator.initSign(privateKey); - sigGenerator.update(encodedIdentityDocument.getBytes()); - String signature = Base64.getEncoder().encodeToString(sigGenerator.sign()); + AthenzService providerService = new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()); + + String signature = signer.generateSignature( + identityDocument.providerUniqueId(), providerService, identityDocument.configServerHostname(), + identityDocument.instanceHostname(), identityDocument.createdAt(), identityDocument.ipAddresses(), identityType, privateKey); return new SignedIdentityDocument( identityDocument, @@ -70,7 +66,7 @@ public class IdentityDocumentGenerator { SignedIdentityDocument.DEFAULT_KEY_VERSION, identityDocument.providerUniqueId(), toZoneDnsSuffix(zone, zoneConfig.certDnsSuffix()), - new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()), + providerService, URI.create(zoneConfig.ztsUrl()), SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION, identityDocument.configServerHostname(), diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java index 0201c46b253..b75f7d05394 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java @@ -11,15 +11,10 @@ import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; +import com.yahoo.vespa.athenz.identityprovider.client.IdentityDocumentSigner; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.util.Base64; import java.util.Optional; import java.util.logging.Logger; @@ -35,6 +30,7 @@ public class InstanceValidator { static final String SERVICE_PROPERTIES_DOMAIN_KEY = "identity.domain"; static final String SERVICE_PROPERTIES_SERVICE_KEY = "identity.service"; + private final IdentityDocumentSigner signer = new IdentityDocumentSigner(); private final KeyProvider keyProvider; private final SuperModelProvider superModelProvider; @@ -55,7 +51,9 @@ public class InstanceValidator { } log.log(LogLevel.INFO, () -> String.format("Validating instance %s.", providerUniqueId)); - if (isInstanceSignatureValid(instanceConfirmation)) { + + PublicKey publicKey = keyProvider.getPublicKey(signedIdentityDocument.signingKeyVersion()); + if (signer.hasValidSignature(signedIdentityDocument, publicKey)) { log.log(LogLevel.INFO, () -> String.format("Instance %s is valid.", providerUniqueId)); return true; } @@ -63,24 +61,6 @@ public class InstanceValidator { return false; } - boolean isInstanceSignatureValid(InstanceConfirmation instanceConfirmation) { - SignedIdentityDocumentEntity signedIdentityDocument = instanceConfirmation.signedIdentityDocument; - - PublicKey publicKey = keyProvider.getPublicKey(signedIdentityDocument.signingKeyVersion); - return isSignatureValid(publicKey, signedIdentityDocument.rawIdentityDocument, signedIdentityDocument.signature); - } - - public static boolean isSignatureValid(PublicKey publicKey, String rawIdentityDocument, String signature) { - try { - Signature signatureVerifier = Signature.getInstance("SHA512withRSA"); - signatureVerifier.initVerify(publicKey); - signatureVerifier.update(rawIdentityDocument.getBytes()); - return signatureVerifier.verify(Base64.getDecoder().decode(signature)); - } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { - throw new RuntimeException(e); - } - } - // If/when we dont care about logging exactly whats wrong, this can be simplified // TODO Use identity type to determine if this check should be performed boolean isSameIdentityAsInServicesXml(ApplicationId applicationId, String domain, String service) { diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java index 578c044796f..a1839ec62a2 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java @@ -14,14 +14,12 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; +import com.yahoo.vespa.athenz.identityprovider.client.IdentityDocumentSigner; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.AutoGeneratedKeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.instanceconfirmation.InstanceValidator; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Allocation; @@ -48,7 +46,7 @@ public class IdentityDocumentGeneratorTest { private static final Zone ZONE = new Zone(SystemName.cd, Environment.dev, RegionName.from("us-north-1")); @Test - public void generates_valid_identity_document() throws Exception { + public void generates_valid_identity_document() { String parentHostname = "docker-host"; String containerHostname = "docker-container"; @@ -99,12 +97,9 @@ public class IdentityDocumentGeneratorTest { // Validate that container ips are present assertThat(signedIdentityDocument.ipAddresses(), Matchers.containsInAnyOrder("::1")); - SignedIdentityDocumentEntity signedIdentityDocumentEntity = EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument); + IdentityDocumentSigner signer = new IdentityDocumentSigner(); // Validate signature - assertTrue("Message", InstanceValidator.isSignatureValid(keyProvider.getPublicKey(0), - signedIdentityDocumentEntity.rawIdentityDocument, - signedIdentityDocument.signature())); - + assertTrue(signer.hasValidSignature(signedIdentityDocument, keyProvider.getPublicKey(0))); } } diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java index 54411b424eb..04c4d4da51a 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java @@ -14,8 +14,6 @@ import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.bindings.IdentityDocumentEntity; import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; import com.yahoo.vespa.athenz.identityprovider.api.bindings.VespaUniqueInstanceIdEntity; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.AutoGeneratedKeyProvider; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils; import org.junit.Test; @@ -49,27 +47,6 @@ public class InstanceValidatorTest { private final String domain = "domain"; private final String service = "service"; - @Test - public void valid_signature() throws Exception { - KeyProvider keyProvider = new AutoGeneratedKeyProvider(); - InstanceValidator instanceValidator = new InstanceValidator(keyProvider, null); - InstanceConfirmation instanceConfirmation = createInstanceConfirmation( - keyProvider.getPrivateKey(0), applicationId, domain, service); - - assertTrue(instanceValidator.isInstanceSignatureValid(instanceConfirmation)); - } - - @Test - public void invalid_signature() throws Exception { - KeyProvider keyProvider = new AutoGeneratedKeyProvider(); - InstanceValidator instanceValidator = new InstanceValidator(keyProvider, null); - - KeyProvider fakeKeyProvider = new AutoGeneratedKeyProvider(); - InstanceConfirmation instanceConfirmation = createInstanceConfirmation( - fakeKeyProvider.getPrivateKey(0), applicationId, domain, service); - - assertFalse(instanceValidator.isInstanceSignatureValid(instanceConfirmation)); - } @Test public void application_does_not_exist() { diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java new file mode 100644 index 00000000000..b3b5df0e68b --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java @@ -0,0 +1,85 @@ +// 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.client; + +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; +import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; +import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; +import com.yahoo.vespa.athenz.tls.SignatureAlgorithm; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.time.Instant; +import java.util.Base64; +import java.util.Set; +import java.util.TreeSet; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Generates and validates the signature for a {@link SignedIdentityDocument} + * + * @author bjorncs + */ +public class IdentityDocumentSigner { + + public String generateSignature(VespaUniqueInstanceId providerUniqueId, + AthenzService providerService, + String configServerHostname, + String instanceHostname, + Instant createdAt, + Set<String> ipAddresses, + IdentityType identityType, + PrivateKey privateKey) { + try { + Signature signer = createSigner(); + signer.initSign(privateKey); + writeToSigner(signer, providerUniqueId, providerService, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType); + byte[] signature = signer.sign(); + return Base64.getEncoder().encodeToString(signature); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + public boolean hasValidSignature(SignedIdentityDocument doc, PublicKey publicKey) { + try { + Signature signer = createSigner(); + signer.initVerify(publicKey); + writeToSigner(signer, doc.providerUniqueId(), doc.providerService(), doc.configServerHostname(), doc.instanceHostname(), doc.createdAt(), doc.ipAddresses(), doc.identityType()); + return signer.verify(Base64.getDecoder().decode(doc.signature())); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + private static Signature createSigner() throws NoSuchAlgorithmException { + return Signature.getInstance(SignatureAlgorithm.SHA512_WITH_RSA.getAlgorithmName()); + } + + private static void writeToSigner(Signature signer, + VespaUniqueInstanceId providerUniqueId, + AthenzService providerService, + String configServerHostname, + String instanceHostname, + Instant createdAt, + Set<String> ipAddresses, + IdentityType identityType) throws SignatureException { + signer.update(providerUniqueId.asDottedString().getBytes(UTF_8)); + signer.update(providerService.getFullName().getBytes(UTF_8)); + signer.update(configServerHostname.getBytes(UTF_8)); + signer.update(instanceHostname.getBytes(UTF_8)); + ByteBuffer timestampAsBuffer = ByteBuffer.allocate(Long.BYTES); + timestampAsBuffer.putLong(createdAt.toEpochMilli()); + signer.update(timestampAsBuffer.array()); + for (String ipAddress : new TreeSet<>(ipAddresses)) { + signer.update(ipAddress.getBytes(UTF_8)); + } + signer.update(identityType.id().getBytes(UTF_8)); + } +} 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 index 3d85a12f714..2f3e2721751 100644 --- 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 @@ -5,7 +5,8 @@ package com.yahoo.vespa.athenz.tls; * @author bjorncs */ public enum SignatureAlgorithm { - SHA256_WITH_RSA("SHA256withRSA"); + SHA256_WITH_RSA("SHA256withRSA"), + SHA512_WITH_RSA("SHA512withRSA"); private final String algorithmName; diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java new file mode 100644 index 00000000000..9cc500ee241 --- /dev/null +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java @@ -0,0 +1,50 @@ +// 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.client; + +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; +import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; +import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; +import com.yahoo.vespa.athenz.tls.KeyAlgorithm; +import com.yahoo.vespa.athenz.tls.KeyUtils; +import org.junit.Test; + +import java.net.URI; +import java.security.KeyPair; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; + +import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.TENANT; +import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION; +import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_KEY_VERSION; +import static org.junit.Assert.*; + +/** + * @author bjorncs + */ +public class IdentityDocumentSignerTest { + + @Test + public void generates_and_validates_signature() { + IdentityDocumentSigner signer = new IdentityDocumentSigner(); + IdentityType identityType = TENANT; + VespaUniqueInstanceId id = + new VespaUniqueInstanceId(1, "cluster-id", "instance", "application", "tenant", "region", "environment", identityType); + AthenzService providerService = new AthenzService("vespa", "service"); + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); + String configserverHostname = "configserverhostname"; + String instanceHostname = "instancehostname"; + Instant createdAt = Instant.EPOCH; + HashSet<String> ipAddresses = new HashSet<>(Arrays.asList("1.2.3.4", "::1")); + String signature = + signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType, keyPair.getPrivate()); + + SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument( + null, signature, DEFAULT_KEY_VERSION, id, "dns-suffix", providerService, URI.create("https://zts"), + DEFAULT_DOCUMENT_VERSION, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType); + + assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic())); + } + +}
\ No newline at end of file |