From 8ec69987bdfa079fcf22f3d5334dc7a088240f41 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 18 Sep 2017 10:42:00 +0200 Subject: [PATCH] Polishing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename VaultDecryptionPayload to Ciphertext and VaultEncryptionPayload to Plaintext. Move methods of VaultEncryptionDecryptionResultHelper to VaultTrainsitTemplate. Extract common base class from VaultDecryptionResult and VaultEncryptionResult. Refactor value objects to immutable objects. Create encrypt(…) and decrypt(…) methods interchanging Plaintext and Ciphertext objects. Generate equals/hashcode methods for Plaintext, Ciphertext and VaultTransitContext. Simplify tests. Javadoc, license headers, formatting, typo fixes. Original pull request: gh-138. Related ticket: gh-137. --- .../vault/core/VaultTransitOperations.java | 56 ++-- .../vault/core/VaultTransitTemplate.java | 193 ++++++++++--- .../vault/support/AbstractResult.java | 93 +++++++ .../vault/support/Ciphertext.java | 73 +++++ .../vault/support/Plaintext.java | 99 +++++++ .../vault/support/VaultDecryptionPayload.java | 47 ---- .../vault/support/VaultDecryptionResult.java | 81 ++++-- ...VaultEncryptionDecryptionResultHelper.java | 55 ---- .../vault/support/VaultEncryptionPayload.java | 66 ----- .../vault/support/VaultEncryptionResult.java | 67 +++-- .../vault/support/VaultTransitContext.java | 3 + .../VaultTransitTemplateIntegrationTests.java | 263 +++++++++--------- src/main/asciidoc/new-features.adoc | 2 +- 13 files changed, 684 insertions(+), 414 deletions(-) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/Ciphertext.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/Plaintext.java delete mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionPayload.java delete mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionDecryptionResultHelper.java delete mode 100644 spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionPayload.java 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 1c0b6136..0ba200b1 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 @@ -17,11 +17,11 @@ package org.springframework.vault.core; import java.util.List; +import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.RawTransitKey; import org.springframework.vault.support.TransitKeyType; -import org.springframework.vault.support.VaultDecryptionPayload; import org.springframework.vault.support.VaultDecryptionResult; -import org.springframework.vault.support.VaultEncryptionPayload; import org.springframework.vault.support.VaultEncryptionResult; import org.springframework.vault.support.VaultTransitContext; import org.springframework.vault.support.VaultTransitKey; @@ -117,6 +117,16 @@ public interface VaultTransitOperations { */ String encrypt(String keyName, String plaintext); + /** + * Encrypts the provided plaintext using the named key. + * + * @param keyName must not be empty or {@literal null}. + * @param plaintext must not be {@literal null}. + * @since 1.1 + * @return cipher text. + */ + Ciphertext encrypt(String keyName, Plaintext plaintext); + /** * Encrypts the provided plaintext using the named key. * @@ -128,17 +138,16 @@ public interface VaultTransitOperations { String encrypt(String keyName, byte[] plaintext, VaultTransitContext transitRequest); /** - * Encrypts the provided list of plaintext using the named key and context. - * The encryption is done using transit backend's batch operation. - * - * works with Vault 0.6.5 and later. + * Encrypts the provided batch of 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 VaultEncryptionPayload which includes plaintext and optional context - * @return list of cipher text in the same order as in plaintexts. - * throws VaultException in case of not matching context found. + * @param batchRequest a list of {@link Plaintext} which includes plaintext and an + * optional context. + * @return the encrypted result in the order of {@code batchRequest} plaintexts. + * @since 1.1 */ - List encrypt(String keyName, List batchRequest); + List encrypt(String keyName, List batchRequest); /** * Decrypts the provided plaintext using the named key. @@ -149,6 +158,16 @@ public interface VaultTransitOperations { */ String decrypt(String keyName, String ciphertext); + /** + * Decrypts the provided plaintext using the named key. + * + * @param keyName must not be empty or {@literal null}. + * @param ciphertext must not be {@literal null}. + * @return plain text. + * @since 1.1 + */ + Plaintext decrypt(String keyName, Ciphertext ciphertext); + /** * Decrypts the provided plaintext using the named key. * @@ -160,17 +179,16 @@ public interface VaultTransitOperations { byte[] decrypt(String keyName, String ciphertext, VaultTransitContext transitRequest); /** - * Decrypts the provided list of ciphertext using the named key and context. - * The decryption is done using transit backend's batch operation. - * - * works with Vault 0.6.5 and later. - * + * Decrypts the provided barch of ciphertext 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 VaultDecryptionPayload which includes plaintext and optional context - * @return list of plain text in the same order as in ciphertexts. - * throws VaultException in case of not matching context found. + * @param batchRequest a list of {@link Ciphertext} which includes plaintext and an + * optional context. + * @return the decrypted result in the order of {@code batchRequest} ciphertexts. + * @since 1.1 */ - List<VaultDecryptionResult> decrypt(String keyName, List<VaultDecryptionPayload> batchRequest); + List<VaultDecryptionResult> decrypt(String keyName, List<Ciphertext> batchRequest); /** * Rewrap the provided ciphertext using the latest version of the named key. Because 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 19353e37..19fd9893 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 @@ -21,14 +21,18 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + import org.springframework.util.Assert; import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; +import org.springframework.vault.VaultException; +import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.RawTransitKey; import org.springframework.vault.support.TransitKeyType; -import org.springframework.vault.support.VaultDecryptionPayload; import org.springframework.vault.support.VaultDecryptionResult; -import org.springframework.vault.support.VaultEncryptionDecryptionResultHelper; -import org.springframework.vault.support.VaultEncryptionPayload; import org.springframework.vault.support.VaultEncryptionResult; import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultResponseSupport; @@ -37,10 +41,6 @@ import org.springframework.vault.support.VaultTransitKey; import org.springframework.vault.support.VaultTransitKeyConfiguration; import org.springframework.vault.support.VaultTransitKeyCreationRequest; -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.Data; - /** * Default implementation of {@link VaultTransitOperations}. * @@ -150,7 +150,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { public String encrypt(String keyName, String plaintext) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.notNull(plaintext, "Plain text must not be null"); + Assert.notNull(plaintext, "Plaintext must not be null"); Map<String, String> request = new LinkedHashMap<String, String>(); @@ -160,13 +160,24 @@ public class VaultTransitTemplate implements VaultTransitOperations { .write(String.format("%s/encrypt/%s", path, keyName), request).getData() .get("ciphertext"); } - + + @Override + public Ciphertext encrypt(String keyName, Plaintext plaintext) { + + Assert.notNull(plaintext, "Plaintext must not be null"); + + String ciphertext = encrypt(keyName, plaintext.getPlaintext(), + plaintext.getContext()); + + return toCiphertext(ciphertext, plaintext.getContext()); + } + @Override public String encrypt(String keyName, byte[] plaintext, VaultTransitContext transitRequest) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.notNull(plaintext, "Plain text must not be null"); + Assert.notNull(plaintext, "Plaintext must not be null"); Map<String, String> request = new LinkedHashMap<String, String>(); @@ -182,20 +193,22 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override - public List<VaultEncryptionResult> encrypt(String keyName, List<VaultEncryptionPayload> batchRequest) { + public List<VaultEncryptionResult> encrypt(String keyName, + List<Plaintext> batchRequest) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.notEmpty(batchRequest, "batchRequest must not be null and should have at least one entry"); + Assert.notEmpty(batchRequest, + "BatchRequest must not be null and must have at least one entry"); - List<Map<String, String>> batch = new ArrayList<Map<String, String>>(); + List<Map<String, String>> batch = new ArrayList<Map<String, String>>( + batchRequest.size()); - for (VaultEncryptionPayload request : batchRequest) { + for (Plaintext request : batchRequest) { - Assert.notNull(request.getPlaintext(), "Plain text must not be null"); + Map<String, String> vaultRequest = new LinkedHashMap<String, String>(2); - Map<String, String> vaultRequest = new LinkedHashMap<String, String>(); - - vaultRequest.put("plaintext", Base64Utils.encodeToString(request.getPlaintext())); + vaultRequest.put("plaintext", + Base64Utils.encodeToString(request.getPlaintext())); if (request.getContext() != null) { applyTransitOptions(request.getContext(), vaultRequest); @@ -204,19 +217,18 @@ public class VaultTransitTemplate implements VaultTransitOperations { batch.add(vaultRequest); } - Map<String, List<Map<String, String>>> request = new LinkedHashMap<String, List<Map<String, String>>>(); - request.put("batch_input", batch); + VaultResponse vaultResponse = vaultOperations.write( + String.format("%s/encrypt/%s", path, keyName), + Collections.singletonMap("batch_input", batch)); - VaultResponse vaultResponse = vaultOperations.write(String.format("%s/encrypt/%s", path, keyName), request); - - return VaultEncryptionDecryptionResultHelper.fetchEncryptionResult(vaultResponse); + return toEncryptionResults(vaultResponse, batchRequest); } - + @Override public String decrypt(String keyName, String ciphertext) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.hasText(keyName, "Cipher text must not be empty"); + Assert.hasText(ciphertext, "Ciphertext must not be empty"); Map<String, String> request = new LinkedHashMap<String, String>(); @@ -229,12 +241,23 @@ public class VaultTransitTemplate implements VaultTransitOperations { return new String(Base64Utils.decodeFromString(plaintext)); } + @Override + public Plaintext decrypt(String keyName, Ciphertext ciphertext) { + + Assert.hasText(keyName, "Ciphertext must not be null"); + + byte[] plaintext = decrypt(keyName, ciphertext.getCiphertext(), + ciphertext.getContext()); + + return toPlaintext(plaintext, ciphertext.getContext()); + } + @Override public byte[] decrypt(String keyName, String ciphertext, VaultTransitContext transitRequest) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.hasText(keyName, "Cipher text must not be empty"); + Assert.hasText(ciphertext, "Ciphertext must not be empty"); Map<String, String> request = new LinkedHashMap<String, String>(); @@ -250,20 +273,21 @@ public class VaultTransitTemplate implements VaultTransitOperations { return Base64Utils.decodeFromString(plaintext); } - + @Override - public List<VaultDecryptionResult> decrypt(String keyName, List<VaultDecryptionPayload> batchRequest) { + public List<VaultDecryptionResult> decrypt(String keyName, + List<Ciphertext> batchRequest) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.notEmpty(batchRequest, "batchRequest must not be null and should have at least one entry"); + Assert.notEmpty(batchRequest, + "BatchRequest must not be null and must have at least one entry"); - List<Map<String, String>> batch = new ArrayList<Map<String, String>>(); + List<Map<String, String>> batch = new ArrayList<Map<String, String>>( + batchRequest.size()); - for (VaultDecryptionPayload request : batchRequest) { + for (Ciphertext request : batchRequest) { - Assert.notNull(request.getCiphertext(), "Cipher text must not be null"); - - Map<String, String> vaultRequest = new LinkedHashMap<String, String>(); + Map<String, String> vaultRequest = new LinkedHashMap<String, String>(2); vaultRequest.put("ciphertext", request.getCiphertext()); @@ -274,19 +298,18 @@ public class VaultTransitTemplate implements VaultTransitOperations { batch.add(vaultRequest); } - Map<String, List<Map<String, String>>> request = new LinkedHashMap<String, List<Map<String, String>>>(); - request.put("batch_input", batch); + VaultResponse vaultResponse = vaultOperations.write( + String.format("%s/decrypt/%s", path, keyName), + Collections.singletonMap("batch_input", batch)); - VaultResponse vaultResponse = vaultOperations.write(String.format("%s/decrypt/%s", path, keyName), request); - - return VaultEncryptionDecryptionResultHelper.fetchDecryptionResult(vaultResponse); + return toDecryptionResults(vaultResponse, batchRequest); } @Override public String rewrap(String keyName, String ciphertext) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.hasText(ciphertext, "Cipher text must not be empty"); + Assert.hasText(ciphertext, "Ciphertext must not be empty"); Map<String, String> request = new LinkedHashMap<String, String>(); request.put("ciphertext", ciphertext); @@ -301,7 +324,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { VaultTransitContext transitRequest) { Assert.hasText(keyName, "KeyName must not be empty"); - Assert.hasText(ciphertext, "Cipher text must not be empty"); + Assert.hasText(ciphertext, "Ciphertext must not be empty"); Map<String, String> request = new LinkedHashMap<String, String>(); @@ -316,7 +339,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { .get("ciphertext"); } - private void applyTransitOptions(VaultTransitContext transitRequest, + private static void applyTransitOptions(VaultTransitContext transitRequest, Map<String, String> request) { if (transitRequest.getContext() != null) { @@ -329,6 +352,90 @@ public class VaultTransitTemplate implements VaultTransitOperations { } } + private static List<VaultEncryptionResult> toEncryptionResults( + VaultResponse vaultResponse, List<Plaintext> batchRequest) { + + List<VaultEncryptionResult> result = new ArrayList<VaultEncryptionResult>( + batchRequest.size()); + List<Map<String, String>> batchData = getBatchData(vaultResponse); + + for (int i = 0; i < batchRequest.size(); i++) { + + VaultEncryptionResult encrypted; + Plaintext plaintext = batchRequest.get(i); + if (batchData.size() > i) { + + Map<String, String> data = batchData.get(i); + if (StringUtils.hasText(data.get("error"))) { + encrypted = new VaultEncryptionResult(new VaultException( + data.get("error"))); + } + else { + encrypted = new VaultEncryptionResult(toCiphertext( + data.get("ciphertext"), plaintext.getContext())); + } + } + else { + encrypted = new VaultEncryptionResult(new VaultException( + "No result for plaintext #" + i)); + } + + result.add(encrypted); + } + + return result; + } + + private static List<VaultDecryptionResult> toDecryptionResults( + VaultResponse vaultResponse, List<Ciphertext> batchRequest) { + + List<VaultDecryptionResult> result = new ArrayList<VaultDecryptionResult>( + batchRequest.size()); + List<Map<String, String>> batchData = getBatchData(vaultResponse); + + for (int i = 0; i < batchRequest.size(); i++) { + + VaultDecryptionResult encrypted; + Ciphertext ciphertext = batchRequest.get(i); + if (batchData.size() > i) { + + Map<String, String> data = batchData.get(i); + if (StringUtils.hasText(data.get("error"))) { + encrypted = new VaultDecryptionResult(new VaultException( + data.get("error"))); + } + else { + encrypted = new VaultDecryptionResult(toPlaintext( + Base64Utils.decodeFromString(data.get("plaintext")), + ciphertext.getContext())); + } + } + else { + encrypted = new VaultDecryptionResult(new VaultException( + "No result for ciphertext #" + i)); + } + + result.add(encrypted); + } + + return result; + } + + private static Ciphertext toCiphertext(String ciphertext, VaultTransitContext context) { + return context != null ? Ciphertext.of(ciphertext).with(context) : Ciphertext + .of(ciphertext); + } + + private static Plaintext toPlaintext(byte[] plaintext, VaultTransitContext context) { + return context != null ? Plaintext.of(plaintext).with(context) : Plaintext + .of(plaintext); + } + + @SuppressWarnings("unchecked") + private static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) { + return (List<Map<String, String>>) vaultResponse.getData().get("batch_results"); + } + @Data static class VaultTransitKeyImpl implements VaultTransitKey { @@ -373,5 +480,5 @@ public class VaultTransitTemplate implements VaultTransitOperations { private String name; } - + } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java b/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java new file mode 100644 index 00000000..4f11c131 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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 + * + * http://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 org.springframework.util.Assert; +import org.springframework.vault.VaultException; + +/** + * Supporting class for computation results allowing introspection of the result value. + * Accessing the result with {@link #get()} returns either the result or throws a + * {@link VaultException} if the execution completed with an error. + * + * @author Mark Paluch + * @since 1.1 + */ +public abstract class AbstractResult<V> { + + private final VaultException exception; + + /** + * Create a {@link AbstractResult} completed without an {@link VaultException}. + */ + protected AbstractResult() { + exception = null; + } + + /** + * Create a {@link AbstractResult} completed with an {@link VaultException}. + * + * @param exception must not be {@literal null}. + */ + protected AbstractResult(VaultException exception) { + + Assert.notNull(exception, "VaultException must not be null"); + + this.exception = exception; + } + + /** + * Returns {@link true} if and only if the batch operation was completed successfully. + * Use {@link #getCause()} to obtain the actual exception if the operation completed + * with an error. + * + * @return {@link true} if the batch operation was completed successfully. + */ + public boolean isSuccessful() { + return exception == null; + } + + /** + * Returns the cause of the failed operation if the operation completed with an error. + * + * @return the cause of the failure or {@literal null} if succeeded. + */ + public Exception getCause() { + return exception; + } + + /** + * Return the result or throw a {@link VaultException} if the operation completed with + * an error. Use {@link #isSuccessful()} to verify the success status of this result + * without throwing an exception. + * + * @return the result value. + * @throws VaultException if the operation completed with an error. + */ + public V get() { + + if (isSuccessful()) { + return get0(); + } + + throw new VaultException(exception.getMessage()); + } + + /** + * @return the actual result if this result completed successfully. + */ + protected abstract V get0(); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Ciphertext.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Ciphertext.java new file mode 100644 index 00000000..e35277f1 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Ciphertext.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 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 + * + * http://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 lombok.EqualsAndHashCode; + +import org.springframework.util.Assert; + +/** + * Value object representing ciphertext with an optional {@link VaultTransitContext}. + * + * @author Praveendra Singh + * @author Mark Paluch + * @since 1.1 + */ +@EqualsAndHashCode +public class Ciphertext { + + private final String ciphertext; + + private final VaultTransitContext context; + + private Ciphertext(String ciphertext, VaultTransitContext context) { + + this.ciphertext = ciphertext; + this.context = context; + } + + /** + * Factory method to create {@link Ciphertext} from the given {@code ciphertext}. + * + * @param ciphertext the ciphertext to decrypt, must not be {@literal null} or empty. + * @return the {@link Ciphertext} for {@code ciphertext}. + */ + public static Ciphertext of(String ciphertext) { + + Assert.hasText(ciphertext, "Ciphertext must not be null or empty"); + + return new Ciphertext(ciphertext, null); + } + + public String getCiphertext() { + return ciphertext; + } + + public VaultTransitContext getContext() { + return context; + } + + /** + * Create a new {@link Ciphertext} object from this ciphertext associated with the + * given {@link VaultTransitContext}. + * + * @param context transit context. + * @return the new {@link Ciphertext} object. + */ + public Ciphertext with(VaultTransitContext context) { + return new Ciphertext(getCiphertext(), context); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Plaintext.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Plaintext.java new file mode 100644 index 00000000..154ce558 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Plaintext.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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 + * + * http://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 lombok.EqualsAndHashCode; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Value object representing plaintext with an optional {@link VaultTransitContext}. + * Plaintext is represented binary safe as {@code byte[]}. + * + * @author Praveendra Singh + * @author Mark Paluch + * @since 1.1 + */ +@EqualsAndHashCode +public class Plaintext { + + private final byte[] plaintext; + + private final VaultTransitContext context; + + private Plaintext(byte[] plaintext, VaultTransitContext context) { + + this.plaintext = plaintext; + this.context = context; + } + + /** + * Factory method to create {@link Plaintext} from a byte sequence. + * + * @param plaintext the plaintext to encrypt, must not be {@literal null} or empty. + * @return the {@link Plaintext} for {@code plaintext}. + */ + public static Plaintext of(byte[] plaintext) { + + Assert.isTrue(!ObjectUtils.isEmpty(plaintext), + "Plaintext must not be null or empty"); + + return new Plaintext(plaintext, null); + } + + /** + * Factory method to create {@link Plaintext} using from {@link String}. + * {@link String} is encoded to {@code byte} using the default + * {@link java.nio.charset.Charset}. + * + * @param plaintext the plaintext to encrypt, must not be {@literal null} or empty. + * @return the {@link Plaintext} for {@code plaintext}. + */ + public static Plaintext of(String plaintext) { + + Assert.hasText(plaintext, "Plaintext must not be null or empty"); + + return of(plaintext.getBytes()); + } + + public byte[] getPlaintext() { + return plaintext; + } + + public VaultTransitContext getContext() { + return context; + } + + /** + * Create a new {@link Plaintext} object from this plaintext associated with the given + * {@link VaultTransitContext}. + * + * @param context transit context. + * @return the new {@link Plaintext} object. + */ + public Plaintext with(VaultTransitContext context) { + return new Plaintext(getPlaintext(), context); + } + + /** + * @return the plaintext as {@link String} decoded using the default + * {@link java.nio.charset.Charset}. + */ + public String asString() { + return new String(getPlaintext()); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionPayload.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionPayload.java deleted file mode 100644 index 5ea436f5..00000000 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionPayload.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.springframework.vault.support; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -/** - * Decryption Value Object used for encrypt() operations. - * - * @author Praveendra Singh - * - */ -@Getter -@Setter -@AllArgsConstructor -public class VaultDecryptionPayload { - private String ciphertext; - private VaultTransitContext context; - - /** - * factory method helps to create decryption value object using ciphertext - * in String - * - * @param ciphertext - * to be decrypted - * @return decryption value object - */ - public static VaultDecryptionPayload of(String ciphertext) { - - if (ciphertext == null) { - throw new IllegalArgumentException("The ciphertext must not be null"); - } - - return new VaultDecryptionPayload(ciphertext, null); - } - - /** - * sets the decryption context to the value object. - * - * @param context - * transit decryption context - * @return decryption value object - */ - public VaultDecryptionPayload with(VaultTransitContext context) { - return new VaultDecryptionPayload(this.getCiphertext(), context); - } -} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionResult.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionResult.java index b874b2ba..37747949 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionResult.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultDecryptionResult.java @@ -1,39 +1,72 @@ +/* + * Copyright 2017 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 + * + * http://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 org.springframework.util.StringUtils; +import org.springframework.util.Assert; import org.springframework.vault.VaultException; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - /** - * Holds the response from decryption operation and provides helper methods to - * deal with the data. - * - * @author Praveendra Singh + * Holds the response from decryption operation and provides methods to access the result. * + * @author Praveendra Singh + * @author Mark Paluch + * @since 1.1 */ -@Getter -@Setter -@AllArgsConstructor -public class VaultDecryptionResult { +public class VaultDecryptionResult extends AbstractResult<Plaintext> { - private byte[] cipherText; - private String error; + private final Plaintext plaintext; /** - * returns the list of plaintext or throws VaultException if error - * encountered. - * - * @return plaintext + * Create {@link VaultDecryptionResult} for a successfully decrypted {@link Plaintext} + * . + * + * @param plaintext must not be {@literal null}. */ - public byte[] get() { + public VaultDecryptionResult(Plaintext plaintext) { - if (!StringUtils.isEmpty(error)) { - throw new VaultException(error); - } - return cipherText; + Assert.notNull(plaintext, "Plaintext must not be null"); + + this.plaintext = plaintext; } + /** + * Create {@link VaultDecryptionResult} for an error during decryption. + * + * @param exception must not be {@literal null}. + */ + public VaultDecryptionResult(VaultException exception) { + + super(exception); + this.plaintext = null; + } + + @Override + protected Plaintext get0() { + return plaintext; + } + + /** + * Return the result as {@link String} or throw a {@link VaultException} if the + * operation completed with an error. Use {@link #isSuccessful()} to verify the + * success status of this result without throwing an exception. + * + * @return the result value. + * @throws VaultException if the operation completed with an error. + */ + public String getAsString() { + return get().asString(); + } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionDecryptionResultHelper.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionDecryptionResultHelper.java deleted file mode 100644 index 60b47ac0..00000000 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionDecryptionResultHelper.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.springframework.vault.support; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.util.Base64Utils; - -import lombok.Getter; -import lombok.Setter; - -/** - * Holds the response from encryption/decryption operation and provides helper - * methods to generate list of encryption/decryption objects by fetching the - * respective fields from VaultResponse. - * - * @author Praveendra Singh - * - */ -@Getter -@Setter -public class VaultEncryptionDecryptionResultHelper { - - public static List<VaultEncryptionResult> fetchEncryptionResult(VaultResponse vaultResponse) { - - List<VaultEncryptionResult> result = new ArrayList<VaultEncryptionResult>(); - - for (Map<String, String> data : getBatchData(vaultResponse)) { - - VaultEncryptionResult res = new VaultEncryptionResult(data.get("ciphertext"), data.get("error")); - result.add(res); - } - - return result; - } - - public static List<VaultDecryptionResult> fetchDecryptionResult(VaultResponse vaultResponse) { - - List<VaultDecryptionResult> result = new ArrayList<VaultDecryptionResult>(); - - for (Map<String, String> data : getBatchData(vaultResponse)) { - - VaultDecryptionResult res = new VaultDecryptionResult(Base64Utils.decodeFromString(data.get("plaintext")), - data.get("error")); - result.add(res); - } - - return result; - } - - @SuppressWarnings("unchecked") - protected static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) { - return (List<Map<String, String>>) vaultResponse.getData().get("batch_results"); - } -} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionPayload.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionPayload.java deleted file mode 100644 index cce3f6b6..00000000 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionPayload.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.springframework.vault.support; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -/** - * Encryption Value Object used for encrypt() operations. - * - * @author Praveendra Singh - * - */ -@Getter -@Setter -@AllArgsConstructor -public class VaultEncryptionPayload { - private byte[] plaintext; - private VaultTransitContext context; - - /** - * factory method helps to create encryption value object using plaintext in - * bytes - * - * @param plaintext - * data to be encrypted - * - * @return encryption value object - */ - public static VaultEncryptionPayload of(byte[] plaintext) { - - if ((plaintext == null) || (plaintext.length == 0)) { - throw new IllegalArgumentException("The plaintext must not be null or empty"); - } - - return new VaultEncryptionPayload(plaintext, null); - } - - /** - * factory method helps to create encryption value object using plaintext in - * String - * - * @param plaintext - * data to be encrypted - * - * @return encryption value object - */ - public static VaultEncryptionPayload of(String plaintext) { - - if (plaintext == null) { - throw new IllegalArgumentException("The plaintext must not be null"); - } - - return of(plaintext.getBytes()); - } - - /** - * sets the encryption context to the value object. - * - * @param context - * transit encryption context - * @return encryption value object - */ - public VaultEncryptionPayload with(VaultTransitContext context) { - return new VaultEncryptionPayload(this.getPlaintext(), context); - } -} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionResult.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionResult.java index e9fa88f6..11295332 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionResult.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultEncryptionResult.java @@ -1,39 +1,56 @@ +/* + * Copyright 2017 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 + * + * http://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 org.springframework.util.StringUtils; import org.springframework.vault.VaultException; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - /** - * Holds the response from encryption operation and provides helper methods to - * deal with the data. - * - * @author Praveendra Singh + * Holds the response from encryption operation and provides methods to access the result. * + * @author Praveendra Singh + * @author Mark Paluch + * @since 1.1 */ -@Getter -@Setter -@AllArgsConstructor -public class VaultEncryptionResult { +public class VaultEncryptionResult extends AbstractResult<Ciphertext> { - private String cipherText; - private String error; + private final Ciphertext cipherText; /** - * returns the list of ciphertexts or throws VaultException if error - * encountered. - * - * @return ciphertexts + * Create {@link VaultEncryptionResult} for a successfully encrypted + * {@link Ciphertext} . + * + * @param cipherText must not be {@literal null}. */ - public String get() { - - if (!StringUtils.isEmpty(error)) { - throw new VaultException(error); - } - return cipherText; + public VaultEncryptionResult(Ciphertext cipherText) { + this.cipherText = cipherText; } + /** + * Create {@link VaultEncryptionResult} for an error during encryption. + * + * @param exception must not be {@literal null}. + */ + public VaultEncryptionResult(VaultException exception) { + + super(exception); + this.cipherText = null; + } + + @Override + protected Ciphertext get0() { + return cipherText; + } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitContext.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitContext.java index 8173c257..6f8f86f1 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitContext.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitContext.java @@ -15,11 +15,14 @@ */ package org.springframework.vault.support; +import lombok.EqualsAndHashCode; + /** * Transit backend encryption/decryption/rewrapping context. * * @author Mark Paluch */ +@EqualsAndHashCode public class VaultTransitContext { /** diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java index 9b5b32c4..3fd647ab 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java @@ -15,25 +15,21 @@ */ package org.springframework.vault.core; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.Assume.assumeTrue; - -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.vault.VaultException; -import org.springframework.vault.support.VaultDecryptionPayload; +import org.springframework.vault.support.Ciphertext; +import org.springframework.vault.support.Plaintext; import org.springframework.vault.support.VaultDecryptionResult; -import org.springframework.vault.support.VaultEncryptionPayload; import org.springframework.vault.support.VaultEncryptionResult; import org.springframework.vault.support.VaultMount; import org.springframework.vault.support.VaultTransitContext; @@ -43,6 +39,10 @@ import org.springframework.vault.support.VaultTransitKeyCreationRequest; import org.springframework.vault.util.IntegrationTestSupport; import org.springframework.vault.util.Version; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.Assume.assumeTrue; + /** * Integration tests for {@link VaultTransitTemplate} through * {@link VaultTransitOperations}. @@ -54,7 +54,7 @@ import org.springframework.vault.util.Version; @ContextConfiguration(classes = VaultIntegrationTestConfiguration.class) public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport { - private static final String BATCH_INTRODUCED_IN_VERSION = "0.6.5"; + private static final Version BATCH_INTRODUCED_IN_VERSION = Version.parse("0.6.5"); @Autowired private VaultOperations vaultOperations; @@ -106,7 +106,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport deleteKey("derived"); } } - + @Test public void createKeyShouldCreateKey() { @@ -209,6 +209,24 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport assertThat(ciphertext).startsWith("vault:v1:"); } + @Test + public void encryptShouldCreateWrappedCiphertextWithNonceAndContext() { + + transitOperations.createKey("mykey", VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true).derived(true).build()); + + VaultTransitContext context = VaultTransitContext.builder() + .context("blubb".getBytes()) // + .nonce("123456789012".getBytes()) // + .build(); + + Ciphertext ciphertext = transitOperations.encrypt("mykey", + Plaintext.of("hello-world").with(context)); + + assertThat(ciphertext.getCiphertext()).startsWith("vault:v1:"); + assertThat(ciphertext.getContext()).isEqualTo(context); + } + @Test public void decryptShouldCreatePlaintext() { @@ -238,6 +256,25 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport assertThat(new String(plaintext)).isEqualTo("hello-world"); } + @Test + public void decryptShouldCreateWrappedPlaintextWithNonceAndContext() { + + transitOperations.createKey("mykey", VaultTransitKeyCreationRequest.builder() + .convergentEncryption(true).derived(true).build()); + + VaultTransitContext context = VaultTransitContext.builder() // + .context("blubb".getBytes()) // + .nonce("123456789012".getBytes()) // + .build(); + + Ciphertext ciphertext = transitOperations.encrypt("mykey", + Plaintext.of("hello-world").with(context)); + Plaintext plaintext = transitOperations.decrypt("mykey", ciphertext); + + assertThat(plaintext.asString()).isEqualTo("hello-world"); + assertThat(plaintext.getContext()).isEqualTo(context); + } + @Test public void encryptAndRewrapShouldCreateCiphertext() { @@ -285,29 +322,50 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport String rewrapped = transitOperations.rewrap("mykey", ciphertext, transitRequest); assertThat(rewrapped).startsWith("vault:v2"); } - - @Test - public void batchEncryptionAndDecryptionTestWithoutContext() { - if (prepare().getVersion().isLessThan(Version.parse(BATCH_INTRODUCED_IN_VERSION))) { - return; - } + @Test + public void shouldBatchEncrypt() { + + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + BATCH_INTRODUCED_IN_VERSION)); transitOperations.createKey("mykey"); - List<String> plaintexts = new ArrayList<String>(); - plaintexts.add("one"); - plaintexts.add("two"); + List<VaultEncryptionResult> encrypted = transitOperations.encrypt("mykey", + Arrays.asList(Plaintext.of("one"), Plaintext.of("two"))); - batchEncryptionAndDecryption(plaintexts, null, null); + assertThat(encrypted.get(0).get().getCiphertext()).startsWith("vault:"); + assertThat(encrypted.get(1).get().getCiphertext()).startsWith("vault:"); } @Test - public void batchEncryptionAndDecryptionTestWithMatchingContext() { + public void shouldBatchDecrypt() { - if (prepare().getVersion().isLessThan(Version.parse(BATCH_INTRODUCED_IN_VERSION))) { - return; - } + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + BATCH_INTRODUCED_IN_VERSION)); + + transitOperations.createKey("mykey"); + + Ciphertext one = transitOperations.encrypt("mykey", Plaintext.of("one")); + Ciphertext two = transitOperations.encrypt("mykey", Plaintext.of("two")); + + Plaintext plainOne = transitOperations.decrypt("mykey", one); + Plaintext plainTwo = transitOperations.decrypt("mykey", two); + + List<VaultDecryptionResult> decrypted = transitOperations.decrypt("mykey", + Arrays.asList(one, two)); + + assertThat(decrypted.get(0).get()).isEqualTo(plainOne); + assertThat(decrypted.get(0).getAsString()).isEqualTo("one"); + assertThat(decrypted.get(1).get()).isEqualTo(plainTwo); + assertThat(decrypted.get(1).getAsString()).isEqualTo("two"); + } + + @Test + public void shouldBatchEncryptWithContext() { + + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + BATCH_INTRODUCED_IN_VERSION)); VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // .derived(true) // @@ -315,140 +373,77 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport transitOperations.createKey("mykey", request); - List<String> plaintexts = new ArrayList<String>(); - plaintexts.add("one"); - plaintexts.add("two"); + Plaintext one = Plaintext.of("one").with( + VaultTransitContext.builder().context("oneContext".getBytes()).build()); - List<VaultTransitContext> contexts = new ArrayList<VaultTransitContext>(); - contexts.add(VaultTransitContext.builder().context("oneContext".getBytes()).build()); - contexts.add(VaultTransitContext.builder().context("twoContext".getBytes()).build()); + Plaintext two = Plaintext.of("two").with( + VaultTransitContext.builder().context("twoContext".getBytes()).build()); - batchEncryptionAndDecryption(plaintexts, contexts, contexts); + List<VaultEncryptionResult> encrypted = transitOperations.encrypt("mykey", + Arrays.asList(one, two)); + + assertThat(encrypted.get(0).get().getContext()).isEqualTo(one.getContext()); + assertThat(encrypted.get(1).get().getContext()).isEqualTo(two.getContext()); } @Test - public void batchEncryptionAndDecryptionTestWithNonEqualContext() { + public void shouldBatchDecryptWithContext() { - if (prepare().getVersion().isLessThan(Version.parse(BATCH_INTRODUCED_IN_VERSION))) { - return; - } + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + BATCH_INTRODUCED_IN_VERSION)); - try { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // + .derived(true) // + .build(); - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // - .derived(true) // - .build(); + transitOperations.createKey("mykey", request); - transitOperations.createKey("mykey", request); + Plaintext one = Plaintext.of("one").with( + VaultTransitContext.builder().context("oneContext".getBytes()).build()); - List<String> plaintexts = new ArrayList<String>(); - plaintexts.add("one"); - plaintexts.add("two"); + Plaintext two = Plaintext.of("two").with( + VaultTransitContext.builder().context("twoContext".getBytes()).build()); - List<VaultTransitContext> encryptionContexts = new ArrayList<VaultTransitContext>(); - encryptionContexts.add(VaultTransitContext.builder().context("oneContext".getBytes()).build()); - encryptionContexts.add(VaultTransitContext.builder().context("twoContext".getBytes()).build()); + List<VaultEncryptionResult> encrypted = transitOperations.encrypt("mykey", + Arrays.asList(one, two)); + List<VaultDecryptionResult> decrypted = transitOperations.decrypt("mykey", + Arrays.asList(encrypted.get(0).get(), encrypted.get(1).get())); - List<VaultTransitContext> decryptionContext = new ArrayList<VaultTransitContext>(); - decryptionContext.add(VaultTransitContext.builder().context("oneContext".getBytes()).build()); - - batchEncryptionAndDecryption(plaintexts, encryptionContexts, decryptionContext); - - } catch (IllegalArgumentException e) { - return; - } catch (VaultException e) { - return; - } - - Assert.fail(); + assertThat(decrypted.get(0).get()).isEqualTo(one); + assertThat(decrypted.get(1).get()).isEqualTo(two); } @Test - public void batchEncryptionAndDecryptionTestWithNonMatchingContext() { + public void shouldBatchDecryptWithWrongContext() { - if (prepare().getVersion().isLessThan(Version.parse(BATCH_INTRODUCED_IN_VERSION))) { - return; - } + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + BATCH_INTRODUCED_IN_VERSION)); - try { + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // + .derived(true) // + .build(); - VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // - .derived(true) // - .build(); + transitOperations.createKey("mykey", request); - transitOperations.createKey("mykey", request); + Plaintext one = Plaintext.of("one").with( + VaultTransitContext.builder().context("oneContext".getBytes()).build()); - List<String> plaintexts = new ArrayList<String>(); - plaintexts.add("one"); - plaintexts.add("two"); + Plaintext two = Plaintext.of("two").with( + VaultTransitContext.builder().context("twoContext".getBytes()).build()); - List<VaultTransitContext> encryptionContexts = new ArrayList<VaultTransitContext>(); - encryptionContexts.add(VaultTransitContext.builder().context("oneContext".getBytes()).build()); - encryptionContexts.add(VaultTransitContext.builder().context("twoContext".getBytes()).build()); + List<VaultEncryptionResult> encrypted = transitOperations.encrypt("mykey", + Arrays.asList(one, two)); - List<VaultTransitContext> decryptionContext = new ArrayList<VaultTransitContext>(); - decryptionContext.add(VaultTransitContext.builder().context("oneContext".getBytes()).build()); - decryptionContext.add(VaultTransitContext.builder().context("wrongTwoContext".getBytes()).build()); + Ciphertext encryptedOne = encrypted.get(0).get(); + Ciphertext decryptedTwo = encrypted.get(1).get(); - batchEncryptionAndDecryption(plaintexts, encryptionContexts, decryptionContext); + Ciphertext tampered = decryptedTwo.with(encryptedOne.getContext()); - } catch (VaultException e) { - return; - } + List<VaultDecryptionResult> decrypted = transitOperations.decrypt("mykey", + Arrays.asList(encryptedOne, tampered)); - Assert.fail(); - } - - private void batchEncryptionAndDecryption(List<String> plaintexts, List<VaultTransitContext> encryptionContexts, - List<VaultTransitContext> decryptionContext) { - - List<VaultEncryptionPayload> encryptionBatchRequest = new ArrayList<VaultEncryptionPayload>(); - - int index = 0; - - for (String plaintext : plaintexts) { - - VaultEncryptionPayload req = VaultEncryptionPayload.of(plaintext); - - if (encryptionContexts != null) { - if (encryptionContexts.size() >= (index + 1)) { - req = req.with(encryptionContexts.get(index)); - } - } - - encryptionBatchRequest.add(req); - index++; - } - - List<VaultEncryptionResult> cipherResult = transitOperations.encrypt("mykey", encryptionBatchRequest); - - List<VaultDecryptionPayload> decryptionBatchRequest = new ArrayList<VaultDecryptionPayload>(); - - index = 0; - - for (VaultEncryptionResult cipher : cipherResult) { - - VaultDecryptionPayload req = VaultDecryptionPayload.of(cipher.get()); - - if (decryptionContext != null) { - if (decryptionContext.size() >= (index + 1)) { - req = req.with(decryptionContext.get(index)); - } - } - - decryptionBatchRequest.add(req); - index++; - } - - List<VaultDecryptionResult> plaintextResult = transitOperations.decrypt("mykey", decryptionBatchRequest); - - Assert.assertEquals(plaintexts.size(), plaintextResult.size()); - - int i = 0; - - for (String plaintext : plaintexts) { - String decrypted = new String(plaintextResult.get(i++).get()); - Assert.assertEquals(plaintext, decrypted); - } + assertThat(decrypted.get(0).get()).isEqualTo(one); + assertThat(decrypted.get(1).isSuccessful()).isEqualTo(false); + assertThat(decrypted.get(1).getCause()).isInstanceOf(VaultException.class); } } diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index b9762b90..b5807dff 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -9,4 +9,4 @@ [[new-features.1-1-0]] === What's new in Spring Vault 1.1.0 -* Batch encryption & decryption support. +* Transit batch encrypt and decrypt support.