From 5b92f0b77a3b765f2aa33bc937fb243dc369b571 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Wed, 5 May 2021 19:28:14 -0700 Subject: [PATCH] Support providing explicit options to VaultTransitSecretEngine sign & verifySignature The `VaultTransitSecretEngine.sign` and `VaultTransitSecretEngine.verifySignature` now have variants that take a `SignVerifyOptions` value. `SignVerifyOptions` allows specifying the following options from the Vault API: * `hashAlgorithm` (aka `hash_algorithm`) * `signatureAlgorithm` - (aka `signature_algorithm`) * `prehashed` * `marshalingAlgorithm` - (aka `marshaling_algorithm`) Some of these options (e.g. `hashAlgorithm`, `signatureAlgorithm` and `prehashed`) can be configured for specific transit keys via Quarkus config. The explicit options provided via `SignVerifyOptions` take precedence over any conifgured values. --- .../dto/transit/VaultTransitSignBody.java | 2 + .../dto/transit/VaultTransitVerifyBody.java | 2 + .../vault/VaultTransitSecretEngine.java | 62 +++++++++++++++++-- .../vault/runtime/VaultTransitManager.java | 57 ++++++++++++++--- .../vault/transit/SignVerifyOptions.java | 45 ++++++++++++++ .../io/quarkus/vault/VaultTransitITCase.java | 62 +++++++++++++++++++ .../vault/test/VaultTestExtension.java | 2 +- 7 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/transit/SignVerifyOptions.java diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitSignBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitSignBody.java index b2cdc2178a2ce..81e47c465a6d7 100644 --- a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitSignBody.java +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitSignBody.java @@ -15,5 +15,7 @@ public class VaultTransitSignBody implements VaultModel { public Boolean prehashed; @JsonProperty("signature_algorithm") public String signatureAlgorithm; + @JsonProperty("marshaling_algorithm") + public String marshalingAlgorithm; } diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitVerifyBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitVerifyBody.java index da6c136176c7d..444ea9e1fd159 100644 --- a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitVerifyBody.java +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/transit/VaultTransitVerifyBody.java @@ -13,5 +13,7 @@ public class VaultTransitVerifyBody implements VaultModel { public Boolean prehashed; @JsonProperty("signature_algorithm") public String signatureAlgorithm; + @JsonProperty("marshaling_algorithm") + public String marshalingAlgorithm; } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultTransitSecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultTransitSecretEngine.java index 9312af1b967e0..1c81fe9cfc85e 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultTransitSecretEngine.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultTransitSecretEngine.java @@ -9,6 +9,7 @@ import io.quarkus.vault.transit.KeyConfigRequestDetail; import io.quarkus.vault.transit.KeyCreationRequestDetail; import io.quarkus.vault.transit.RewrappingRequest; +import io.quarkus.vault.transit.SignVerifyOptions; import io.quarkus.vault.transit.SigningInput; import io.quarkus.vault.transit.SigningRequest; import io.quarkus.vault.transit.TransitContext; @@ -165,9 +166,22 @@ public interface VaultTransitSecretEngine { */ String sign(String keyName, SigningInput input, TransitContext transitContext); + /** + * Sign the input with the specified key and an optional explicit sign/verify options and an optional transit + * context used for key derivation, if applicable. + * + * @param keyName the signing key to use + * @param input data to sign + * @param options optional explicit sign/verify options + * @param transitContext optional transit context used for key derivation + * @return the signature + * @see sign data + */ + String sign(String keyName, SigningInput input, SignVerifyOptions options, TransitContext transitContext); + /** * Sign a list of inputs items. Each item shall specify the input to sign, an optional key version, and - * an optional transit context used for ky derivation, if applicable. + * an optional transit context used for key derivation, if applicable. * If any error occurs, the service will throw a {@link VaultSigningBatchException} * * @param keyName the signing key to use @@ -177,6 +191,19 @@ public interface VaultTransitSecretEngine { */ Map sign(String keyName, List requests); + /** + * Sign a list of inputs items and an optional explicit sign/verify options. Each item shall specify the input to + * sign, an optional key version, and an optional transit context used for key derivation, if applicable. + * If any error occurs, the service will throw a {@link VaultSigningBatchException} + * + * @param keyName the signing key to use + * @param requests the list of inputs to sign + * @param options optional explicit sign/verify options + * @return a map of each request with its corresponding signature item + * @see sign data + */ + Map sign(String keyName, List requests, SignVerifyOptions options); + /** * Checks that the signature was obtained from signing the input with the specified key. * The service will throw a {@link VaultException} if this is not the case. @@ -200,11 +227,25 @@ public interface VaultTransitSecretEngine { */ void verifySignature(String keyName, String signature, SigningInput input, TransitContext transitContext); + /** + * Checks that the signature was obtained from signing the input with the specified key an an optional explicit + * sign/verify options. + * The service will throw a {@link VaultException} if this is not the case. + * + * @param keyName the key that was used to sign the input + * @param signature the signature obtained from one of the sign methods + * @param input the original input data + * @param options optional explicit sign/verify options + * @param transitContext optional transit context used for key derivation + * @see verify signed data + */ + void verifySignature(String keyName, String signature, SigningInput input, SignVerifyOptions options, + TransitContext transitContext); + /** * Checks a list of verification requests. Each request shall specify an input and the signature we want to match - * against, and an optional transit context used for key derivation, if applicable. - * If the signature does not match, or if any other error occurs, - * the service will throw a {@link VaultVerificationBatchException} + * against, and an optional transit context used for key derivation, if applicable. If the signature does not + * match, or if any other error occurs, the service will throw a {@link VaultVerificationBatchException} * * @param keyName the key that was used to sign the input * @param requests a list of items specifying an input and a signature to match against @@ -212,6 +253,19 @@ public interface VaultTransitSecretEngine { */ void verifySignature(String keyName, List requests); + /** + * Checks a list of verification requests. Each request shall specify an input and the signature we want to match + * against, and an optional explicit sign/verify options and an optionals transit context used for key derivation, + * if applicable. If the signature does not match, or if any other error occurs, the service will throw a + * {@link VaultVerificationBatchException} + * + * @param keyName the key that was used to sign the input + * @param requests a list of items specifying an input and a signature to match against + * @param options optional explicit sign/verify options + * @see verify signed data + */ + void verifySignature(String keyName, List requests, SignVerifyOptions options); + // --- admin operations /** diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultTransitManager.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultTransitManager.java index 2d1c72054ef73..93a629dc8a061 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultTransitManager.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultTransitManager.java @@ -59,6 +59,7 @@ import io.quarkus.vault.transit.KeyConfigRequestDetail; import io.quarkus.vault.transit.KeyCreationRequestDetail; import io.quarkus.vault.transit.RewrappingRequest; +import io.quarkus.vault.transit.SignVerifyOptions; import io.quarkus.vault.transit.SigningInput; import io.quarkus.vault.transit.SigningRequest; import io.quarkus.vault.transit.TransitContext; @@ -228,27 +229,37 @@ public String sign(String keyName, String input) { @Override public String sign(String keyName, SigningInput input, TransitContext transitContext) { + return sign(keyName, input, null, transitContext); + } + + @Override + public String sign(String keyName, SigningInput input, SignVerifyOptions options, TransitContext transitContext) { SigningRequest item = new SigningRequest(input, transitContext); List pairs = singletonList(new SigningRequestResultPair(item)); - signBatch(keyName, NO_KEY_VERSION, pairs); + signBatch(keyName, NO_KEY_VERSION, pairs, options); return pairs.get(0).getResult().getValueOrElseError(); } @Override public Map sign(String keyName, List requests) { + return sign(keyName, requests, null); + } + + @Override + public Map sign(String keyName, List requests, SignVerifyOptions options) { List pairs = requests.stream().map(SigningRequestResultPair::new).collect(toList()); pairs.stream() .collect(groupingBy(SigningRequestResultPair::getKeyVersion)) - .forEach((keyVersion, subpairs) -> signBatch(keyName, keyVersion, subpairs)); + .forEach((keyVersion, subpairs) -> signBatch(keyName, keyVersion, subpairs, options)); List results = pairs.stream().map(SigningRequestResultPair::getResult).collect(toList()); checkBatchErrors(results, errors -> new VaultSigningBatchException(errors + " signing errors", zip(requests, results))); return zipRequestToValue(requests, results); } - private void signBatch(String keyName, int keyVersion, List pairs) { + private void signBatch(String keyName, int keyVersion, List pairs, SignVerifyOptions options) { String hashAlgorithm = null; @@ -267,6 +278,13 @@ private void signBatch(String keyName, int keyVersion, List requests) { - List results = verifyBatch(keyName, requests); + verifySignature(keyName, requests, null); + } + + @Override + public void verifySignature(String keyName, List requests, SignVerifyOptions options) { + List results = verifyBatch(keyName, requests, options); Map resultMap = zip(requests, results); checkBatchErrors(results, errors -> new VaultVerificationBatchException(errors + " verification errors", resultMap)); } - private List verifyBatch(String keyName, List requests) { + private List verifyBatch(String keyName, List requests, + SignVerifyOptions options) { String hashAlgorithm = null; @@ -309,8 +339,15 @@ private List verifyBatch(String keyName, List Map zip(List keys, List values, Function f) return map; } + private T defaultIfNull(T value, T defaultValue) { + if (value != null) + return value; + return defaultValue; + } + } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/transit/SignVerifyOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/transit/SignVerifyOptions.java new file mode 100644 index 0000000000000..423e6fb611c4a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/transit/SignVerifyOptions.java @@ -0,0 +1,45 @@ +package io.quarkus.vault.transit; + +public class SignVerifyOptions { + + private String signatureAlgorithm; + private String hashAlgorithm; + private Boolean prehashed; + private String marshalingAlgorithm; + + public String getSignatureAlgorithm() { + return signatureAlgorithm; + } + + public SignVerifyOptions setSignatureAlgorithm(String signatureAlgorithm) { + this.signatureAlgorithm = signatureAlgorithm; + return this; + } + + public String getHashAlgorithm() { + return hashAlgorithm; + } + + public SignVerifyOptions setHashAlgorithm(String hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + return this; + } + + public Boolean getPrehashed() { + return prehashed; + } + + public SignVerifyOptions setPrehashed(Boolean prehashed) { + this.prehashed = prehashed; + return this; + } + + public String getMarshalingAlgorithm() { + return marshalingAlgorithm; + } + + public SignVerifyOptions setMarshalingAlgorithm(String marshalingAlgorithm) { + this.marshalingAlgorithm = marshalingAlgorithm; + return this; + } +} diff --git a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultTransitITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultTransitITCase.java index 5816a4c7cf076..9b92b3a7f00d0 100644 --- a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultTransitITCase.java +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultTransitITCase.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -38,6 +39,7 @@ import io.quarkus.vault.transit.KeyConfigRequestDetail; import io.quarkus.vault.transit.KeyCreationRequestDetail; import io.quarkus.vault.transit.RewrappingRequest; +import io.quarkus.vault.transit.SignVerifyOptions; import io.quarkus.vault.transit.SigningInput; import io.quarkus.vault.transit.SigningRequest; import io.quarkus.vault.transit.TransitContext; @@ -128,6 +130,66 @@ public void signString() { transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, null); } + @Test + public void signStringExplicitHashAlgorithmSha256() { + SignVerifyOptions options = new SignVerifyOptions().setHashAlgorithm("sha2-256"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, options, null); + } + + @Test + public void signStringExplicitHashAlgorithmSha512() { + SignVerifyOptions options = new SignVerifyOptions().setHashAlgorithm("sha2-512"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, options, null); + } + + @Test + public void signStringExplicitHashAlgorithmMismatched() { + SignVerifyOptions options = new SignVerifyOptions().setHashAlgorithm("sha2-256"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + assertThrows(VaultException.class, + () -> transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, + options.setHashAlgorithm("sha1"), null)); + } + + @Test + public void signStringExplicitMarshalingAlgorithmASN1() { + SignVerifyOptions options = new SignVerifyOptions().setMarshalingAlgorithm("asn1"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, options, null); + } + + @Test + public void signStringExplicitMarshalingAlgorithmJWS() { + SignVerifyOptions options = new SignVerifyOptions().setMarshalingAlgorithm("jws"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, options, null); + } + + @Test + public void signStringExplicitMarshalingAlgorithmMismatched() { + SignVerifyOptions options = new SignVerifyOptions().setMarshalingAlgorithm("jws"); + String signature = transitSecretEngine.sign(SIGN_KEY_NAME, input, options, null); + assertThrows(VaultException.class, + () -> transitSecretEngine.verifySignature(SIGN_KEY_NAME, signature, input, + options.setMarshalingAlgorithm("asn1"), null)); + } + + @Test + public void signStringExplicitSignatureAlgorithmPKCS1() { + SignVerifyOptions options = new SignVerifyOptions().setSignatureAlgorithm("pkcs1v15"); + String signature = transitSecretEngine.sign(SIGN_KEY2_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY2_NAME, signature, input, options, null); + } + + @Test + public void signStringExplicitSignatureAlgorithmPSS() { + SignVerifyOptions options = new SignVerifyOptions().setSignatureAlgorithm("pss"); + String signature = transitSecretEngine.sign(SIGN_KEY2_NAME, input, options, null); + transitSecretEngine.verifySignature(SIGN_KEY2_NAME, signature, input, options, null); + } + @Test public void signJws() { String signature = transitSecretEngine.sign("jws", input, null); diff --git a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java index 086a314a786f1..a9dfb4832e1e5 100644 --- a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java +++ b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java @@ -371,7 +371,7 @@ private void initVault() throws InterruptedException, IOException { execVault(format("vault write -f transit/keys/%s", ENCRYPTION_KEY2_NAME)); execVault(format("vault write transit/keys/%s derived=true", ENCRYPTION_DERIVED_KEY_NAME)); execVault(format("vault write transit/keys/%s type=ecdsa-p256", SIGN_KEY_NAME)); - execVault(format("vault write transit/keys/%s type=ecdsa-p256", SIGN_KEY2_NAME)); + execVault(format("vault write transit/keys/%s type=rsa-2048", SIGN_KEY2_NAME)); execVault(format("vault write transit/keys/%s type=ed25519 derived=true", SIGN_DERIVATION_KEY_NAME)); execVault("vault write transit/keys/jws type=ecdsa-p256");