Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support providing explicit options to VaultTransitSecretEngine sign & verifySignature #17032

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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