diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java index cf4570ea..df0c9425 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultOperations.java @@ -38,6 +38,7 @@ import java.util.function.Function; * {@link #doWithVault(Function) without a session}. * * @author Mark Paluch + * @author James Luke * @since 2.0 * @see #doWithSession(Function) * @see #doWithVault(Function) @@ -48,6 +49,21 @@ import java.util.function.Function; */ public interface ReactiveVaultOperations { + /** + * @return the operations interface to interact with the Vault transit backend. + * @since 3.1 + */ + ReactiveVaultTransitOperations opsForTransit(); + + /** + * Return {@link ReactiveVaultTransitOperations} if the transit backend is mounted on + * a different path than {@code transit}. + * @param path the mount path + * @return the operations interface to interact with the Vault transit backend. + * @since 3.1 + */ + ReactiveVaultTransitOperations opsForTransit(String path); + /** * Read from a Vault path. Reading data using this method is suitable for API * calls/secret backends that do not require a request body. @@ -122,17 +138,4 @@ public interface ReactiveVaultOperations { > T doWithSession(Function sessionCallback) throws VaultException, WebClientException; - /** - * @return the operations interface to interact with the Vault transit backend. - */ - ReactiveVaultTransitOperations opsForTransit(); - - /** - * Return {@link ReactiveVaultTransitOperations} if the transit backend is mounted on - * a different path than {@code transit}. - * @param path the mount path - * @return the operations interface to interact with the Vault transit backend. - */ - ReactiveVaultTransitOperations opsForTransit(String path); - } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java index ea2f4547..aa5dbfbd 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java @@ -56,6 +56,7 @@ import static org.springframework.web.reactive.function.client.ExchangeFilterFun * * @author Mark Paluch * @author Raoof Mohammed + * @author James Luke * @see SessionManager * @since 2.0 */ @@ -226,6 +227,16 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations { })); } + @Override + public ReactiveVaultTransitOperations opsForTransit() { + return opsForTransit("transit"); + } + + @Override + public ReactiveVaultTransitOperations opsForTransit(String path) { + return new ReactiveVaultTransitTemplate(this, path); + } + @Override public Mono read(String path) { @@ -365,14 +376,4 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations { } - @Override - public ReactiveVaultTransitOperations opsForTransit() { - return opsForTransit("transit"); - } - - @Override - public ReactiveVaultTransitOperations opsForTransit(String path) { - return new ReactiveVaultTransitTemplate(this, path); - } - } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitOperations.java index 99ce4a4b..d8f36dc5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitOperations.java @@ -37,17 +37,18 @@ import reactor.core.publisher.Mono; import java.util.List; /** - * Interface that specifies a set of {@code transit} operations executed on a reactive - * infrastructure, implemented by - * {@link org.springframework.vault.core.ReactiveVaultTransitTemplate}. + * * Interface that specifies operations using the {@code transit} backend. * * @author James Luke + * @since 3.1 + * @see Transit + * Secret Backend */ public interface ReactiveVaultTransitOperations { /** - * Create a new named encryption key given a {@code name} - * @param keyName must not be empty or {@literal null} + * Create a new named encryption key given a {@code name}. + * @param keyName must not be empty or {@literal null}. */ Mono createKey(String keyName); @@ -61,7 +62,8 @@ public interface ReactiveVaultTransitOperations { Mono createKey(String keyName, VaultTransitKeyCreationRequest createKeyRequest); /** - * @return stream of transit key names. + * Get a {@link Flux} of transit key names. + * @return {@link Flux} of transit key names. */ Flux getKeys(); @@ -78,14 +80,14 @@ public interface ReactiveVaultTransitOperations { * operation. * @param keyName must not be empty or {@literal null}. * @param type must not be {@literal null}. - * @return the {@link RawTransitKey}. May be empty if key does not exist + * @return the {@link RawTransitKey}. Empty if key does not exist */ Mono exportKey(String keyName, TransitKeyType type); /** * Return information about a named encryption key. * @param keyName must not be empty or {@literal null}. - * @return the {@link VaultTransitKey}. May be empty if key does not exist + * @return the {@link VaultTransitKey}. Empty if key does not exist. */ Mono getKey(String keyName); @@ -230,10 +232,10 @@ public interface ReactiveVaultTransitOperations { * a type that supports rotation, configured {@link VaultHmacRequest#getKeyVersion()} * will be used. * @param keyName must not be empty or {@literal null}. - * @param hmacRequest the {@link VaultHmacRequest}, must not be {@literal null}. + * @param request the {@link VaultHmacRequest}, must not be {@literal null}. * @return the digest of given data the default hash algorithm and the named key. */ - Mono getHmac(String keyName, VaultHmacRequest hmacRequest); + Mono getHmac(String keyName, VaultHmacRequest request); /** * Create a cryptographic signature using {@code keyName} of the given @@ -250,10 +252,10 @@ public interface ReactiveVaultTransitOperations { * {@link VaultSignRequest} and the specified hash algorithm. The key must be of a * type that supports signing. * @param keyName must not be empty or {@literal null}. - * @param signRequest {@link VaultSignRequest} must not be empty or {@literal null}. + * @param request {@link VaultSignRequest} must not be empty or {@literal null}. * @return Signature for {@link VaultSignRequest}. */ - Mono sign(String keyName, VaultSignRequest signRequest); + Mono sign(String keyName, VaultSignRequest request); /** * Verify the cryptographic signature using {@code keyName} of the given @@ -269,10 +271,10 @@ public interface ReactiveVaultTransitOperations { * Verify the cryptographic signature using {@code keyName} of the given * {@link VaultSignRequest}. * @param keyName must not be empty or {@literal null}. - * @param verificationRequest {@link VaultSignatureVerificationRequest} must not be + * @param request {@link VaultSignatureVerificationRequest} must not be * {@literal null}. * @return the resulting {@link SignatureValidation}. */ - Mono verify(String keyName, VaultSignatureVerificationRequest verificationRequest); + Mono verify(String keyName, VaultSignatureVerificationRequest request); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitTemplate.java index 421ce2bd..35ec5b29 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTransitTemplate.java @@ -47,9 +47,10 @@ import java.util.Map; import static org.springframework.vault.core.VaultTransitTemplate.*; /** - * Default implementation of {@link ReactiveVaultTransitOperations} + * Default implementation of {@link ReactiveVaultTransitOperations}. * * @author James Luke + * @since 3.1 */ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperations { @@ -58,6 +59,7 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati private final String path; public ReactiveVaultTransitTemplate(ReactiveVaultOperations reactiveVaultOperations, String path) { + Assert.notNull(reactiveVaultOperations, "ReactiveVaultOperations must not be null"); Assert.hasText(path, "Path must not be empty"); @@ -67,6 +69,7 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono createKey(String keyName) { + Assert.hasText(keyName, "Key name must not be empty"); return this.reactiveVaultOperations.write(String.format("%s/keys/%s", this.path, keyName), null).then(); @@ -74,15 +77,17 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono createKey(String keyName, VaultTransitKeyCreationRequest createKeyRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(createKeyRequest, "VaultTransitKeyCreationRequest must not be empty"); return this.reactiveVaultOperations.write(String.format("%s/keys/%s", this.path, keyName), createKeyRequest) - .then(); + .then(); } @Override public Mono rotate(String keyName) { + Assert.hasText(keyName, "Key name must not be empty"); return this.reactiveVaultOperations.write(String.format("%s/keys/%s/rotate", this.path, keyName), null).then(); @@ -90,6 +95,7 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono encrypt(String keyName, String plaintext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(plaintext, "Plaintext must not be null"); @@ -98,20 +104,23 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati request.put("plaintext", Base64.getEncoder().encodeToString(plaintext.getBytes())); return this.reactiveVaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), request) - .map(it -> (String) it.getRequiredData().get("ciphertext")); + .map(it -> (String) it.getRequiredData().get("ciphertext")); } @Override public Mono configureKey(String keyName, VaultTransitKeyConfiguration keyConfiguration) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(keyConfiguration, "VaultKeyConfiguration must not be empty"); return this.reactiveVaultOperations - .write(String.format("%s/keys/%s/config", this.path, keyName), keyConfiguration).then(); + .write(String.format("%s/keys/%s/config", this.path, keyName), keyConfiguration) + .then(); } @Override public Mono deleteKey(String keyName) { + Assert.hasText(keyName, "Key name must not be empty"); return this.reactiveVaultOperations.delete(String.format("%s/keys/%s", this.path, keyName)); @@ -121,11 +130,12 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @SuppressWarnings("unchecked") public Flux getKeys() { return this.reactiveVaultOperations.read(String.format("%s/keys?list=true", this.path)) - .flatMapIterable(it -> (List) it.getRequiredData().get("keys")); + .flatMapIterable(it -> (List) it.getRequiredData().get("keys")); } @Override public Mono encrypt(String keyName, byte[] plaintext, VaultTransitContext transitContext) { + Assert.notNull(plaintext, "Plaintext must not be null"); Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(transitContext, "VaultTransitContext must not be null"); @@ -137,20 +147,22 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati applyTransitOptions(transitContext, request); return this.reactiveVaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), request) - .map(it -> (String) it.getRequiredData().get("ciphertext")); + .map(it -> (String) it.getRequiredData().get("ciphertext")); } @Override public Mono encrypt(String keyName, Plaintext plaintext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(plaintext, "Plaintext must not be null"); return encrypt(keyName, plaintext.getPlaintext(), plaintext.getContext()) - .map(ciphertext -> toCiphertext(ciphertext, plaintext.getContext())); + .map(ciphertext -> toCiphertext(ciphertext, plaintext.getContext())); } @Override public Mono decrypt(String keyName, String ciphertext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.hasText(ciphertext, "Ciphertext must not be empty"); @@ -159,21 +171,23 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati request.put("ciphertext", ciphertext); return this.reactiveVaultOperations.write(String.format("%s/decrypt/%s", this.path, keyName), request) - .map(it -> (String) it.getRequiredData().get("plaintext")) - .map(plaintext -> new String(Base64.getDecoder().decode(plaintext))); + .map(it -> (String) it.getRequiredData().get("plaintext")) + .map(plaintext -> new String(Base64.getDecoder().decode(plaintext))); } @Override public Mono decrypt(String keyName, Ciphertext ciphertext) { + Assert.hasText(keyName, "Key name must not be null"); Assert.notNull(ciphertext, "Ciphertext must not be null"); return decrypt(keyName, ciphertext.getCiphertext(), ciphertext.getContext()) - .map(plaintext -> Plaintext.of(plaintext).with(ciphertext.getContext())); + .map(plaintext -> Plaintext.of(plaintext).with(ciphertext.getContext())); } @Override public Mono<byte[]> decrypt(String keyName, String ciphertext, VaultTransitContext transitContext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.hasText(ciphertext, "Ciphertext must not be empty"); Assert.notNull(transitContext, "VaultTransitContext must not be null"); @@ -185,11 +199,13 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati applyTransitOptions(transitContext, request); return this.reactiveVaultOperations.write(String.format("%s/decrypt/%s", this.path, keyName), request) - .map(it -> (String) it.getRequiredData().get("plaintext")).map(Base64.getDecoder()::decode); + .map(it -> (String) it.getRequiredData().get("plaintext")) + .map(Base64.getDecoder()::decode); } @Override public Mono<String> rewrap(String keyName, String ciphertext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.hasText(ciphertext, "Ciphertext must not be empty"); @@ -197,11 +213,12 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati request.put("ciphertext", ciphertext); return this.reactiveVaultOperations.write(String.format("%s/rewrap/%s", this.path, keyName), request) - .map(response -> (String) response.getRequiredData().get("ciphertext")); + .map(response -> (String) response.getRequiredData().get("ciphertext")); } @Override public Mono<String> rewrap(String keyName, String ciphertext, VaultTransitContext transitContext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.hasText(ciphertext, "Ciphertext must not be empty"); Assert.notNull(transitContext, "VaultTransitContext must not be null"); @@ -213,7 +230,7 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati applyTransitOptions(transitContext, request); return this.reactiveVaultOperations.write(String.format("%s/rewrap/%s", this.path, keyName), request) - .map(response -> (String) response.getRequiredData().get("ciphertext")); + .map(response -> (String) response.getRequiredData().get("ciphertext")); } @Override @@ -226,14 +243,16 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati vaultRequest.put("plaintext", Base64.getEncoder().encodeToString(request.getPlaintext())); applyTransitOptions(request.getContext(), vaultRequest); return vaultRequest; - }).collectList() - .flatMap(batch -> this.reactiveVaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), - Collections.singletonMap("batch_input", batch))) - .flatMapIterable(vaultResponse -> toEncryptionResults(vaultResponse, batchRequest)); + }) + .collectList() + .flatMap(batch -> this.reactiveVaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), + Collections.singletonMap("batch_input", batch))) + .flatMapIterable(vaultResponse -> toEncryptionResults(vaultResponse, batchRequest)); } @Override public Flux<VaultDecryptionResult> decrypt(String keyName, List<Ciphertext> batchRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notEmpty(batchRequest, "BatchRequest must not be null and must have at least one entry"); @@ -242,14 +261,16 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati vaultRequest.put("ciphertext", request.getCiphertext()); applyTransitOptions(request.getContext(), vaultRequest); return vaultRequest; - }).collectList() - .flatMap(batch -> this.reactiveVaultOperations.write(String.format("%s/decrypt/%s", this.path, keyName), - Collections.singletonMap("batch_input", batch))) - .flatMapIterable(vaultResponse -> toDecryptionResults(vaultResponse, batchRequest)); + }) + .collectList() + .flatMap(batch -> this.reactiveVaultOperations.write(String.format("%s/decrypt/%s", this.path, keyName), + Collections.singletonMap("batch_input", batch))) + .flatMapIterable(vaultResponse -> toDecryptionResults(vaultResponse, batchRequest)); } @Override public Mono<Hmac> getHmac(String keyName, Plaintext plaintext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(plaintext, "Plaintext must not be null"); @@ -260,15 +281,19 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono<Hmac> getHmac(String keyName, VaultHmacRequest hmacRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(hmacRequest, "HMAC request must not be null"); - return this.reactiveVaultOperations.write(String.format("%s/hmac/%s", this.path, keyName), hmacRequest) - .map(vaultResponse -> (String) vaultResponse.getRequiredData().get("hmac")).map(Hmac::of); + return this.reactiveVaultOperations + .write(String.format("%s/hmac/%s", this.path, keyName), toRequestBody(hmacRequest)) + .map(vaultResponse -> (String) vaultResponse.getRequiredData().get("hmac")) + .map(Hmac::of); } @Override public Mono<Signature> sign(String keyName, Plaintext plaintext) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(plaintext, "Plaintext must not be null"); @@ -279,15 +304,19 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono<Signature> sign(String keyName, VaultSignRequest signRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(signRequest, "Sign request must not be null"); - return this.reactiveVaultOperations.write(String.format("%s/sign/%s", this.path, keyName), signRequest) - .map(vaultResponse -> (String) vaultResponse.getRequiredData().get("signature")).map(Signature::of); + return this.reactiveVaultOperations + .write(String.format("%s/sign/%s", this.path, keyName), toRequestBody(signRequest)) + .map(vaultResponse -> (String) vaultResponse.getRequiredData().get("signature")) + .map(Signature::of); } @Override public Mono<Boolean> verify(String keyName, Plaintext plaintext, Signature signature) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(plaintext, "Plaintext must not be null"); Assert.notNull(signature, "Signature must not be null"); @@ -299,36 +328,41 @@ public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperati @Override public Mono<SignatureValidation> verify(String keyName, VaultSignatureVerificationRequest verificationRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(verificationRequest, "Signature verification request must not be null"); return this.reactiveVaultOperations - .write(String.format("%s/verify/%s", this.path, keyName), verificationRequest) - .map(VaultResponse::getRequiredData).map(vaultResponse -> { - if (vaultResponse.containsKey("valid") && (Boolean) vaultResponse.get("valid")) { - return SignatureValidation.valid(); - } - return SignatureValidation.invalid(); - }); + .write(String.format("%s/verify/%s", this.path, keyName), toRequestBody(verificationRequest)) + .map(VaultResponse::getRequiredData) + .map(vaultResponse -> { + if (vaultResponse.containsKey("valid") && (Boolean) vaultResponse.get("valid")) { + return SignatureValidation.valid(); + } + return SignatureValidation.invalid(); + }); } @Override public Mono<RawTransitKey> exportKey(String keyName, TransitKeyType type) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(type, "Key type must not be null"); return this.reactiveVaultOperations - .read(String.format("%s/export/%s/%s", this.path, type.getValue(), keyName), - VaultTransitTemplate.RawTransitKeyImpl.class) - .flatMap(vaultResponse -> Mono.justOrEmpty(vaultResponse.getRequiredData())); + .read(String.format("%s/export/%s/%s", this.path, type.getValue(), keyName), + VaultTransitTemplate.RawTransitKeyImpl.class) + .flatMap(vaultResponse -> Mono.justOrEmpty(vaultResponse.getRequiredData())); } @Override public Mono<VaultTransitKey> getKey(String keyName) { + Assert.hasText(keyName, "Key name must not be empty"); + return this.reactiveVaultOperations - .read(String.format("%s/keys/%s", this.path, keyName), VaultTransitKeyImpl.class) - .map(VaultResponseSupport::getRequiredData); + .read(String.format("%s/keys/%s", this.path, keyName), VaultTransitKeyImpl.class) + .map(VaultResponseSupport::getRequiredData); } } 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 b5fde91a..90656c69 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 @@ -187,7 +187,7 @@ public interface VaultTransitOperations { byte[] decrypt(String keyName, String ciphertext, VaultTransitContext transitContext); /** - * Decrypts the provided barch of cipher text using the named key and context. The* + * Decrypts the provided batch of cipher text using the named key and context. The* * decryption is done using transit backend's batch operation. * @param keyName must not be empty or {@literal null}. * @param batchRequest a list of {@link Ciphertext} which includes plain text and an 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 1ab84aec..ad0e374f 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 @@ -16,9 +16,10 @@ package org.springframework.vault.core; import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.NotNull; + import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.Base64Utils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.vault.VaultException; @@ -42,6 +43,7 @@ import org.springframework.vault.support.VaultTransitKeyConfiguration; import org.springframework.vault.support.VaultTransitKeyCreationRequest; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -166,7 +168,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map<String, String> request = new LinkedHashMap<>(); - request.put("plaintext", Base64Utils.encodeToString(plaintext.getBytes())); + request.put("plaintext", Base64.getEncoder().encodeToString(plaintext.getBytes())); return (String) this.vaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), request) .getRequiredData() @@ -193,7 +195,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map<String, String> request = new LinkedHashMap<>(); - request.put("plaintext", Base64Utils.encodeToString(plaintext)); + request.put("plaintext", Base64.getEncoder().encodeToString(plaintext)); applyTransitOptions(transitContext, request); @@ -214,7 +216,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map<String, String> vaultRequest = new LinkedHashMap<>(2); - vaultRequest.put("plaintext", Base64Utils.encodeToString(request.getPlaintext())); + vaultRequest.put("plaintext", Base64.getEncoder().encodeToString(request.getPlaintext())); if (request.getContext() != null) { applyTransitOptions(request.getContext(), vaultRequest); @@ -244,7 +246,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { .getRequiredData() .get("plaintext"); - return new String(Base64Utils.decodeFromString(plaintext)); + return new String(Base64.getDecoder().decode(plaintext)); } @Override @@ -276,7 +278,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { .getRequiredData() .get("plaintext"); - return Base64Utils.decodeFromString(plaintext); + return Base64.getDecoder().decode(plaintext); } @Override @@ -355,12 +357,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(hmacRequest, "HMAC request must not be null"); - Map<String, Object> request = new LinkedHashMap<>(3); - PropertyMapper mapper = PropertyMapper.get(); - - mapper.from(hmacRequest.getPlaintext()::getPlaintext).as(Base64Utils::encodeToString).to("input", request); - mapper.from(hmacRequest::getAlgorithm).whenHasText().to("algorithm", request); - mapper.from(hmacRequest::getKeyVersion).whenNonNull().to("key_version", request); + Map<String, Object> request = toRequestBody(hmacRequest); String hmac = (String) this.vaultOperations.write(String.format("%s/hmac/%s", this.path, keyName), request) .getRequiredData() @@ -369,6 +366,20 @@ public class VaultTransitTemplate implements VaultTransitOperations { return Hmac.of(hmac); } + static Map<String, Object> toRequestBody(VaultHmacRequest hmacRequest) { + + Map<String, Object> request = new LinkedHashMap<>(3); + PropertyMapper mapper = PropertyMapper.get(); + + mapper.from(hmacRequest.getPlaintext()::getPlaintext) + .as(Base64.getEncoder()::encodeToString) + .to("input", request); + mapper.from(hmacRequest::getAlgorithm).whenHasText().to("algorithm", request); + mapper.from(hmacRequest::getKeyVersion).whenNonNull().to("key_version", request); + + return request; + } + @Override public Signature sign(String keyName, Plaintext plaintext) { @@ -382,15 +393,11 @@ public class VaultTransitTemplate implements VaultTransitOperations { @Override public Signature sign(String keyName, VaultSignRequest signRequest) { + Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(signRequest, "Sign request must not be null"); - Map<String, Object> request = new LinkedHashMap<>(3); - PropertyMapper mapper = PropertyMapper.get(); - - mapper.from(signRequest.getPlaintext()::getPlaintext).as(Base64Utils::encodeToString).to("input", request); - mapper.from(signRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request); - mapper.from(signRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request); + Map<String, Object> request = toRequestBody(signRequest); String signature = (String) this.vaultOperations.write(String.format("%s/sign/%s", this.path, keyName), request) .getRequiredData() @@ -399,6 +406,20 @@ public class VaultTransitTemplate implements VaultTransitOperations { return Signature.of(signature); } + static Map<String, Object> toRequestBody(VaultSignRequest signRequest) { + + Map<String, Object> request = new LinkedHashMap<>(3); + PropertyMapper mapper = PropertyMapper.get(); + + mapper.from(signRequest.getPlaintext()::getPlaintext) + .as(Base64.getEncoder()::encodeToString) + .to("input", request); + mapper.from(signRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request); + mapper.from(signRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request); + + return request; + } + @Override public boolean verify(String keyName, Plaintext plainText, Signature signature) { @@ -416,19 +437,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(verificationRequest, "Signature verification request must not be null"); - Map<String, Object> request = new LinkedHashMap<>(5); - PropertyMapper mapper = PropertyMapper.get(); - - mapper.from(verificationRequest.getPlaintext()::getPlaintext) - .as(Base64Utils::encodeToString) - .to("input", request); - mapper.from(verificationRequest::getHmac).whenNonNull().as(Hmac::getHmac).to("hmac", request); - mapper.from(verificationRequest::getSignature) - .whenNonNull() - .as(Signature::getSignature) - .to("signature", request); - mapper.from(verificationRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request); - mapper.from(verificationRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request); + Map<String, Object> request = toRequestBody(verificationRequest); Map<String, Object> response = this.vaultOperations .write(String.format("%s/verify/%s", this.path, keyName), request) @@ -441,19 +450,37 @@ public class VaultTransitTemplate implements VaultTransitOperations { return SignatureValidation.invalid(); } - public static void applyTransitOptions(VaultTransitContext context, Map<String, String> request) { + static Map<String, Object> toRequestBody(VaultSignatureVerificationRequest verificationRequest) { + + Map<String, Object> request = new LinkedHashMap<>(5); + PropertyMapper mapper = PropertyMapper.get(); + + mapper.from(verificationRequest.getPlaintext()::getPlaintext) + .as(Base64.getEncoder()::encodeToString) + .to("input", request); + mapper.from(verificationRequest::getHmac).whenNonNull().as(Hmac::getHmac).to("hmac", request); + mapper.from(verificationRequest::getSignature) + .whenNonNull() + .as(Signature::getSignature) + .to("signature", request); + mapper.from(verificationRequest::getHashAlgorithm).whenHasText().to("hash_algorithm", request); + mapper.from(verificationRequest::getSignatureAlgorithm).whenHasText().to("signature_algorithm", request); + + return request; + } + + static void applyTransitOptions(VaultTransitContext context, Map<String, String> request) { if (!ObjectUtils.isEmpty(context.getContext())) { - request.put("context", Base64Utils.encodeToString(context.getContext())); + request.put("context", Base64.getEncoder().encodeToString(context.getContext())); } if (!ObjectUtils.isEmpty(context.getNonce())) { - request.put("nonce", Base64Utils.encodeToString(context.getNonce())); + request.put("nonce", Base64.getEncoder().encodeToString(context.getNonce())); } } - public static List<VaultEncryptionResult> toEncryptionResults(VaultResponse vaultResponse, - List<Plaintext> batchRequest) { + static List<VaultEncryptionResult> toEncryptionResults(VaultResponse vaultResponse, List<Plaintext> batchRequest) { List<VaultEncryptionResult> result = new ArrayList<>(batchRequest.size()); List<Map<String, String>> batchData = getBatchData(vaultResponse); @@ -482,8 +509,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { return result; } - public static List<VaultDecryptionResult> toDecryptionResults(VaultResponse vaultResponse, - List<Ciphertext> batchRequest) { + static List<VaultDecryptionResult> toDecryptionResults(VaultResponse vaultResponse, List<Ciphertext> batchRequest) { List<VaultDecryptionResult> result = new ArrayList<>(batchRequest.size()); List<Map<String, String>> batchData = getBatchData(vaultResponse); @@ -514,19 +540,19 @@ public class VaultTransitTemplate implements VaultTransitOperations { if (StringUtils.hasText(data.get("plaintext"))) { - byte[] plaintext = Base64Utils.decodeFromString(data.get("plaintext")); + byte[] plaintext = Base64.getDecoder().decode(data.get("plaintext")); return new VaultDecryptionResult(Plaintext.of(plaintext).with(ciphertext.getContext())); } return new VaultDecryptionResult(Plaintext.empty().with(ciphertext.getContext())); } - public static Ciphertext toCiphertext(String ciphertext, @Nullable VaultTransitContext context) { + static Ciphertext toCiphertext(String ciphertext, @Nullable VaultTransitContext context) { return context != null ? Ciphertext.of(ciphertext).with(context) : Ciphertext.of(ciphertext); } @SuppressWarnings("unchecked") - public static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) { + static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) { return (List<Map<String, String>>) vaultResponse.getRequiredData().get("batch_results"); } 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 index 06d1af5f..ac143c12 100644 --- 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 @@ -31,14 +31,10 @@ import org.springframework.util.Assert; @JsonInclude(JsonInclude.Include.NON_NULL) public class VaultHmacRequest { - @JsonProperty("input") - @JsonSerialize(converter = PlaintextToBase64StringConverter.class) private final Plaintext plaintext; - @JsonProperty("algorithm") private final @Nullable String algorithm; - @JsonProperty("key_version") private final @Nullable Integer keyVersion; private VaultHmacRequest(Plaintext plaintext, @Nullable String algorithm, @Nullable Integer keyVersion) { 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 index 2041a248..010dc3c4 100644 --- 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 @@ -30,8 +30,6 @@ import org.springframework.util.Assert; */ public class VaultSignRequest { - @JsonProperty("input") - @JsonSerialize(converter = PlaintextToBase64StringConverter.class) private final Plaintext plaintext; private final @Nullable String hashAlgorithm; 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 index 37f2e45c..d9c04d73 100644 --- 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 @@ -15,16 +15,8 @@ */ package org.springframework.vault.support; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.vault.support.VaultSignatureVerificationRequest.VaultSignatureVerificationRequestSerializer; - -import java.io.IOException; /** * Request for a signature verification. @@ -35,7 +27,6 @@ import java.io.IOException; * @author James Luke * @since 2.0 */ -@JsonSerialize(using = VaultSignatureVerificationRequestSerializer.class) public class VaultSignatureVerificationRequest { private final Plaintext plaintext; @@ -145,31 +136,6 @@ public class VaultSignatureVerificationRequest { return getSignatureAlgorithm(); } - static class VaultSignatureVerificationRequestSerializer extends JsonSerializer<VaultSignatureVerificationRequest> { - - static PlaintextToBase64StringConverter plaintextConverter = new PlaintextToBase64StringConverter(); - - @Override - public void serialize(VaultSignatureVerificationRequest request, JsonGenerator gen, - SerializerProvider serializers) throws IOException { - gen.writeStartObject(); - gen.writeStringField("input", plaintextConverter.convert(request.plaintext)); - if (request.getHmac() != null) { - gen.writeStringField("hmac", request.getHmac().getHmac()); - } - - if (request.getSignature() != null) { - gen.writeStringField("signature", request.getSignature().getSignature()); - } - - if (StringUtils.hasText(request.getAlgorithm())) { - gen.writeStringField("algorithm", request.getAlgorithm()); - } - gen.writeEndObject(); - } - - } - /** * Builder to build a {@link VaultSignatureVerificationRequest}. */ diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTransitIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTransitIntegrationTests.java index 9f06ba4c..e68d77bd 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTransitIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/ReactiveVaultTransitIntegrationTests.java @@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.vault.core.VaultTransitTemplateIntegrationTests.*; /** - * Integration tests for {@link ReactiveVaultTransitTemplate} using the {@code generic} + * Integration tests for {@link ReactiveVaultTransitTemplate} using the {@code transit} * backend. * * @author James Luke @@ -71,6 +71,7 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport @BeforeEach void before() { + this.reactiveTransitOperations = this.reactiveVaultOperations.opsForTransit(); if (!this.vaultOperations.opsForSys().getMounts().containsKey("transit/")) { @@ -88,9 +89,11 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport } private Mono<Void> deleteKey(String keyName) { + return this.reactiveTransitOperations - .configureKey(keyName, VaultTransitKeyConfiguration.builder().deletionAllowed(true).build()) - .and(this.reactiveTransitOperations.deleteKey(keyName)).onErrorResume(e -> Mono.empty()); + .configureKey(keyName, VaultTransitKeyConfiguration.builder().deletionAllowed(true).build()) + .and(this.reactiveTransitOperations.deleteKey(keyName)) + .onErrorResume(e -> Mono.empty()); } private void removeKeys() { @@ -99,128 +102,166 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport @Test void createKeyShouldCreateKey() { - this.reactiveTransitOperations.createKey("myKey").then(this.reactiveTransitOperations.getKey("myKey")) - .as(StepVerifier::create).assertNext(myKey -> { - assertThat(myKey).isNotNull(); - assertThat(myKey.getType()).startsWith("aes"); - assertThat(myKey.getName()).isEqualTo("myKey"); - assertThat(myKey.isDeletionAllowed()).isFalse(); - assertThat(myKey.isDerived()).isFalse(); - assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); - assertThat(myKey.getLatestVersion()).isEqualTo(1); - if (this.vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.7.0"))) { - assertThat(myKey.supportsDecryption()).isTrue(); - assertThat(myKey.supportsEncryption()).isTrue(); - assertThat(myKey.supportsDerivation()).isTrue(); - assertThat(myKey.supportsSigning()).isFalse(); - } - }).verifyComplete(); + this.reactiveTransitOperations.createKey("myKey") + .then(this.reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey).isNotNull(); + assertThat(myKey.getType()).startsWith("aes"); + assertThat(myKey.getName()).isEqualTo("myKey"); + assertThat(myKey.isDeletionAllowed()).isFalse(); + assertThat(myKey.isDerived()).isFalse(); + assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); + assertThat(myKey.getLatestVersion()).isEqualTo(1); + + if (this.vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.7.0"))) { + assertThat(myKey.supportsDecryption()).isTrue(); + assertThat(myKey.supportsEncryption()).isTrue(); + assertThat(myKey.supportsDerivation()).isTrue(); + assertThat(myKey.supportsSigning()).isFalse(); + } + }) + .verifyComplete(); } @Test @RequiresVaultVersion("0.6.4") void createKeyShouldCreateEcDsaKey() { - createEcdsaP256Key().flatMap(keyName -> this.reactiveTransitOperations.getKey(keyName)).as(StepVerifier::create) - .assertNext(myKey -> { - assertThat(myKey).isNotNull(); - assertThat(myKey.getType()).startsWith("ecdsa"); - assertThat(myKey.getKeys()).isNotEmpty(); - }).verifyComplete(); + + createEcdsaP256Key().flatMap(keyName -> this.reactiveTransitOperations.getKey(keyName)) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey).isNotNull(); + assertThat(myKey.getType()).startsWith("ecdsa"); + assertThat(myKey.getKeys()).isNotEmpty(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(ED25519_INTRODUCED_IN_VERSION) void createKeyShouldCreateEdKey() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.ofKeyType("ed25519"); this.reactiveTransitOperations.createKey("ed-key", request) - .then(this.reactiveTransitOperations.getKey("ed-key")).as(StepVerifier::create).assertNext(myKey -> { - assertThat(myKey).isNotNull(); - assertThat(myKey.getType()).startsWith("ed"); - assertThat(myKey.getKeys()).isNotEmpty(); - }).verifyComplete(); + .then(this.reactiveTransitOperations.getKey("ed-key")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey).isNotNull(); + assertThat(myKey.getType()).startsWith("ed"); + assertThat(myKey.getKeys()).isNotEmpty(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(ECDSA521_INTRODUCED_IN_VERSION) void createKeyShouldCreateEcdsaKey() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p521"); this.reactiveTransitOperations.createKey("ecdsa-p521-key", request) - .then(this.reactiveTransitOperations.getKey("ecdsa-p521-key")).as(StepVerifier::create) - .assertNext(myKey -> { - assertThat(myKey.getType()).isEqualTo("ecdsa-p521"); - assertThat(myKey.getKeys()).isNotEmpty(); - }).verifyComplete(); + .then(this.reactiveTransitOperations.getKey("ecdsa-p521-key")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey.getType()).isEqualTo("ecdsa-p521"); + assertThat(myKey.getKeys()).isNotEmpty(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(RSA3072_INTRODUCED_IN_VERSION) void createKeyShouldCreateRsa3072Key() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.ofKeyType("rsa-3072"); this.reactiveTransitOperations.createKey("rsa-3072-key", request) - .then(this.reactiveTransitOperations.getKey("rsa-3072-key")).as(StepVerifier::create) - .assertNext(myKey -> { - assertThat(myKey.getType()).isEqualTo("rsa-3072"); - assertThat(myKey.getKeys()).isNotEmpty(); - }).verifyComplete(); + .then(this.reactiveTransitOperations.getKey("rsa-3072-key")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey.getType()).isEqualTo("rsa-3072"); + assertThat(myKey.getKeys()).isNotEmpty(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(AES256_GCM96_INTRODUCED_IN_VERSION) void createKeyShouldCreateAes256Gcm96Key() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.ofKeyType("aes256-gcm96"); this.reactiveTransitOperations.createKey("aes256-gcm96-key", request) - .then(this.reactiveTransitOperations.getKey("aes256-gcm96-key")).as(StepVerifier::create) - .assertNext(myKey -> { - assertThat(myKey.getType()).isEqualTo("aes256-gcm96"); - assertThat(myKey.getKeys()).isNotEmpty(); - }).verifyComplete(); + .then(this.reactiveTransitOperations.getKey("aes256-gcm96-key")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey.getType()).isEqualTo("aes256-gcm96"); + assertThat(myKey.getKeys()).isNotEmpty(); + }) + .verifyComplete(); } @Test void createKeyShouldCreateKeyWithOptions() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - this.reactiveTransitOperations.createKey("myKey", request).then(this.reactiveTransitOperations.getKey("myKey")) - .as(StepVerifier::create).assertNext(myKey -> { - assertThat(myKey.getName()).isEqualTo("myKey"); - assertThat(myKey.isDeletionAllowed()).isFalse(); - assertThat(myKey.isDerived()).isTrue(); - assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); - assertThat(myKey.getLatestVersion()).isEqualTo(1); - }).verifyComplete(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + this.reactiveTransitOperations.createKey("myKey", request) + .then(this.reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey.getName()).isEqualTo("myKey"); + assertThat(myKey.isDeletionAllowed()).isFalse(); + assertThat(myKey.isDerived()).isTrue(); + assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); + assertThat(myKey.getLatestVersion()).isEqualTo(1); + }) + .verifyComplete(); } @Test void shouldConfigureKey() { - VaultTransitKeyConfiguration configuration = VaultTransitKeyConfiguration.builder().deletionAllowed(true) - .minDecryptionVersion(1).minEncryptionVersion(2).build(); - this.reactiveTransitOperations.createKey("myKey").then(this.reactiveTransitOperations.rotate("myKey")) - .then(this.reactiveTransitOperations.rotate("myKey")) - .then(this.reactiveTransitOperations.configureKey("myKey", configuration)) - .then(this.reactiveTransitOperations.getKey("myKey")).as(StepVerifier::create).assertNext(myKey -> { - assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); - if (this.vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.8.0"))) { - assertThat(myKey.getMinEncryptionVersion()).isEqualTo(2); - } - else { - assertThat(myKey.getMinEncryptionVersion()).isEqualTo(0); - } - }).verifyComplete(); + VaultTransitKeyConfiguration configuration = VaultTransitKeyConfiguration.builder() + .deletionAllowed(true) + .minDecryptionVersion(1) + .minEncryptionVersion(2) + .build(); + + this.reactiveTransitOperations.createKey("myKey") + .then(this.reactiveTransitOperations.rotate("myKey")) + .then(this.reactiveTransitOperations.rotate("myKey")) + .then(this.reactiveTransitOperations.configureKey("myKey", configuration)) + .then(this.reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .assertNext(myKey -> { + assertThat(myKey.getMinDecryptionVersion()).isEqualTo(1); + if (this.vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.8.0"))) { + assertThat(myKey.getMinEncryptionVersion()).isEqualTo(2); + } + else { + assertThat(myKey.getMinEncryptionVersion()).isEqualTo(0); + } + }) + .verifyComplete(); } @Test @RequiresVaultVersion("0.6.4") void shouldEnumerateKey() { + this.reactiveTransitOperations.getKeys().as(StepVerifier::create).verifyComplete(); - this.reactiveTransitOperations.createKey("myKey").thenMany(this.reactiveTransitOperations.getKeys()) - .as(StepVerifier::create).assertNext(keys -> assertThat(keys).contains("myKey")).verifyComplete(); + this.reactiveTransitOperations.createKey("myKey") + .thenMany(this.reactiveTransitOperations.getKeys()) + .as(StepVerifier::create) + .assertNext(keys -> assertThat(keys).contains("myKey")) + .verifyComplete(); } @Test @@ -230,178 +271,240 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport @Test void deleteKeyShouldFailIfKeyNotExists() { - this.reactiveTransitOperations.deleteKey("myKey").as(StepVerifier::create) - .consumeErrorWith(e -> assertThat(e).hasMessageContaining("Status 400")).verify(); + this.reactiveTransitOperations.deleteKey("myKey") + .as(StepVerifier::create) + .consumeErrorWith(e -> assertThat(e).hasMessageContaining("Status 400")) + .verify(); } @Test void deleteKeyShouldDeleteKey() { - VaultTransitKeyConfiguration configuration = VaultTransitKeyConfiguration.builder().deletionAllowed(true) - .build(); + + VaultTransitKeyConfiguration configuration = VaultTransitKeyConfiguration.builder() + .deletionAllowed(true) + .build(); this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.configureKey("myKey", configuration)) - .then(this.reactiveTransitOperations.deleteKey("myKey")) - .then(this.reactiveTransitOperations.getKey("myKey")).as(StepVerifier::create).verifyComplete(); + .then(this.reactiveTransitOperations.configureKey("myKey", configuration)) + .then(this.reactiveTransitOperations.deleteKey("myKey")) + .then(this.reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .verifyComplete(); } @Test void encryptShouldCreateCiphertext() { + this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")).as(StepVerifier::create) - .assertNext(ciphertext -> assertThat(ciphertext).startsWith("vault:v")).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")) + .as(StepVerifier::create) + .assertNext(ciphertext -> assertThat(ciphertext).startsWith("vault:v")) + .verifyComplete(); } @Test void encryptShouldCreateCiphertextWithNonceAndContext() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - VaultTransitContext context = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext context = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); this.reactiveTransitOperations.createKey("mykey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), context)) - .as(StepVerifier::create).assertNext(ciphertext -> assertThat(ciphertext).startsWith("vault:v1:")) - .verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), context)) + .as(StepVerifier::create) + .assertNext(ciphertext -> assertThat(ciphertext).startsWith("vault:v1:")) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void encryptShouldEncryptEmptyValues() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - VaultTransitContext context = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext context = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("").with(context))) - .as(StepVerifier::create).assertNext(ciphertext -> { - assertThat(ciphertext.getCiphertext()).startsWith("vault:v1:"); - assertThat(ciphertext.getContext()).isEqualTo(context); - }).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("").with(context))) + .as(StepVerifier::create) + .assertNext(ciphertext -> { + assertThat(ciphertext.getCiphertext()).startsWith("vault:v1:"); + assertThat(ciphertext.getContext()).isEqualTo(context); + }) + .verifyComplete(); } @Test void encryptShouldCreateWrappedCiphertextWithNonceAndContext() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - VaultTransitContext context = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext context = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("hello-world").with(context))) - .as(StepVerifier::create).assertNext(ciphertext -> { - assertThat(ciphertext.getCiphertext()).startsWith("vault:v1:"); - assertThat(ciphertext.getContext()).isEqualTo(context); - }).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("hello-world").with(context))) + .as(StepVerifier::create) + .assertNext(ciphertext -> { + assertThat(ciphertext.getCiphertext()).startsWith("vault:v1:"); + assertThat(ciphertext.getContext()).isEqualTo(context); + }) + .verifyComplete(); } @Test void decryptShouldCreatePlaintext() { + this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")) - .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext)) - .as(StepVerifier::create).assertNext(plaintext -> assertThat(plaintext).isEqualTo("hello-world")) - .verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")) + .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext)) + .as(StepVerifier::create) + .assertNext(plaintext -> assertThat(plaintext).isEqualTo("hello-world")) + .verifyComplete(); } @Test void decryptShouldCreatePlaintextWithNonceAndContext() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - VaultTransitContext transitRequest = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext transitRequest = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), transitRequest)) - .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext, transitRequest)) - .as(StepVerifier::create) - .assertNext(plaintext -> assertThat(new String(plaintext)).isEqualTo("hello-world")).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), transitRequest)) + .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext, transitRequest)) + .as(StepVerifier::create) + .assertNext(plaintext -> assertThat(new String(plaintext)).isEqualTo("hello-world")) + .verifyComplete(); } @Test void decryptShouldCreateWrappedPlaintextWithNonceAndContext() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - VaultTransitContext context = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext context = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("hello-world").with(context))) - .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext)) - .as(StepVerifier::create).assertNext(plaintext -> { - assertThat(plaintext.asString()).isEqualTo("hello-world"); - assertThat(plaintext.getContext()).isEqualTo(context); - }).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("hello-world").with(context))) + .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext)) + .as(StepVerifier::create) + .assertNext(plaintext -> { + assertThat(plaintext.asString()).isEqualTo("hello-world"); + assertThat(plaintext.getContext()).isEqualTo(context); + }) + .verifyComplete(); } @Test void encryptAndRewrapShouldCreateCiphertext() { + String ciphertext = this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")).block(); - - assertThat(ciphertext).isNotNull(); - - this.reactiveTransitOperations.rotate("myKey").then(this.reactiveTransitOperations.rewrap("myKey", ciphertext)) - .as(StepVerifier::create).assertNext(rewrapped -> assertThat(rewrapped).startsWith("vault:v2:")) - .verifyComplete(); - } - - @Test - void shouldEncryptBinaryPlaintext() { - this.reactiveTransitOperations.createKey("myKey"); - byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; - - this.reactiveTransitOperations.encrypt("myKey", plaintext, VaultTransitContext.empty()) - .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext, - VaultTransitContext.empty())) - .as(StepVerifier::create).assertNext(decrypted -> assertThat(decrypted).isEqualTo(plaintext)) - .verifyComplete(); - } - - @Test - void encryptAndRewrapShouldCreateCiphertextWithNonceAndContext() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().convergentEncryption(true) - .derived(true).build(); - - VaultTransitContext transitRequest = VaultTransitContext.builder().context("blubb".getBytes()) - .nonce("123456789012".getBytes()).build(); - - String ciphertext = this.reactiveTransitOperations.createKey("myKey", request) - .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), transitRequest)) - .block(); + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world")) + .block(); assertThat(ciphertext).isNotNull(); this.reactiveTransitOperations.rotate("myKey") - .then(this.reactiveTransitOperations.rewrap("myKey", ciphertext, transitRequest)) - .as(StepVerifier::create).assertNext(rewrapped -> assertThat(rewrapped).startsWith("vault:v2")) - .verifyComplete(); + .then(this.reactiveTransitOperations.rewrap("myKey", ciphertext)) + .as(StepVerifier::create) + .assertNext(rewrapped -> assertThat(rewrapped).startsWith("vault:v2:")) + .verifyComplete(); + } + + @Test + void shouldEncryptBinaryPlaintext() { + + this.reactiveTransitOperations.createKey("myKey").as(StepVerifier::create).verifyComplete(); + + byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; + + this.reactiveTransitOperations.encrypt("myKey", plaintext, VaultTransitContext.empty()) + .flatMap(ciphertext -> this.reactiveTransitOperations.decrypt("myKey", ciphertext, + VaultTransitContext.empty())) + .as(StepVerifier::create) + .assertNext(decrypted -> assertThat(decrypted).isEqualTo(plaintext)) + .verifyComplete(); + } + + @Test + void encryptAndRewrapShouldCreateCiphertextWithNonceAndContext() { + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true) + .derived(true) + .build(); + + VaultTransitContext transitRequest = VaultTransitContext.builder() + .context("blubb".getBytes()) + .nonce("123456789012".getBytes()) + .build(); + + String ciphertext = this.reactiveTransitOperations.createKey("myKey", request) + .then(this.reactiveTransitOperations.encrypt("myKey", "hello-world".getBytes(), transitRequest)) + .block(); + + assertThat(ciphertext).isNotNull(); + + this.reactiveTransitOperations.rotate("myKey") + .then(this.reactiveTransitOperations.rewrap("myKey", ciphertext, transitRequest)) + .as(StepVerifier::create) + .assertNext(rewrapped -> assertThat(rewrapped).startsWith("vault:v2")) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchEncrypt() { + this.reactiveTransitOperations.createKey("myKey") - .thenMany(this.reactiveTransitOperations.encrypt("myKey", - Arrays.asList(Plaintext.of("one"), Plaintext.of("two")))) - .as(StepVerifier::create).assertNext(encrypted -> { - assertThat(encrypted.get()).isNotNull(); - assertThat(Objects.requireNonNull(encrypted.get()).getCiphertext()).startsWith("vault:"); - }).assertNext(encrypted -> { - assertThat(encrypted.get()).isNotNull(); - assertThat(Objects.requireNonNull(encrypted.get()).getCiphertext()).startsWith("vault:"); - }).verifyComplete(); + .thenMany(this.reactiveTransitOperations.encrypt("myKey", + Arrays.asList(Plaintext.of("one"), Plaintext.of("two")))) + .as(StepVerifier::create) + .assertNext(encrypted -> { + assertThat(encrypted.get()).isNotNull(); + assertThat(Objects.requireNonNull(encrypted.get()).getCiphertext()).startsWith("vault:"); + }) + .assertNext(encrypted -> { + assertThat(encrypted.get()).isNotNull(); + assertThat(Objects.requireNonNull(encrypted.get()).getCiphertext()).startsWith("vault:"); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchDecrypt() { + this.reactiveTransitOperations.createKey("myKey").block(); Ciphertext one = this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("one")).block(); Ciphertext two = this.reactiveTransitOperations.encrypt("myKey", Plaintext.of("two")).block(); @@ -410,20 +513,24 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport assertThat(two).isNotNull(); this.reactiveTransitOperations.decrypt("myKey", Arrays.asList(one, two)) - .zipWith(Flux.merge(this.reactiveTransitOperations.decrypt("myKey", one), - this.reactiveTransitOperations.decrypt("myKey", two))) - .as(StepVerifier::create).assertNext(it -> { - assertThat(it.getT1().getAsString()).isEqualTo(it.getT2().asString()); - assertThat(it.getT1().getAsString()).isEqualTo("one"); - }).assertNext(it -> { - assertThat(it.getT1().getAsString()).isEqualTo(it.getT2().asString()); - assertThat(it.getT1().getAsString()).isEqualTo("two"); - }).verifyComplete(); + .zipWith(Flux.merge(this.reactiveTransitOperations.decrypt("myKey", one), + this.reactiveTransitOperations.decrypt("myKey", two))) + .as(StepVerifier::create) + .assertNext(it -> { + assertThat(it.getT1().getAsString()).isEqualTo(it.getT2().asString()); + assertThat(it.getT1().getAsString()).isEqualTo("one"); + }) + .assertNext(it -> { + assertThat(it.getT1().getAsString()).isEqualTo(it.getT2().asString()); + assertThat(it.getT1().getAsString()).isEqualTo("two"); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchEncryptWithContext() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().derived(true).build(); VaultTransitContext context1 = VaultTransitContext.builder().context("oneContext".getBytes()).build(); @@ -431,47 +538,54 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport VaultTransitContext context2 = VaultTransitContext.builder().context("twoContext".getBytes()).build(); this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(this.reactiveTransitOperations.encrypt("myKey", - Arrays.asList(Plaintext.of("one").with(context1), Plaintext.of("two").with(context2)))) - .as(StepVerifier::create) - .assertNext(it -> assertThat(Objects.requireNonNull(it.get()).getContext()).isEqualTo(context1)) - .assertNext(it -> assertThat(Objects.requireNonNull(it.get()).getContext()).isEqualTo(context2)) - .verifyComplete(); + .thenMany(this.reactiveTransitOperations.encrypt("myKey", + Arrays.asList(Plaintext.of("one").with(context1), Plaintext.of("two").with(context2)))) + .as(StepVerifier::create) + .assertNext(it -> assertThat(Objects.requireNonNull(it.get()).getContext()).isEqualTo(context1)) + .assertNext(it -> assertThat(Objects.requireNonNull(it.get()).getContext()).isEqualTo(context2)) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchDecryptWithContext() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().derived(true).build(); Plaintext one = Plaintext.of("one") - .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); + .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); Plaintext two = Plaintext.of("two") - .with(VaultTransitContext.builder().context("twoContext".getBytes()).build()); + .with(VaultTransitContext.builder().context("twoContext".getBytes()).build()); this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(this.reactiveTransitOperations.encrypt("myKey", Arrays.asList(one, two))) - .flatMap(it -> Mono.justOrEmpty(it.get())).collectList() - .flatMapMany(it -> this.reactiveTransitOperations.decrypt("myKey", it)).as(StepVerifier::create) - .assertNext(it -> assertThat(it.get()).isEqualTo(one)) - .assertNext(it -> assertThat(it.get()).isEqualTo(two)).verifyComplete(); + .thenMany(this.reactiveTransitOperations.encrypt("myKey", Arrays.asList(one, two))) + .flatMap(it -> Mono.justOrEmpty(it.get())) + .collectList() + .flatMapMany(it -> this.reactiveTransitOperations.decrypt("myKey", it)) + .as(StepVerifier::create) + .assertNext(it -> assertThat(it.get()).isEqualTo(one)) + .assertNext(it -> assertThat(it.get()).isEqualTo(two)) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchDecryptWithWrongContext() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().derived(true).build(); Plaintext one = Plaintext.of("one") - .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); + .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); Plaintext two = Plaintext.of("two") - .with(VaultTransitContext.builder().context("twoContext".getBytes()).build()); + .with(VaultTransitContext.builder().context("twoContext".getBytes()).build()); List<Ciphertext> encrypted = this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(this.reactiveTransitOperations.encrypt("myKey", Arrays.asList(one, two))) - .flatMap(it -> Mono.justOrEmpty(it.get())).collectList().block(); + .thenMany(this.reactiveTransitOperations.encrypt("myKey", Arrays.asList(one, two))) + .flatMap(it -> Mono.justOrEmpty(it.get())) + .collectList() + .block(); assertThat(encrypted).isNotNull(); @@ -481,7 +595,8 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport Ciphertext tampered = decryptedTwo.with(encryptedOne.getContext()); StepVerifier.FirstStep<VaultDecryptionResult> stepVerifier = this.reactiveTransitOperations - .decrypt("myKey", Arrays.asList(encryptedOne, tampered)).as(StepVerifier::create); + .decrypt("myKey", Arrays.asList(encryptedOne, tampered)) + .as(StepVerifier::create); if (this.vaultVersion.isGreaterThanOrEqualTo(Version.parse("1.6.0"))) { stepVerifier.consumeErrorWith(e -> assertThat(e).hasMessageContaining("error")).verify(); @@ -497,185 +612,243 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchDecryptEmptyPlaintext() { + this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.empty())) - .flatMapMany(empty -> this.reactiveTransitOperations.decrypt("myKey", Collections.singletonList(empty))) - .as(StepVerifier::create).assertNext(it -> assertThat(it.getAsString()).isEmpty()).verifyComplete(); + .then(this.reactiveTransitOperations.encrypt("myKey", Plaintext.empty())) + .flatMapMany(empty -> this.reactiveTransitOperations.decrypt("myKey", Collections.singletonList(empty))) + .as(StepVerifier::create) + .assertNext(it -> assertThat(it.getAsString()).isEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION) void shouldBatchDecryptEmptyPlaintextWithContext() { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().derived(true).build(); Plaintext empty = Plaintext.empty() - .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); + .with(VaultTransitContext.builder().context("oneContext".getBytes()).build()); this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(this.reactiveTransitOperations.encrypt("myKey", Collections.singletonList(empty))) - .flatMap(it -> Mono.justOrEmpty(it.get())).collectList() - .flatMapMany(it -> this.reactiveTransitOperations.decrypt("myKey", it)).as(StepVerifier::create) - .assertNext(it -> assertThat(it.get()).isEqualTo(empty)).verifyComplete(); + .thenMany(this.reactiveTransitOperations.encrypt("myKey", Collections.singletonList(empty))) + .flatMap(it -> Mono.justOrEmpty(it.get())) + .collectList() + .flatMapMany(it -> this.reactiveTransitOperations.decrypt("myKey", it)) + .as(StepVerifier::create) + .assertNext(it -> assertThat(it.get()).isEqualTo(empty)) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void generateHmacShouldCreateHmac() { + createEcdsaP256Key() - .flatMap(keyName -> this.reactiveTransitOperations.getHmac(keyName, Plaintext.of("hello-world"))) - .as(StepVerifier::create).assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()).verifyComplete(); + .flatMap(keyName -> this.reactiveTransitOperations.getHmac(keyName, Plaintext.of("hello-world"))) + .as(StepVerifier::create) + .assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void generateHmacShouldCreateHmacForRotatedKey() { - VaultHmacRequest request = VaultHmacRequest.builder().plaintext(Plaintext.of("hello-world")).keyVersion(2) - .build(); + + VaultHmacRequest request = VaultHmacRequest.builder() + .plaintext(Plaintext.of("hello-world")) + .keyVersion(2) + .build(); createEcdsaP256Key() - .flatMap(keyName -> this.reactiveTransitOperations.rotate(keyName) - .then(this.reactiveTransitOperations.getHmac(keyName, request))) - .as(StepVerifier::create).assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()).verifyComplete(); + .flatMap(keyName -> this.reactiveTransitOperations.rotate(keyName) + .then(this.reactiveTransitOperations.getHmac(keyName, request))) + .as(StepVerifier::create) + .assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void generateHmacWithCustomAlgorithmShouldCreateHmac() { - VaultHmacRequest request = VaultHmacRequest.builder().plaintext(Plaintext.of("hello-world")) - .algorithm("sha2-512").build(); + + VaultHmacRequest request = VaultHmacRequest.builder() + .plaintext(Plaintext.of("hello-world")) + .algorithm("sha2-512") + .build(); createEcdsaP256Key().flatMap(keyName -> this.reactiveTransitOperations.getHmac(keyName, request)) - .as(StepVerifier::create).assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()).verifyComplete(); + .as(StepVerifier::create) + .assertNext(hmac -> assertThat(hmac.getHmac()).isNotEmpty()) + .verifyComplete(); } @Test void generateHmacWithInvalidAlgorithmShouldFail() { - VaultHmacRequest request = VaultHmacRequest.builder().plaintext(Plaintext.of("hello-world")) - .algorithm("blah-512").build(); + + VaultHmacRequest request = VaultHmacRequest.builder() + .plaintext(Plaintext.of("hello-world")) + .algorithm("blah-512") + .build(); createEcdsaP256Key().flatMap(keyName -> this.reactiveTransitOperations.getHmac("myKey", request)) - .as(StepVerifier::create).consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)) - .verify(); + .as(StepVerifier::create) + .consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)) + .verify(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void signShouldCreateSignature() { + createEcdsaP256Key() - .flatMap(keyName -> this.reactiveTransitOperations.sign(keyName, Plaintext.of("hello-world"))) - .as(StepVerifier::create).assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) - .verifyComplete(); + .flatMap(keyName -> this.reactiveTransitOperations.sign(keyName, Plaintext.of("hello-world"))) + .as(StepVerifier::create) + .assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(ED25519_INTRODUCED_IN_VERSION) void signShouldCreateSignatureUsingEd25519() { + VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ed25519"); this.reactiveTransitOperations.createKey("ed-key", keyCreationRequest) - .then(this.reactiveTransitOperations.sign("ed-key", Plaintext.of("hello-world"))) - .as(StepVerifier::create).assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) - .verifyComplete(); + .then(this.reactiveTransitOperations.sign("ed-key", Plaintext.of("hello-world"))) + .as(StepVerifier::create) + .assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) + .verifyComplete(); } @Test void signWithInvalidKeyFormatShouldFail() { + this.reactiveTransitOperations.createKey("myKey") - .then(this.reactiveTransitOperations.sign("myKey", Plaintext.of("hello-world"))) - .as(StepVerifier::create).consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)) - .verify(); + .then(this.reactiveTransitOperations.sign("myKey", Plaintext.of("hello-world"))) + .as(StepVerifier::create) + .consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)) + .verify(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void signWithCustomAlgorithmShouldCreateSignature() { - VaultSignRequest request = VaultSignRequest.builder().plaintext(Plaintext.of("hello-world")) - .signatureAlgorithm("sha2-512").build(); + + VaultSignRequest request = VaultSignRequest.builder() + .plaintext(Plaintext.of("hello-world")) + .signatureAlgorithm("sha2-512") + .build(); createEcdsaP256Key().flatMap(keyName -> this.reactiveTransitOperations.sign(keyName, request)) - .as(StepVerifier::create).assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) - .verifyComplete(); + .as(StepVerifier::create) + .assertNext(signature -> assertThat(signature.getSignature()).isNotEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void shouldVerifyValidSignature() { + Plaintext plaintext = Plaintext.of("hello-world"); createEcdsaP256Key() - .flatMap(keyName -> this.reactiveTransitOperations.sign(keyName, plaintext) - .flatMap(signature -> this.reactiveTransitOperations.verify(keyName, plaintext, signature))) - .as(StepVerifier::create).assertNext(valid -> assertThat(valid).isTrue()).verifyComplete(); + .flatMap(keyName -> this.reactiveTransitOperations.sign(keyName, plaintext) + .flatMap(signature -> this.reactiveTransitOperations.verify(keyName, plaintext, signature))) + .as(StepVerifier::create) + .assertNext(valid -> assertThat(valid).isTrue()) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void shouldVerifyValidHmac() { + Plaintext plaintext = Plaintext.of("hello-world"); createEcdsaP256Key() - .flatMap(keyName -> this.reactiveTransitOperations.getHmac(keyName, plaintext) - .flatMap(hmac -> this.reactiveTransitOperations.verify(keyName, - VaultSignatureVerificationRequest.create(plaintext, hmac)))) - .as(StepVerifier::create).assertNext(valid -> assertThat(valid).isEqualTo(SignatureValidation.valid())) - .verifyComplete(); + .flatMap(keyName -> this.reactiveTransitOperations.getHmac(keyName, plaintext) + .flatMap(hmac -> this.reactiveTransitOperations.verify(keyName, + VaultSignatureVerificationRequest.create(plaintext, hmac)))) + .as(StepVerifier::create) + .assertNext(valid -> assertThat(valid).isEqualTo(SignatureValidation.valid())) + .verifyComplete(); } @Test @RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION) void shouldVerifyValidSignatureWithCustomAlgorithm() { + Plaintext plaintext = Plaintext.of("hello-world"); - VaultSignRequest request = VaultSignRequest.builder().plaintext(plaintext).signatureAlgorithm("sha2-512") - .build(); + VaultSignRequest request = VaultSignRequest.builder() + .plaintext(plaintext) + .signatureAlgorithm("sha2-512") + .build(); createEcdsaP256Key() - .flatMap((keyName) -> this.reactiveTransitOperations.sign(keyName, request) - .map(signature -> VaultSignatureVerificationRequest.builder().signatureAlgorithm("sha2-512") - .plaintext(plaintext).signature(signature).build()) - .flatMap(verificationRequest -> this.reactiveTransitOperations.verify(keyName, - verificationRequest))) - .as(StepVerifier::create).assertNext(valid -> assertThat(valid).isEqualTo(SignatureValidation.valid())) - .verifyComplete(); + .flatMap((keyName) -> this.reactiveTransitOperations.sign(keyName, request) + .map(signature -> VaultSignatureVerificationRequest.builder() + .signatureAlgorithm("sha2-512") + .plaintext(plaintext) + .signature(signature) + .build()) + .flatMap(verificationRequest -> this.reactiveTransitOperations.verify(keyName, verificationRequest))) + .as(StepVerifier::create) + .assertNext(valid -> assertThat(valid).isEqualTo(SignatureValidation.valid())) + .verifyComplete(); } @Test @RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION) void shouldCreateNewExportableKey() { + VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder() - .exportable(true).derived(true).build(); + .exportable(true) + .derived(true) + .build(); reactiveTransitOperations.createKey("myKey", vaultTransitKeyCreationRequest) - .then(reactiveTransitOperations.getKey("myKey")).as(StepVerifier::create) - .assertNext(vaultTransitKey -> { - assertThat(vaultTransitKey.getName()).isEqualTo("myKey"); - assertThat(vaultTransitKey.isExportable()).isTrue(); - }).verifyComplete(); + .then(reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .assertNext(vaultTransitKey -> { + assertThat(vaultTransitKey.getName()).isEqualTo("myKey"); + assertThat(vaultTransitKey.isExportable()).isTrue(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION) void shouldCreateNotExportableKeyByDefault() { - reactiveTransitOperations.createKey("myKey").then(reactiveTransitOperations.getKey("myKey")) - .as(StepVerifier::create).assertNext(vaultTransitKey -> { - assertThat(vaultTransitKey.getName()).isEqualTo("myKey"); - assertThat(vaultTransitKey.isExportable()).isFalse(); - }).verifyComplete(); + + reactiveTransitOperations.createKey("myKey") + .then(reactiveTransitOperations.getKey("myKey")) + .as(StepVerifier::create) + .assertNext(vaultTransitKey -> { + assertThat(vaultTransitKey.getName()).isEqualTo("myKey"); + assertThat(vaultTransitKey.isExportable()).isFalse(); + }) + .verifyComplete(); } @Test @RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION) void shouldExportEncryptionKey() { + VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder() - .exportable(true).build(); + .exportable(true) + .build(); reactiveTransitOperations.createKey("myKey", vaultTransitKeyCreationRequest) - .then(reactiveTransitOperations.exportKey("myKey", TransitKeyType.ENCRYPTION_KEY)) - .as(StepVerifier::create).assertNext(rawTransitKey -> { - assertThat(rawTransitKey.getName()).isEqualTo("myKey"); - assertThat(rawTransitKey.getKeys()).isNotEmpty(); - assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); - }).verifyComplete(); + .then(reactiveTransitOperations.exportKey("myKey", TransitKeyType.ENCRYPTION_KEY)) + .as(StepVerifier::create) + .assertNext(rawTransitKey -> { + assertThat(rawTransitKey.getName()).isEqualTo("myKey"); + assertThat(rawTransitKey.getKeys()).isNotEmpty(); + assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); + }) + .verifyComplete(); } @Test @@ -683,40 +856,54 @@ public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport void shouldNotAllowExportSigningKey() { VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder() - .exportable(true).build(); + .exportable(true) + .build(); reactiveTransitOperations.createKey("myKey", vaultTransitKeyCreationRequest) - .then(reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY)).as(StepVerifier::create) - .consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)).verify(); + .then(reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY)) + .as(StepVerifier::create) + .consumeErrorWith(e -> assertThat(e).isInstanceOf(VaultException.class)) + .verify(); } @Test @RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION) void shouldExportEcDsaKey() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().type("ecdsa-p256") - .exportable(true).build(); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .type("ecdsa-p256") + .exportable(true) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(Flux.merge(this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.HMAC_KEY), - this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY))) - .as(StepVerifier::create).assertNext(hmacKey -> assertThat(hmacKey.getKeys()).isNotEmpty()) - .assertNext(signingKey -> assertThat(signingKey.getKeys()).isNotEmpty()).verifyComplete(); + .thenMany(Flux.merge(this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.HMAC_KEY), + this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY))) + .as(StepVerifier::create) + .assertNext(hmacKey -> assertThat(hmacKey.getKeys()).isNotEmpty()) + .assertNext(signingKey -> assertThat(signingKey.getKeys()).isNotEmpty()) + .verifyComplete(); } @Test @RequiresVaultVersion(ED25519_INTRODUCED_IN_VERSION) void shouldExportEdKey() { - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().type("ed25519") - .exportable(true).build(); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .type("ed25519") + .exportable(true) + .build(); this.reactiveTransitOperations.createKey("myKey", request) - .thenMany(Flux.merge(this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.HMAC_KEY), - this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY))) - .as(StepVerifier::create).assertNext(hmacKey -> assertThat(hmacKey.getKeys()).isNotEmpty()) - .assertNext(signingKey -> assertThat(signingKey.getKeys()).isNotEmpty()).verifyComplete(); + .thenMany(Flux.merge(this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.HMAC_KEY), + this.reactiveTransitOperations.exportKey("myKey", TransitKeyType.SIGNING_KEY))) + .as(StepVerifier::create) + .assertNext(hmacKey -> assertThat(hmacKey.getKeys()).isNotEmpty()) + .assertNext(signingKey -> assertThat(signingKey.getKeys()).isNotEmpty()) + .verifyComplete(); } private Mono<String> createEcdsaP256Key() { + String keyName = "ecdsa-key"; VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256"); return this.reactiveTransitOperations.createKey(keyName, keyCreationRequest).thenReturn(keyName);