Skip to content

Commit

Permalink
Support providing explicit options to VaultTransitSecretEngine sign &…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
kdubb committed May 7, 2021
1 parent a398a88 commit 5b92f0b
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public class VaultTransitSignBody implements VaultModel {
public Boolean prehashed;
@JsonProperty("signature_algorithm")
public String signatureAlgorithm;
@JsonProperty("marshaling_algorithm")
public String marshalingAlgorithm;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public class VaultTransitVerifyBody implements VaultModel {
public Boolean prehashed;
@JsonProperty("signature_algorithm")
public String signatureAlgorithm;
@JsonProperty("marshaling_algorithm")
public String marshalingAlgorithm;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <a href="https://www.vaultproject.io/api/secret/transit/index.html#sign-data">sign data</a>
*/
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
Expand All @@ -177,6 +191,19 @@ public interface VaultTransitSecretEngine {
*/
Map<SigningRequest, String> sign(String keyName, List<SigningRequest> 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 <a href="https://www.vaultproject.io/api/secret/transit/index.html#sign-data">sign data</a>
*/
Map<SigningRequest, String> sign(String keyName, List<SigningRequest> 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.
Expand All @@ -200,18 +227,45 @@ 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 <a href="https://www.vaultproject.io/api/secret/transit/index.html#verify-signed-data">verify signed data</a>
*/
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
* @see <a href="https://www.vaultproject.io/api/secret/transit/index.html#verify-signed-data">verify signed data</a>
*/
void verifySignature(String keyName, List<VerificationRequest> 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 <a href="https://www.vaultproject.io/api/secret/transit/index.html#verify-signed-data">verify signed data</a>
*/
void verifySignature(String keyName, List<VerificationRequest> requests, SignVerifyOptions options);

// --- admin operations

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SigningRequestResultPair> 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<SigningRequest, String> sign(String keyName, List<SigningRequest> requests) {
return sign(keyName, requests, null);
}

@Override
public Map<SigningRequest, String> sign(String keyName, List<SigningRequest> requests, SignVerifyOptions options) {

List<SigningRequestResultPair> 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<SigningResult> 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<SigningRequestResultPair> pairs) {
private void signBatch(String keyName, int keyVersion, List<SigningRequestResultPair> pairs, SignVerifyOptions options) {

String hashAlgorithm = null;

Expand All @@ -267,6 +278,13 @@ private void signBatch(String keyName, int keyVersion, List<SigningRequestResult
body.prehashed = config.prehashed.orElse(null);
}

if (options != null) {
hashAlgorithm = defaultIfNull(options.getHashAlgorithm(), hashAlgorithm);
body.signatureAlgorithm = defaultIfNull(options.getSignatureAlgorithm(), body.signatureAlgorithm);
body.prehashed = defaultIfNull(options.getPrehashed(), body.prehashed);
body.marshalingAlgorithm = defaultIfNull(options.getMarshalingAlgorithm(), null);
}

VaultTransitSign sign = vaultInternalTransitSecretEngine.sign(getToken(), keyName, hashAlgorithm, body);

for (int i = 0; i < pairs.size(); i++) {
Expand All @@ -284,21 +302,33 @@ public void verifySignature(String keyName, String signature, String input) {

@Override
public void verifySignature(String keyName, String signature, SigningInput input, TransitContext transitContext) {
verifySignature(keyName, signature, input, null, transitContext);
}

@Override
public void verifySignature(String keyName, String signature, SigningInput input, SignVerifyOptions options,
TransitContext transitContext) {
VerificationRequest item = new VerificationRequest(signature, input, transitContext);
Boolean valid = verifyBatch(keyName, singletonList(item)).get(0).getValueOrElseError();
Boolean valid = verifyBatch(keyName, singletonList(item), options).get(0).getValueOrElseError();
if (!TRUE.equals(valid)) {
throw new VaultException(INVALID_SIGNATURE);
}
}

@Override
public void verifySignature(String keyName, List<VerificationRequest> requests) {
List<VerificationResult> results = verifyBatch(keyName, requests);
verifySignature(keyName, requests, null);
}

@Override
public void verifySignature(String keyName, List<VerificationRequest> requests, SignVerifyOptions options) {
List<VerificationResult> results = verifyBatch(keyName, requests, options);
Map<VerificationRequest, VerificationResult> resultMap = zip(requests, results);
checkBatchErrors(results, errors -> new VaultVerificationBatchException(errors + " verification errors", resultMap));
}

private List<VerificationResult> verifyBatch(String keyName, List<VerificationRequest> requests) {
private List<VerificationResult> verifyBatch(String keyName, List<VerificationRequest> requests,
SignVerifyOptions options) {

String hashAlgorithm = null;

Expand All @@ -309,8 +339,15 @@ private List<VerificationResult> verifyBatch(String keyName, List<VerificationRe
if (config != null) {
keyName = config.name.orElse(keyName);
hashAlgorithm = config.hashAlgorithm.orElse(null);
body.prehashed = config.prehashed.orElse(null);
body.signatureAlgorithm = config.signatureAlgorithm.orElse(null);
body.prehashed = config.prehashed.orElse(null);
}

if (options != null) {
hashAlgorithm = defaultIfNull(options.getHashAlgorithm(), hashAlgorithm);
body.signatureAlgorithm = defaultIfNull(options.getSignatureAlgorithm(), body.signatureAlgorithm);
body.prehashed = defaultIfNull(options.getPrehashed(), body.prehashed);
body.marshalingAlgorithm = defaultIfNull(options.getMarshalingAlgorithm(), null);
}

VaultTransitVerify verify = vaultInternalTransitSecretEngine.verify(getToken(), keyName, hashAlgorithm, body);
Expand Down Expand Up @@ -484,4 +521,10 @@ private <K, T, V> Map<K, V> zip(List<K> keys, List<T> values, Function<T, V> f)
return map;
}

private <T> T defaultIfNull(T value, T defaultValue) {
if (value != null)
return value;
return defaultValue;
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit 5b92f0b

Please sign in to comment.