From 10488de17ad5c16b30be0a0be4e8e785dcfa01dd Mon Sep 17 00:00:00 2001 From: Luander Ribeiro Date: Sun, 15 Oct 2017 12:08:13 +0200 Subject: [PATCH] Extend Transit API to support Hashing and Signing Operations. Transit API now has support to generate HMAC digests, signatures and verify signatures. Plaintext plaintext = Plaintext.of("hello-world"); Signature signature = transitOperations.sign(keyName, plaintext); boolean valid = transitOperations.verify(keyName, plaintext, signature); Hmac hmac = transitOperations.getHmac(keyName, plaintext); SignatureValidation validation = transitOperations.verify(keyName, VaultSignatureVerificationRequest.create(plaintext, hmac)); Original pull request: gh-162. Closes: gh-148. --- .../vault/core/VaultTransitOperations.java | 62 ++++++ .../vault/core/VaultTransitTemplate.java | 86 +++++++++ .../springframework/vault/support/Hmac.java | 86 +++++++++ .../vault/support/Signature.java | 87 +++++++++ .../vault/support/VaultHmacRequest.java | 165 ++++++++++++++++ .../vault/support/VaultSignRequest.java | 135 +++++++++++++ .../VaultSignatureVerificationRequest.java | 178 ++++++++++++++++++ .../VaultTransitTemplateIntegrationTests.java | 169 +++++++++++++++++ 8 files changed, 968 insertions(+) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/Hmac.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/Signature.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultHmacRequest.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignRequest.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignatureVerificationRequest.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitOperations.java index ba9d3644..00f769a5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitOperations.java @@ -19,11 +19,16 @@ import java.util.List; import org.springframework.lang.Nullable; import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Hmac; import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.RawTransitKey; +import org.springframework.vault.support.Signature; import org.springframework.vault.support.TransitKeyType; import org.springframework.vault.support.VaultDecryptionResult; import org.springframework.vault.support.VaultEncryptionResult; +import org.springframework.vault.support.VaultHmacRequest; +import org.springframework.vault.support.VaultSignRequest; +import org.springframework.vault.support.VaultSignatureVerificationRequest; import org.springframework.vault.support.VaultTransitContext; import org.springframework.vault.support.VaultTransitKey; import org.springframework.vault.support.VaultTransitKeyConfiguration; @@ -222,4 +227,61 @@ public interface VaultTransitOperations { * @see #rotate(String) */ String rewrap(String keyName, String ciphertext, VaultTransitContext transitContext); + + /** + * Generate HMAC digest of given data. + * + * @param keyName must not be empty or {@literal null}. + * @param plaintext must not be empty or {@literal null}. + * @return the digest of given data using sha2-256 hash algorithm and the named key. + */ + Hmac generateHmac(String keyName, Plaintext plaintext); + + /** + * Generate HMAC digest of given data. + * + * @param keyName must not be empty or {@literal null}. + * @param request {@link VaultHmacRequest} must not be empty or {@literal null}. + * @return the digest of given data using the specified hash algorithm and the named key. + */ + Hmac generateHmac(String keyName, VaultHmacRequest request); + + /** + * Sign a String using a key from the vault using the SHA-256 algorithm. + * + * @param keyName must not be empty or {@literal null}. + * @param plaintext must not be empty or {@literal null}. + * @return Signature of the payload + */ + Signature sign(String keyName, Plaintext plaintext); + + /** + * Sign a String using a key from the vault. + * + * @param keyName must not be empty or {@literal null}. + * @param request {@link VaultSignRequest} + * must not be empty or {@literal null}. + * @return Signature of the payload + */ + Signature sign(String keyName, VaultSignRequest request); + + /** + * Verify the validity of a signature in the vault. + * + * @param keyName must not be empty or {@literal null}. + * @param plaintext must not be empty or {@literal null}. + * @param signature Signature to be verified + * @return true if the signature is valid, false otherwise + */ + boolean verify(String keyName, Plaintext plaintext, Signature signature); + + /** + * Verify the validity of a signature in the vault. + * + * @param keyName must not be empty or {@literal null}. + * @param request {@link VaultSignatureVerificationRequest} + * must not be empty or {@literal null}. + * @return true if the signature is valid, false otherwise + */ + boolean verify(String keyName, VaultSignatureVerificationRequest request); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java index 12c15fd9..c3fb36f2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java @@ -31,13 +31,18 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.vault.VaultException; import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Hmac; import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.RawTransitKey; +import org.springframework.vault.support.Signature; import org.springframework.vault.support.TransitKeyType; import org.springframework.vault.support.VaultDecryptionResult; import org.springframework.vault.support.VaultEncryptionResult; +import org.springframework.vault.support.VaultHmacRequest; import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultResponseSupport; +import org.springframework.vault.support.VaultSignRequest; +import org.springframework.vault.support.VaultSignatureVerificationRequest; import org.springframework.vault.support.VaultTransitContext; import org.springframework.vault.support.VaultTransitKey; import org.springframework.vault.support.VaultTransitKeyConfiguration; @@ -49,6 +54,7 @@ import org.springframework.vault.support.VaultTransitKeyCreationRequest; * @author Mark Paluch * @author Sven Schürmann * @author Praveendra Singh + * @author Luander Ribeiro */ public class VaultTransitTemplate implements VaultTransitOperations { @@ -56,6 +62,8 @@ public class VaultTransitTemplate implements VaultTransitOperations { private final String path; + private static final String DEFAULT_SIGN_ALGORITHM = "sha2-256"; + public VaultTransitTemplate(VaultOperations vaultOperations, String path) { Assert.notNull(vaultOperations, "VaultOperations must not be null"); @@ -340,6 +348,74 @@ public class VaultTransitTemplate implements VaultTransitOperations { .getRequiredData().get("ciphertext"); } + @Override + public Hmac generateHmac(String keyName, Plaintext plaintext) { + + Assert.notNull(plaintext, "Plaintext must not be null"); + + VaultHmacRequest request = VaultHmacRequest.ofInput(plaintext); + + return generateHmac(keyName, request); + } + + @Override + public Hmac generateHmac(String keyName, VaultHmacRequest hmacRequest) { + + Assert.hasText(keyName, "KeyName must not be empty"); + Assert.notNull(hmacRequest, "Request must not be null"); + + String hmac = (String) vaultOperations. + write(String.format("%s/hmac/%s", path, keyName), hmacRequest).getData() + .get("hmac"); + return toHmac(hmac, hmacRequest.getContext()); + } + + @Override + public Signature sign(String keyName, Plaintext plaintext) { + + Assert.notNull(plaintext, "Plaintext must not be null"); + + VaultSignRequest request = VaultSignRequest.ofInput(plaintext); + + return sign(keyName, request); + } + + @Override + public Signature sign(String keyName, VaultSignRequest signRequest) { + + Assert.hasText(keyName, "KeyName must not be empty"); + Assert.notNull(signRequest, "Plain text must not be null"); + + String signature = (String) vaultOperations. + write(String.format("%s/sign/%s", path, keyName), signRequest).getData() + .get("signature"); + return toSignature(signature, signRequest.getContext()); + } + + @Override + public boolean verify(String keyName, Plaintext plainText, Signature signature) { + + Assert.notNull(signature, "Signature must not be null"); + Assert.notNull(plainText, "Input must not be null"); + + VaultSignatureVerificationRequest request = + VaultSignatureVerificationRequest.builder() + .input(plainText) + .signature(signature) + .build(); + return verify(keyName, request); + } + + @Override + public boolean verify(String keyName, VaultSignatureVerificationRequest request) { + + Assert.notNull(request, "Request must not be null"); + + return (boolean) vaultOperations. + write(String.format("%s/verify/%s", path, keyName), request).getData() + .get("valid"); + } + private static void applyTransitOptions(VaultTransitContext context, Map request) { @@ -431,6 +507,16 @@ public class VaultTransitTemplate implements VaultTransitOperations { .of(plaintext); } + private static Hmac toHmac(String plaintext, VaultTransitContext context) { + return context != null ? Hmac.of(plaintext).with(context) : Hmac + .of(plaintext); + } + + private static Signature toSignature(String plaintext, VaultTransitContext context) { + return context != null ? Signature.of(plaintext).with(context) : Signature + .of(plaintext); + } + @SuppressWarnings("unchecked") private static List> getBatchData(VaultResponse vaultResponse) { return (List>) vaultResponse.getRequiredData().get( diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Hmac.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Hmac.java new file mode 100644 index 00000000..24d1a529 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Hmac.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.support; + +import lombok.EqualsAndHashCode; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +import java.util.Arrays; + +/** + * Value object representing Hmac digest with an optional {@link VaultTransitContext}. + * + * @author Luander Ribeiro + */ +@EqualsAndHashCode +public class Hmac { + + private final String hmac; + + private final VaultTransitContext context; + + private Hmac(String hmac, VaultTransitContext context) { + this.hmac = hmac; + this.context = context; + } + + /** + * Factory method to create {@link Hmac} from a byte sequence. + * + * @param hmac the Hmac digest, must not be {@literal null} or empty. + * @return the {@link Hmac} for {@code plaintext}. + */ + public static Hmac of(byte[] hmac) { + + Assert.isTrue(!ObjectUtils.isEmpty(hmac), + "Hmac must not be null or empty"); + + return new Hmac(Arrays.toString(hmac), VaultTransitContext.empty()); + } + + /** + * Factory method to create {@link Hmac} from the given {@code hmac}. + * + * @param hmac the Hmac digest, must not be {@literal null} or empty. + * @return the {@link Hmac} for {@code hmac}. + */ + public static Hmac of(String hmac) { + + Assert.hasText(hmac, "Hmac digest must not be null or empty"); + + return new Hmac(hmac, VaultTransitContext.empty()); + } + + public String getHmac() { + return hmac; + } + + public VaultTransitContext getContext() { + return context; + } + + /** + * Create a new {@link Hmac} object from this Hmac digest associated with the + * given {@link VaultTransitContext}. + * + * @param context transit context. + * @return the new {@link Hmac} object. + */ + public Hmac with(VaultTransitContext context) { + return new Hmac(getHmac(), context); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Signature.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Signature.java new file mode 100644 index 00000000..0d0be0fa --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Signature.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.support; + +import lombok.EqualsAndHashCode; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +import java.util.Arrays; + +/** + * Value object representing Signature with an optional {@link VaultTransitContext}. + * + * @author Luander Ribeiro + */ +@EqualsAndHashCode +public class Signature { + + private final String signature; + + private final VaultTransitContext context; + + private Signature(String signature, VaultTransitContext context) { + this.signature = signature; + this.context = context; + } + + /** + * Factory method to create {@link Signature} from the given {@code signature}. + * + * @param signature the signature, must not be {@literal null} or empty. + * @return the {@link Signature} for {@code signature}. + */ + public static Signature of(byte[] signature) { + + Assert.isTrue(!ObjectUtils.isEmpty(signature), + "Signature must not be null or empty"); + + return new Signature(Arrays.toString(signature), VaultTransitContext.empty()); + } + + /** + * Factory method to create {@link Signature} from the given {@code signature}. + * + * @param signature the signature, must not be {@literal null} or empty. + * @return the {@link Signature} for {@code signature}. + */ + public static Signature of(String signature) { + + Assert.hasText(signature, "Signature must not be null or empty"); + + return new Signature(signature, VaultTransitContext.empty()); + } + + public String getSignature() { + return signature; + } + + public VaultTransitContext getContext() { + return context; + } + + /** + * Create a new {@link Signature} object from this signature associated with the + * given {@link VaultTransitContext}. + * + * @param context transit context. + * @return the new {@link Signature} object. + */ + public Signature with(VaultTransitContext context) { + return new Signature(getSignature(), context); + } +} + diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultHmacRequest.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultHmacRequest.java new file mode 100644 index 00000000..331fa8d3 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultHmacRequest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.support; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +/** + * Request for a HMAC Digest. + * + * @author Luander Ribeiro + */ +public class VaultHmacRequest { + + @JsonProperty("key_version") + private final int keyVersion; + + private final String algorithm; + + private final String input; + + @JsonIgnore + private final VaultTransitContext context; + + private VaultHmacRequest(int keyVersion, String algorithm, + String input, VaultTransitContext context) { + this.algorithm = algorithm; + this.input = Base64Utils.encodeToString(input.getBytes()); + this.keyVersion = keyVersion; + this.context = context; + } + + /** + * @return New instance of {@link VaultHmacRequest.VaultHmacRequestBuilder} + */ + public static VaultHmacRequestBuilder builder() { + return new VaultHmacRequestBuilder(); + } + + /** + * Create a new {@link VaultHmacRequest} specifically for an {@code input}. + * Uses {@code sha2-256} algorithm. + * + * @return a new {@link VaultHmacRequest} for the given {@code input}. + */ + public static VaultHmacRequest ofInput(Plaintext input) { + return builder().input(input).build(); + } + + /** + * @return Algorithm used for creating the digest. + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * @return plain text input used as basis to generate the digest. + */ + public String getInput() { + return input; + } + + /** + * @return Version of the key used. If not set the latest version is used. + */ + public int getKeyVersion() { + return keyVersion; + } + + public VaultTransitContext getContext() { + return context; + } + + public static class VaultHmacRequestBuilder { + + private int keyVersion; + + private String algorithm = "sha2-256"; + + private Plaintext input; + + private VaultTransitContext context; + + /** + * Configure the algorithm to be used for the operation. + * + * @param algorithm Specify the algorithm to be used for the operation. If not set, + * sha2-256 is used. + * Supported algorithms are: + * sha2-224, sha2-256, sha2-384, sha2-512 + * @return {@code this} + */ + public VaultHmacRequestBuilder algorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this}. + */ + public VaultHmacRequestBuilder input(Plaintext input) { + this.input = input; + this.context = input.getContext(); + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this} + */ + public VaultHmacRequestBuilder input(String input) { + this.input = Plaintext.of(input); + this.context = VaultTransitContext.empty(); + return this; + } + + /** + * Configure the version to be used for the operation. + * + * @param version key version to be used. If not set, uses the latest version. + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultHmacRequestBuilder keyVersion(int version) { + this.keyVersion = version; + return this; + } + + /** + * Build a new {@link VaultHmacRequest} instance. Requires + * {@link #input(String)} or {@link #input(Plaintext)} to be configured. + * + * @return a new {@link VaultHmacRequest}. + */ + public VaultHmacRequest build() { + + Assert.notNull(input, "Input must not be empty"); + + return new VaultHmacRequest(keyVersion, algorithm, input.asString(), context); + } + + + + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignRequest.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignRequest.java new file mode 100644 index 00000000..100db915 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignRequest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.support; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +/** + * Request for a signature creation request. + * + * @author Luander Ribeiro + */ +public class VaultSignRequest { + + private final String algorithm; + + private final String input; + + @JsonIgnore + private final VaultTransitContext context; + + private VaultSignRequest(String algorithm, String input, VaultTransitContext context) { + this.algorithm = algorithm; + this.input = Base64Utils.encodeToString(input.getBytes()); + this.context = context; + } + + /** + * @return New instance of {@link VaultSignRequest.VaultSignRequestBuilder} + */ + public static VaultSignRequestBuilder builder() { + return new VaultSignRequestBuilder(); + } + + /** + * Create a new {@link VaultSignRequest} specifically for an {@code input}. + * Uses {@code sha2-256} algorithm. + * + * @return a new {@link VaultSignRequest} for the given {@code input}. + */ + public static VaultSignRequest ofInput(Plaintext input) { + return builder().input(input).build(); + } + + /** + * @return Algorithm used for creating the digest. + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * @return plain text input used as basis to generate the digest. + */ + public String getInput() { + return input; + } + + public VaultTransitContext getContext() { + return context; + } + + public static class VaultSignRequestBuilder { + + private String algorithm = "sha2-256"; + + private Plaintext input; + + private VaultTransitContext context; + + /** + * Configure the algorithm to be used for the operation. + * + * @param algorithm Specify the algorithm to be used for the operation. If not set, + * sha2-256 is used. + * Supported algorithms are: + * sha2-224, sha2-256, sha2-384, sha2-512 + * @return {@code this} + */ + public VaultSignRequestBuilder algorithm(String algorithm) { + this.algorithm = algorithm; + this.context = VaultTransitContext.empty(); + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this}. + */ + public VaultSignRequestBuilder input(Plaintext input) { + this.input = input; + this.context = input.getContext(); + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this} + */ + public VaultSignRequestBuilder input(String input) { + this.input = Plaintext.of(input); + return this; + } + + /** + * Build a new {@link VaultHmacRequest} instance. Requires + * {@link #input(String)} or {@link #input(Plaintext)} to be configured. + * + * @return a new {@link VaultHmacRequest}. + */ + public VaultSignRequest build() { + Assert.notNull(input, "Input must not be empty"); + + return new VaultSignRequest(algorithm, input.asString(), context); + } + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignatureVerificationRequest.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignatureVerificationRequest.java new file mode 100644 index 00000000..d7a32d9b --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultSignatureVerificationRequest.java @@ -0,0 +1,178 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.support; + +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +/** + * Request for a signature verification. + * + * @author Luander Ribeiro + */ +public class VaultSignatureVerificationRequest { + + private final String algorithm; + + private final String input; + + private final String signature; + + private final String hmac; + + private VaultSignatureVerificationRequest(String algorithm, String input, + String signature, String hmac) { + this.algorithm = algorithm; + this.input = Base64Utils.encodeToString(input.getBytes()); + this.signature = String.valueOf(signature); + this.hmac = hmac; + } + + /** + * @return New instance of + * {@link VaultSignatureVerificationRequest.VaultSignatureVerificationRequestBuilder} + */ + public static VaultSignatureVerificationRequestBuilder builder() { + return new VaultSignatureVerificationRequestBuilder(); + } + + /** + * Create a new {@link VaultHmacRequest} specifically for a {@code algorithm}. + * + * @param algorithm Specify the algorithm to be used for the operation. If not set, + * sha2-256 is used. + * Supported algorithms are: + * sha2-224, sha2-256, sha2-384, sha2-512 + * @return a new {@link VaultHmacRequest} for the given {@code algorithm}. + */ + public VaultSignatureVerificationRequest ofAlgorithm(String algorithm) { + return builder().algorithm(algorithm).build(); + } + + /** + * @return Algorithm used for creating the digest. + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * @return plain text input used as basis to generate the digest. + */ + public String getInput() { + return input; + } + + /** + * @return Signature resulting of a sign operation. + */ + public String getSignature() { + return signature; + } + + /** + * @return Digest resulting of a Hmac operation. + */ + public String getHmac() { + return hmac; + } + + public static class VaultSignatureVerificationRequestBuilder { + + private String algorithm = "sha2-256"; + + private Plaintext input; + + private Signature signature; + + private String hmac; + + /** + * Configure the algorithm to be used for the operation. + * + * @param algorithm Specify the algorithm to be used for the operation. If not set, + * sha2-256 is used. + * Supported algorithms are: + * sha2-224, sha2-256, sha2-384, sha2-512 + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultSignatureVerificationRequestBuilder algorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + /** + * Configure the signature to be verified. + * + * @param signature to be verified. + * Either signature or hmac must not be empty of {@literal null} + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultSignatureVerificationRequestBuilder signature(Signature signature) { + this.signature = signature; + return this; + } + + /** + * Configure the hmac to be verified. + * + * @param hmac to be verified. + * Either signature or hmac must not be empty of {@literal null} + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultSignatureVerificationRequestBuilder hmac(String hmac) { + this.hmac = hmac; + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultSignatureVerificationRequestBuilder input(Plaintext input) { + this.input = input; + return this; + } + + /** + * Configure the input to be used to create the digest. + * + * @param input base input to create the digest, must not be empty or {@literal null}. + * @return {@code this} {@link VaultHmacRequest.VaultHmacRequestBuilder}. + */ + public VaultSignatureVerificationRequestBuilder input(String input) { + this.input = Plaintext.of(input); + return this; + } + + /** + * Build a new {@link VaultHmacRequest} instance. Requires + * {@link #input(String)} or {@link #input(Plaintext)} to be configured. + * + * @return a new {@link VaultHmacRequest}. + */ + public VaultSignatureVerificationRequest build() { + + Assert.hasText(input.asString(), "Input must not be empty"); + + return new VaultSignatureVerificationRequest(algorithm, + input.asString(), signature.getSignature(), hmac); + } + + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java index 1feaeb51..e52da3b3 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java @@ -29,12 +29,17 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.vault.VaultException; import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Hmac; import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.RawTransitKey; +import org.springframework.vault.support.Signature; import org.springframework.vault.support.TransitKeyType; import org.springframework.vault.support.VaultDecryptionResult; import org.springframework.vault.support.VaultEncryptionResult; +import org.springframework.vault.support.VaultHmacRequest; import org.springframework.vault.support.VaultMount; +import org.springframework.vault.support.VaultSignRequest; +import org.springframework.vault.support.VaultSignatureVerificationRequest; import org.springframework.vault.support.VaultTransitContext; import org.springframework.vault.support.VaultTransitKey; import org.springframework.vault.support.VaultTransitKeyConfiguration; @@ -517,6 +522,170 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport assertThat(decrypted.get(1).getCause()).isInstanceOf(VaultException.class); } + @Test + public void generateHmacShouldCreateHmac() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + Hmac hmac = transitOperations.generateHmac("ecdsa-key", Plaintext.of("hello-world")); + assertThat(hmac.getHmac()).startsWith("vault:v"); + } + + @Test + public void generateHmacWithCustomAlgorithmShouldCreateHmac() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + VaultHmacRequest request = VaultHmacRequest.builder() + .input("hello-world") + .algorithm("sha2-512") + .build(); + + Hmac hmac = transitOperations.generateHmac("ecdsa-key", request); + assertThat(hmac.getHmac()).startsWith("vault:v"); + } + + @Test(expected = VaultException.class) + public void generateHmacWithInvalidAlgorithmShouldFail() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + VaultHmacRequest request = VaultHmacRequest.builder() + .input("hello-world") + .algorithm("blah-512") + .build(); + + transitOperations.generateHmac("ecdsa-key", request); + } + + @Test + public void signShouldCreateSignature() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + Signature signature = transitOperations.sign("ecdsa-key", Plaintext.of("hello-world")); + assertThat(signature.getSignature()).startsWith("vault:v"); + } + + @Test(expected = VaultException.class) + public void signWithInvalidKeyFormatShouldFail() { + + transitOperations.createKey("mykey"); + + transitOperations.sign("mykey", Plaintext.of("hello-world")); + } + + @Test + public void signWithCustomAlgorithShouldCreateSignature() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + VaultSignRequest request = VaultSignRequest.builder() + .input("hello-world") + .algorithm("sha2-512") + .build(); + + Signature signature = transitOperations.sign("ecdsa-key", request); + assertThat(signature.getSignature()).startsWith("vault:v"); + } + + @Test(expected = VaultException.class) + public void signWithInvalidAlgorithmShouldFail() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + VaultSignRequest request = VaultSignRequest.builder() + .input("hello-world") + .algorithm("blah-512") + .build(); + + transitOperations.sign("ecdsa-key", request); + } + + @Test + public void shouldVerifyValidSignature() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + Signature signature = transitOperations.sign("ecdsa-key", Plaintext.of("hello-world")); + assertThat(signature.getSignature()).startsWith("vault:v"); + + boolean valid = transitOperations.verify("ecdsa-key", + Plaintext.of("hello-world"), signature); + assertThat(valid).isTrue(); + } + + @Test + public void shouldVerifyValidSignatureWithCustomAlgorithm() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + VaultSignRequest request = VaultSignRequest.builder() + .input("hello-world") + .algorithm("sha2-512") + .build(); + + Signature signature = transitOperations.sign("ecdsa-key", request); + assertThat(signature.getSignature()).startsWith("vault:v"); + + VaultSignatureVerificationRequest verificationRequest = + VaultSignatureVerificationRequest.builder() + .algorithm("sha2-512") + .input("hello-world") + .signature(signature) + .build(); + + boolean valid = transitOperations.verify("ecdsa-key", verificationRequest); + assertThat(valid).isTrue(); + } + + @Test + public void shouldFailToVerifyValidSignatureWithInvalidExistingCustomAlgorithm() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + Signature signature = transitOperations.sign("ecdsa-key", Plaintext.of("hello-world")); + assertThat(signature.getSignature()).startsWith("vault:v"); + + VaultSignatureVerificationRequest verificationRequest = + VaultSignatureVerificationRequest.builder() + .algorithm("sha2-512") + .input("hello-world") + .signature(signature) + .build(); + + boolean valid = transitOperations.verify("ecdsa-key", verificationRequest); + assertThat(valid).isFalse(); + } + + @Test(expected = VaultException.class) + public void shouldFailToVerifyValidSignatureWithInvalidCustomAlgorithm() { + + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); + transitOperations.createKey("ecdsa-key", keyCreationRequest); + + Signature signature = transitOperations.sign("ecdsa-key", Plaintext.of("hello-world")); + assertThat(signature.getSignature()).startsWith("vault:v"); + + VaultSignatureVerificationRequest verificationRequest = + VaultSignatureVerificationRequest.builder() + .algorithm("blah-512") + .input("hello-world") + .signature(signature) + .build(); + + transitOperations.verify("ecdsa-key", verificationRequest); + } + @Test public void shouldCreateNewExportableKey() {