Add reactive support for transit operations.
Closes gh-620 Original pull request: gh-778
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 2017-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,18 +15,17 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Interface that specifies a basic set of Vault operations executed on a reactive
|
||||
@@ -123,4 +122,17 @@ public interface ReactiveVaultOperations {
|
||||
<V, T extends Publisher<V>> T doWithSession(Function<WebClient, ? extends T> 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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 2017-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,17 +15,9 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -49,6 +41,12 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
|
||||
import org.springframework.web.reactive.function.client.WebClientException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunction.ofRequestProcessor;
|
||||
|
||||
@@ -367,4 +365,14 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveVaultTransitOperations opsForTransit() {
|
||||
return opsForTransit("transit");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveVaultTransitOperations opsForTransit(String path) {
|
||||
return new ReactiveVaultTransitTemplate(this, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import org.springframework.vault.support.Ciphertext;
|
||||
import org.springframework.vault.support.Hmac;
|
||||
import org.springframework.vault.support.Plaintext;
|
||||
import org.springframework.vault.support.RawTransitKey;
|
||||
import org.springframework.vault.support.Signature;
|
||||
import org.springframework.vault.support.SignatureValidation;
|
||||
import org.springframework.vault.support.TransitKeyType;
|
||||
import org.springframework.vault.support.VaultDecryptionResult;
|
||||
import org.springframework.vault.support.VaultEncryptionResult;
|
||||
import org.springframework.vault.support.VaultHmacRequest;
|
||||
import org.springframework.vault.support.VaultSignRequest;
|
||||
import org.springframework.vault.support.VaultSignatureVerificationRequest;
|
||||
import org.springframework.vault.support.VaultTransitContext;
|
||||
import org.springframework.vault.support.VaultTransitKey;
|
||||
import org.springframework.vault.support.VaultTransitKeyConfiguration;
|
||||
import org.springframework.vault.support.VaultTransitKeyCreationRequest;
|
||||
import reactor.core.publisher.Flux;
|
||||
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}.
|
||||
*
|
||||
* @author James Luke
|
||||
*/
|
||||
public interface ReactiveVaultTransitOperations {
|
||||
|
||||
/**
|
||||
* Create a new named encryption key given a {@code name}
|
||||
* @param keyName must not be empty or {@literal null}
|
||||
*/
|
||||
Mono<Void> createKey(String keyName);
|
||||
|
||||
/**
|
||||
* Create a new named encryption key given a {@code name} and
|
||||
* {@link VaultTransitKeyCreationRequest}. The key options set here cannot be changed
|
||||
* after key creation.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param createKeyRequest must not be {@literal null}.
|
||||
*/
|
||||
Mono<Void> createKey(String keyName, VaultTransitKeyCreationRequest createKeyRequest);
|
||||
|
||||
/**
|
||||
* @return stream of transit key names.
|
||||
*/
|
||||
Flux<String> getKeys();
|
||||
|
||||
/**
|
||||
* Create a new named encryption key given a {@code name}.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param keyConfiguration must not be {@literal null}.
|
||||
*/
|
||||
Mono<Void> configureKey(String keyName, VaultTransitKeyConfiguration keyConfiguration);
|
||||
|
||||
/**
|
||||
* Returns the value of the named encryption key. Depending on the type of key,
|
||||
* different information may be returned. The key must be exportable to support this
|
||||
* 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
|
||||
*/
|
||||
Mono<RawTransitKey> 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
|
||||
*/
|
||||
Mono<VaultTransitKey> getKey(String keyName);
|
||||
|
||||
/**
|
||||
* Deletes a named encryption key. It will no longer be possible to decrypt any data
|
||||
* encrypted with the named key.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
*/
|
||||
Mono<Void> deleteKey(String keyName);
|
||||
|
||||
/**
|
||||
* Rotates the version of the named key. After rotation, new plain text requests will
|
||||
* be encrypted with the new version of the key. To upgrade ciphertext to be encrypted
|
||||
* with the latest version of the key, use {@link #rewrap(String, String)}.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @see #rewrap(String, String)
|
||||
*/
|
||||
Mono<Void> rotate(String keyName);
|
||||
|
||||
/**
|
||||
* Encrypts the provided plain text using the named key. The given {@code plaintext}
|
||||
* is encoded into bytes using the {@link java.nio.charset.Charset#defaultCharset()
|
||||
* default charset}. Use
|
||||
* {@link #encrypt(String, org.springframework.vault.support.Plaintext)} to construct
|
||||
* a {@link org.springframework.vault.support.Plaintext#of(byte[]) Plaintext} object
|
||||
* from bytes to avoid {@link java.nio.charset.Charset} mismatches.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be empty or {@literal null}.
|
||||
* @return cipher text.
|
||||
*/
|
||||
Mono<String> encrypt(String keyName, String plaintext);
|
||||
|
||||
/**
|
||||
* Encrypts the provided {@code plaintext} using the named key.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be {@literal null}.
|
||||
* @return cipher text.
|
||||
*/
|
||||
Mono<Ciphertext> encrypt(String keyName, Plaintext plaintext);
|
||||
|
||||
/**
|
||||
* Encrypts the provided {@code plaintext} using the named key.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be empty or {@literal null}.
|
||||
* @param transitRequest must not be {@literal null}. Use
|
||||
* {@link VaultTransitContext#empty()} if no request options provided.
|
||||
* @return cipher text.
|
||||
*/
|
||||
Mono<String> encrypt(String keyName, byte[] plaintext, VaultTransitContext transitRequest);
|
||||
|
||||
/**
|
||||
* Encrypts the provided batch of {@code plaintext} using the named key and context.
|
||||
* The encryption is done using transit backend's batch operation.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param batchRequest a list of {@link Plaintext} which includes plain text and an
|
||||
* optional context.
|
||||
* @return the encrypted result in the order of {@code batchRequest} plaintexts.
|
||||
*/
|
||||
Flux<VaultEncryptionResult> encrypt(String keyName, List<Plaintext> batchRequest);
|
||||
|
||||
/**
|
||||
* Decrypts the provided plain text using the named key. The decoded {@code plaintext}
|
||||
* is decoded into {@link String} the {@link java.nio.charset.Charset#defaultCharset()
|
||||
* default charset}. Use
|
||||
* {@link #decrypt(String, org.springframework.vault.support.Ciphertext)} to obtain a
|
||||
* {@link org.springframework.vault.support.Ciphertext} object that allows to control
|
||||
* the {@link java.nio.charset.Charset} for later consumption.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @return plain text.
|
||||
*/
|
||||
Mono<String> decrypt(String keyName, String ciphertext);
|
||||
|
||||
/**
|
||||
* Decrypts the provided cipher text using the named key.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be {@literal null}.
|
||||
* @return plain text.
|
||||
*/
|
||||
Mono<Plaintext> decrypt(String keyName, Ciphertext ciphertext);
|
||||
|
||||
/**
|
||||
* Decrypts the provided {@code ciphertext} using the named key.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @param transitContext must not be {@literal null}. Use
|
||||
* {@link VaultTransitContext#empty()} if no request options provided.
|
||||
* @return cipher text.
|
||||
* @return plain text.
|
||||
*/
|
||||
Mono<byte[]> decrypt(String keyName, String ciphertext, VaultTransitContext transitContext);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* optional context.
|
||||
* @return the decrypted result in the order of {@code batchRequest} ciphertexts.
|
||||
*/
|
||||
Flux<VaultDecryptionResult> decrypt(String keyName, List<Ciphertext> batchRequest);
|
||||
|
||||
/**
|
||||
* Rewrap the provided cipher text using the latest version of the named key. Because
|
||||
* this never returns plain text, it is possible to delegate this functionality to
|
||||
* untrusted users or scripts.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @return cipher text.
|
||||
* @see #rotate(String)
|
||||
*/
|
||||
Mono<String> rewrap(String keyName, String ciphertext);
|
||||
|
||||
/**
|
||||
* Rewrap the provided cipher text using the latest version of the named key. Because
|
||||
* this never returns plain text, it is possible to delegate this functionality to
|
||||
* untrusted users or scripts.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @param transitContext must not be {@literal null}. Use
|
||||
* {@link VaultTransitContext#empty()} if no request options provided.
|
||||
* @return cipher text.
|
||||
* @see #rotate(String)
|
||||
*/
|
||||
Mono<String> rewrap(String keyName, String ciphertext, VaultTransitContext transitContext);
|
||||
|
||||
/**
|
||||
* Create a HMAC using {@code keyName} of given {@link Plaintext} using the default
|
||||
* hash algorithm. The key can be of any type supported by transit; the raw key will
|
||||
* be marshaled into bytes to be used for the HMAC function. If the key is of a type
|
||||
* that supports rotation, the latest (current) version will be used.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be {@literal null}.
|
||||
* @return the digest of given data the default hash algorithm and the named key.
|
||||
*/
|
||||
Mono<Hmac> getHmac(String keyName, Plaintext plaintext);
|
||||
|
||||
/**
|
||||
* Create a HMAC using {@code keyName} of given {@link VaultHmacRequest} using the
|
||||
* default hash algorithm. The key can be of any type supported by transit; the raw
|
||||
* key will be marshaled into bytes to be used for the HMAC function. If the key is of
|
||||
* 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}.
|
||||
* @return the digest of given data the default hash algorithm and the named key.
|
||||
*/
|
||||
Mono<Hmac> getHmac(String keyName, VaultHmacRequest hmacRequest);
|
||||
|
||||
/**
|
||||
* Create a cryptographic signature using {@code keyName} of the given
|
||||
* {@link Plaintext} and the default hash algorithm. The key must be of a type that
|
||||
* supports signing.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be empty or {@literal null}.
|
||||
* @return Signature for {@link Plaintext}.
|
||||
*/
|
||||
Mono<Signature> sign(String keyName, Plaintext plaintext);
|
||||
|
||||
/**
|
||||
* Create a cryptographic signature using {@code keyName} of the given
|
||||
* {@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}.
|
||||
* @return Signature for {@link VaultSignRequest}.
|
||||
*/
|
||||
Mono<Signature> sign(String keyName, VaultSignRequest signRequest);
|
||||
|
||||
/**
|
||||
* Verify the cryptographic signature using {@code keyName} of the given
|
||||
* {@link Plaintext} and {@link Signature}.
|
||||
* @param keyName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be {@literal null}.
|
||||
* @param signature Signature to be verified, must not be {@literal null}.
|
||||
* @return {@literal true} if the signature is valid, {@literal false} otherwise.
|
||||
*/
|
||||
Mono<Boolean> verify(String keyName, Plaintext plaintext, Signature signature);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@literal null}.
|
||||
* @return the resulting {@link SignatureValidation}.
|
||||
*/
|
||||
Mono<SignatureValidation> verify(String keyName, VaultSignatureVerificationRequest verificationRequest);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.core.VaultTransitTemplate.VaultTransitKeyImpl;
|
||||
import org.springframework.vault.support.Ciphertext;
|
||||
import org.springframework.vault.support.Hmac;
|
||||
import org.springframework.vault.support.Plaintext;
|
||||
import org.springframework.vault.support.RawTransitKey;
|
||||
import org.springframework.vault.support.Signature;
|
||||
import org.springframework.vault.support.SignatureValidation;
|
||||
import org.springframework.vault.support.TransitKeyType;
|
||||
import org.springframework.vault.support.VaultDecryptionResult;
|
||||
import org.springframework.vault.support.VaultEncryptionResult;
|
||||
import org.springframework.vault.support.VaultHmacRequest;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.vault.support.VaultSignRequest;
|
||||
import org.springframework.vault.support.VaultSignatureVerificationRequest;
|
||||
import org.springframework.vault.support.VaultTransitContext;
|
||||
import org.springframework.vault.support.VaultTransitKey;
|
||||
import org.springframework.vault.support.VaultTransitKeyConfiguration;
|
||||
import org.springframework.vault.support.VaultTransitKeyCreationRequest;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.vault.core.VaultTransitTemplate.*;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ReactiveVaultTransitOperations}
|
||||
*
|
||||
* @author James Luke
|
||||
*/
|
||||
public class ReactiveVaultTransitTemplate implements ReactiveVaultTransitOperations {
|
||||
|
||||
private final ReactiveVaultOperations reactiveVaultOperations;
|
||||
|
||||
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");
|
||||
|
||||
this.reactiveVaultOperations = reactiveVaultOperations;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> encrypt(String keyName, String plaintext) {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteKey(String keyName) {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
|
||||
return this.reactiveVaultOperations.delete(String.format("%s/keys/%s", this.path, keyName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Flux<String> getKeys() {
|
||||
return this.reactiveVaultOperations.read(String.format("%s/keys?list=true", this.path))
|
||||
.flatMapIterable(it -> (List<String>) it.getRequiredData().get("keys"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> 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");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("plaintext", Base64.getEncoder().encodeToString(plaintext));
|
||||
|
||||
applyTransitOptions(transitContext, request);
|
||||
|
||||
return this.reactiveVaultOperations.write(String.format("%s/encrypt/%s", this.path, keyName), request)
|
||||
.map(it -> (String) it.getRequiredData().get("ciphertext"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Ciphertext> 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()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> decrypt(String keyName, String ciphertext) {
|
||||
Assert.hasText(keyName, "Key name must not be empty");
|
||||
Assert.hasText(ciphertext, "Ciphertext must not be empty");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Plaintext> 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()));
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("ciphertext", ciphertext);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
request.put("ciphertext", ciphertext);
|
||||
|
||||
return this.reactiveVaultOperations.write(String.format("%s/rewrap/%s", this.path, keyName), request)
|
||||
.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");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("ciphertext", ciphertext);
|
||||
|
||||
applyTransitOptions(transitContext, request);
|
||||
|
||||
return this.reactiveVaultOperations.write(String.format("%s/rewrap/%s", this.path, keyName), request)
|
||||
.map(response -> (String) response.getRequiredData().get("ciphertext"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<VaultEncryptionResult> encrypt(String keyName, List<Plaintext> 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");
|
||||
|
||||
return Flux.fromIterable(batchRequest).map(request -> {
|
||||
Map<String, String> vaultRequest = new LinkedHashMap<>(2);
|
||||
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));
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
return Flux.fromIterable(batchRequest).map(request -> {
|
||||
Map<String, String> vaultRequest = new LinkedHashMap<>(2);
|
||||
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));
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
VaultHmacRequest request = VaultHmacRequest.create(plaintext);
|
||||
|
||||
return getHmac(keyName, request);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
VaultSignRequest request = VaultSignRequest.create(plaintext);
|
||||
|
||||
return sign(keyName, request);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
VaultSignatureVerificationRequest request = VaultSignatureVerificationRequest.create(plaintext, signature);
|
||||
|
||||
return verify(keyName, request).map(SignatureValidation::isValid);
|
||||
}
|
||||
|
||||
@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();
|
||||
});
|
||||
}
|
||||
|
||||
@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()));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
* Copyright 2016-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,15 +15,7 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.Base64Utils;
|
||||
@@ -49,6 +41,13 @@ import org.springframework.vault.support.VaultTransitKey;
|
||||
import org.springframework.vault.support.VaultTransitKeyConfiguration;
|
||||
import org.springframework.vault.support.VaultTransitKeyCreationRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link VaultTransitOperations}.
|
||||
*
|
||||
@@ -383,7 +382,6 @@ 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");
|
||||
|
||||
@@ -443,7 +441,7 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
return SignatureValidation.invalid();
|
||||
}
|
||||
|
||||
private static void applyTransitOptions(VaultTransitContext context, Map<String, String> request) {
|
||||
public static void applyTransitOptions(VaultTransitContext context, Map<String, String> request) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(context.getContext())) {
|
||||
request.put("context", Base64Utils.encodeToString(context.getContext()));
|
||||
@@ -454,7 +452,7 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<VaultEncryptionResult> toEncryptionResults(VaultResponse vaultResponse,
|
||||
public static List<VaultEncryptionResult> toEncryptionResults(VaultResponse vaultResponse,
|
||||
List<Plaintext> batchRequest) {
|
||||
|
||||
List<VaultEncryptionResult> result = new ArrayList<>(batchRequest.size());
|
||||
@@ -484,7 +482,7 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<VaultDecryptionResult> toDecryptionResults(VaultResponse vaultResponse,
|
||||
public static List<VaultDecryptionResult> toDecryptionResults(VaultResponse vaultResponse,
|
||||
List<Ciphertext> batchRequest) {
|
||||
|
||||
List<VaultDecryptionResult> result = new ArrayList<>(batchRequest.size());
|
||||
@@ -523,12 +521,12 @@ public class VaultTransitTemplate implements VaultTransitOperations {
|
||||
return new VaultDecryptionResult(Plaintext.empty().with(ciphertext.getContext()));
|
||||
}
|
||||
|
||||
private static Ciphertext toCiphertext(String ciphertext, @Nullable VaultTransitContext context) {
|
||||
public static Ciphertext toCiphertext(String ciphertext, @Nullable VaultTransitContext context) {
|
||||
return context != null ? Ciphertext.of(ciphertext).with(context) : Ciphertext.of(ciphertext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) {
|
||||
public static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) {
|
||||
return (List<Map<String, String>>) vaultResponse.getRequiredData().get("batch_results");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.support;
|
||||
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Converts Plaintext to Base64 encoded string for use with
|
||||
* {@link com.fasterxml.jackson.databind.ObjectMapper}
|
||||
*
|
||||
* @author James Luke
|
||||
*/
|
||||
public class PlaintextToBase64StringConverter extends StdConverter<Plaintext, String> {
|
||||
|
||||
@Override
|
||||
public String convert(Plaintext plaintext) {
|
||||
return Base64.getEncoder().encodeToString(plaintext.getPlaintext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 2017-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.vault.support;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -25,12 +28,17 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 2017-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.vault.support;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -28,6 +30,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class VaultSignRequest {
|
||||
|
||||
@JsonProperty("input")
|
||||
@JsonSerialize(converter = PlaintextToBase64StringConverter.class)
|
||||
private final Plaintext plaintext;
|
||||
|
||||
private final @Nullable String hashAlgorithm;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
* Copyright 2016-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,8 +15,16 @@
|
||||
*/
|
||||
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.
|
||||
@@ -24,8 +32,10 @@ import org.springframework.util.Assert;
|
||||
* @author Luander Ribeiro
|
||||
* @author Mark Paluch
|
||||
* @author My-Lan Aragon
|
||||
* @author James Luke
|
||||
* @since 2.0
|
||||
*/
|
||||
@JsonSerialize(using = VaultSignatureVerificationRequestSerializer.class)
|
||||
public class VaultSignatureVerificationRequest {
|
||||
|
||||
private final Plaintext plaintext;
|
||||
@@ -135,6 +145,31 @@ 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}.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,725 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.support.Ciphertext;
|
||||
import org.springframework.vault.support.Plaintext;
|
||||
import org.springframework.vault.support.SignatureValidation;
|
||||
import org.springframework.vault.support.TransitKeyType;
|
||||
import org.springframework.vault.support.VaultDecryptionResult;
|
||||
import org.springframework.vault.support.VaultHmacRequest;
|
||||
import org.springframework.vault.support.VaultMount;
|
||||
import org.springframework.vault.support.VaultSignRequest;
|
||||
import org.springframework.vault.support.VaultSignatureVerificationRequest;
|
||||
import org.springframework.vault.support.VaultTransitContext;
|
||||
import org.springframework.vault.support.VaultTransitKeyConfiguration;
|
||||
import org.springframework.vault.support.VaultTransitKeyCreationRequest;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.RequiresVaultVersion;
|
||||
import org.springframework.vault.util.Version;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.vault.core.VaultTransitTemplateIntegrationTests.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReactiveVaultTransitTemplate} using the {@code generic}
|
||||
* backend.
|
||||
*
|
||||
* @author James Luke
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
|
||||
public class ReactiveVaultTransitIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
@Autowired
|
||||
VaultOperations vaultOperations;
|
||||
|
||||
@Autowired
|
||||
ReactiveVaultOperations reactiveVaultOperations;
|
||||
|
||||
ReactiveVaultTransitOperations reactiveTransitOperations;
|
||||
|
||||
Version vaultVersion;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
this.reactiveTransitOperations = this.reactiveVaultOperations.opsForTransit();
|
||||
|
||||
if (!this.vaultOperations.opsForSys().getMounts().containsKey("transit/")) {
|
||||
this.vaultOperations.opsForSys().mount("transit", VaultMount.create("transit"));
|
||||
}
|
||||
|
||||
this.vaultVersion = prepare().getVersion();
|
||||
|
||||
removeKeys();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
removeKeys();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private void removeKeys() {
|
||||
reactiveTransitOperations.getKeys().flatMap(this::deleteKey).blockLast();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyShouldReturnEmptyIfKeyNotExists() {
|
||||
this.reactiveTransitOperations.getKey("myKey").as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteKeyShouldFailIfKeyNotExists() {
|
||||
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();
|
||||
|
||||
this.reactiveTransitOperations.createKey("myKey")
|
||||
.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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptShouldCreateCiphertextWithNonceAndContext() {
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptShouldCreateWrappedCiphertextWithNonceAndContext() {
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptShouldCreatePlaintextWithNonceAndContext() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptShouldCreateWrappedPlaintextWithNonceAndContext() {
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
assertThat(one).isNotNull();
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(BATCH_INTRODUCED_IN_VERSION)
|
||||
void shouldBatchEncryptWithContext() {
|
||||
VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder().derived(true).build();
|
||||
|
||||
VaultTransitContext context1 = VaultTransitContext.builder().context("oneContext".getBytes()).build();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
Plaintext two = Plaintext.of("two")
|
||||
.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();
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
Plaintext two = Plaintext.of("two")
|
||||
.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();
|
||||
|
||||
assertThat(encrypted).isNotNull();
|
||||
|
||||
Ciphertext encryptedOne = encrypted.get(0);
|
||||
Ciphertext decryptedTwo = encrypted.get(1);
|
||||
|
||||
Ciphertext tampered = decryptedTwo.with(encryptedOne.getContext());
|
||||
|
||||
StepVerifier.FirstStep<VaultDecryptionResult> stepVerifier = this.reactiveTransitOperations
|
||||
.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();
|
||||
}
|
||||
else {
|
||||
stepVerifier.assertNext(it -> assertThat(it.get()).isEqualTo(one)).assertNext(it -> {
|
||||
assertThat(it.isSuccessful()).isEqualTo(false);
|
||||
assertThat(it.getCause()).isInstanceOf(VaultException.class);
|
||||
}).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION)
|
||||
void generateHmacShouldCreateHmacForRotatedKey() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION)
|
||||
void generateHmacWithCustomAlgorithmShouldCreateHmac() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateHmacWithInvalidAlgorithmShouldFail() {
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(SIGN_VERIFY_INTRODUCED_IN_VERSION)
|
||||
void signWithCustomAlgorithmShouldCreateSignature() {
|
||||
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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION)
|
||||
void shouldCreateNewExportableKey() {
|
||||
VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder()
|
||||
.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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION)
|
||||
void shouldExportEncryptionKey() {
|
||||
VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder()
|
||||
.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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION)
|
||||
void shouldNotAllowExportSigningKey() {
|
||||
|
||||
VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest.builder()
|
||||
.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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(KEY_EXPORT_INTRODUCED_IN_VERSION)
|
||||
void shouldExportEcDsaKey() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresVaultVersion(ED25519_INTRODUCED_IN_VERSION)
|
||||
void shouldExportEdKey() {
|
||||
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();
|
||||
}
|
||||
|
||||
private Mono<String> createEcdsaP256Key() {
|
||||
String keyName = "ecdsa-key";
|
||||
VaultTransitKeyCreationRequest keyCreationRequest = VaultTransitKeyCreationRequest.ofKeyType("ecdsa-p256");
|
||||
return this.reactiveTransitOperations.createKey(keyName, keyCreationRequest).thenReturn(keyName);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user