Introduce null-safe transit operations.

Require non-null VaultTransitContext in VaultTransitOperations and refactor nonce/context bytes to null-safe values.

See gh-112.
This commit is contained in:
Mark Paluch
2017-07-11 11:50:29 +02:00
parent 18c78059a7
commit 488a341d40
4 changed files with 123 additions and 43 deletions

View File

@@ -120,11 +120,11 @@ public interface VaultTransitOperations {
*
* @param keyName must not be empty or {@literal null}.
* @param plaintext must not be empty or {@literal null}.
* @param transitRequest may be {@literal null} if no request options provided.
* @param transitRequest must not be {@literal null}. Use
* {@link VaultTransitContext#empty()} if no request options provided.
* @return cipher text.
*/
String encrypt(String keyName, byte[] plaintext,
@Nullable VaultTransitContext transitRequest);
String encrypt(String keyName, byte[] plaintext, VaultTransitContext transitRequest);
/**
* Decrypts the provided plaintext using the named key.
@@ -140,11 +140,12 @@ public interface VaultTransitOperations {
*
* @param keyName must not be empty or {@literal null}.
* @param ciphertext must not be empty or {@literal null}.
* @param transitRequest may be {@literal null} if no request options provided.
* @param transitContext must not be {@literal null}. Use
* {@link VaultTransitContext#empty()} if no request options provided.
* @return cipher text.
* @return plain text.
*/
byte[] decrypt(String keyName, String ciphertext,
@Nullable VaultTransitContext transitRequest);
byte[] decrypt(String keyName, String ciphertext, VaultTransitContext transitContext);
/**
* Rewrap the provided ciphertext using the latest version of the named key. Because
@@ -165,10 +166,10 @@ public interface VaultTransitOperations {
*
* @param keyName must not be empty or {@literal null}.
* @param ciphertext must not be empty or {@literal null}.
* @param transitRequest may be {@literal null} if no request options provided.
* @param transitContext must not be {@literal null}. Use
* {@link VaultTransitContext#empty()} if no request options provided.
* @return cipher text.
* @see #rotate(String)
*/
String rewrap(String keyName, String ciphertext,
@Nullable VaultTransitContext transitRequest);
String rewrap(String keyName, String ciphertext, VaultTransitContext transitContext);
}

View File

@@ -26,6 +26,7 @@ import lombok.Data;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;
import org.springframework.vault.support.RawTransitKey;
import org.springframework.vault.support.TransitKeyType;
import org.springframework.vault.support.VaultResponse;
@@ -158,18 +159,17 @@ public class VaultTransitTemplate implements VaultTransitOperations {
@Override
public String encrypt(String keyName, byte[] plaintext,
@Nullable VaultTransitContext transitRequest) {
VaultTransitContext transitContext) {
Assert.hasText(keyName, "KeyName must not be empty");
Assert.notNull(plaintext, "Plain text must not be null");
Assert.notNull(transitContext, "VaultTransitContext must not be null");
Map<String, String> request = new LinkedHashMap<>();
request.put("plaintext", Base64Utils.encodeToString(plaintext));
if (transitRequest != null) {
applyTransitOptions(transitRequest, request);
}
applyTransitOptions(transitContext, request);
return (String) vaultOperations
.write(String.format("%s/encrypt/%s", path, keyName), request)
@@ -180,7 +180,7 @@ public class VaultTransitTemplate implements VaultTransitOperations {
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, "Cipher text must not be empty");
Map<String, String> request = new LinkedHashMap<>();
@@ -195,18 +195,17 @@ public class VaultTransitTemplate implements VaultTransitOperations {
@Override
public byte[] decrypt(String keyName, String ciphertext,
@Nullable VaultTransitContext transitRequest) {
VaultTransitContext transitContext) {
Assert.hasText(keyName, "KeyName must not be empty");
Assert.hasText(keyName, "Cipher text must not be empty");
Assert.hasText(ciphertext, "Cipher text must not be empty");
Assert.notNull(transitContext, "VaultTransitContext must not be null");
Map<String, String> request = new LinkedHashMap<>();
request.put("ciphertext", ciphertext);
if (transitRequest != null) {
applyTransitOptions(transitRequest, request);
}
applyTransitOptions(transitContext, request);
String plaintext = (String) vaultOperations
.write(String.format("%s/decrypt/%s", path, keyName), request)
@@ -231,34 +230,32 @@ public class VaultTransitTemplate implements VaultTransitOperations {
@Override
public String rewrap(String keyName, String ciphertext,
@Nullable VaultTransitContext transitRequest) {
VaultTransitContext transitContext) {
Assert.hasText(keyName, "KeyName must not be empty");
Assert.hasText(ciphertext, "Cipher text must not be empty");
Assert.notNull(transitContext, "VaultTransitContext must not be null");
Map<String, String> request = new LinkedHashMap<>();
request.put("ciphertext", ciphertext);
if (transitRequest != null) {
applyTransitOptions(transitRequest, request);
}
applyTransitOptions(transitContext, request);
return (String) vaultOperations
.write(String.format("%s/rewrap/%s", path, keyName), request)
.getRequiredData().get("ciphertext");
}
private void applyTransitOptions(VaultTransitContext transitRequest,
private static void applyTransitOptions(VaultTransitContext context,
Map<String, String> request) {
if (transitRequest.getContext() != null) {
request.put("context",
Base64Utils.encodeToString(transitRequest.getContext()));
if (!ObjectUtils.isEmpty(context.getContext())) {
request.put("context", Base64Utils.encodeToString(context.getContext()));
}
if (transitRequest.getNonce() != null) {
request.put("nonce", Base64Utils.encodeToString(transitRequest.getNonce()));
if (!ObjectUtils.isEmpty(context.getNonce())) {
request.put("nonce", Base64Utils.encodeToString(context.getNonce()));
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.vault.support;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Transit backend encryption/decryption/rewrapping context.
@@ -28,15 +28,14 @@ public class VaultTransitContext {
* Empty (default) {@link VaultTransitContext} without a {@literal context} and
* {@literal nonce}.
*/
private static final VaultTransitContext EMPTY = new VaultTransitContext(null, null);
private static final VaultTransitContext EMPTY = new VaultTransitContext(new byte[0],
new byte[0]);
@Nullable
private final byte[] context;
@Nullable
private final byte[] nonce;
VaultTransitContext(@Nullable byte[] context, @Nullable byte[] nonce) {
VaultTransitContext(byte[] context, byte[] nonce) {
this.context = context;
this.nonce = nonce;
}
@@ -55,10 +54,31 @@ public class VaultTransitContext {
return EMPTY;
}
/**
* Create a {@link VaultTransitContext} given {@code context} bytes.
*
* @param context context bytes, must not be {@literal null}.
* @return a {@link VaultTransitContext} for {@code context}.
* @since 2.0
*/
public static VaultTransitContext fromContext(byte[] context) {
return builder().context(context).build();
}
/**
* Create a {@link VaultTransitContext} given {@code nonce} bytes.
*
* @param nonce nonce bytes, must not be {@literal null}.
* @return a {@link VaultTransitContext} for {@code nonce}.
* @since 2.0
*/
public static VaultTransitContext fromNonce(byte[] nonce) {
return builder().nonce(nonce).build();
}
/**
* @return the key derivation context.
*/
@Nullable
public byte[] getContext() {
return context;
}
@@ -66,7 +86,6 @@ public class VaultTransitContext {
/**
* @return the
*/
@Nullable
public byte[] getNonce() {
return nonce;
}
@@ -76,11 +95,8 @@ public class VaultTransitContext {
*/
public static class VaultTransitRequestBuilder {
@Nullable
private byte[] context;
@Nullable
private byte[] nonce;
private byte[] context = new byte[0];
private byte[] nonce = new byte[0];
VaultTransitRequestBuilder() {
}
@@ -92,7 +108,10 @@ public class VaultTransitContext {
* provided if derivation is enabled.
* @return {@code this} {@link VaultTransitRequestBuilder}.
*/
public VaultTransitRequestBuilder context(@Nullable byte[] context) {
public VaultTransitRequestBuilder context(byte[] context) {
Assert.notNull(context, "Context must not be null");
this.context = context;
return this;
}
@@ -107,7 +126,10 @@ public class VaultTransitContext {
* nonce value is never reused
* @return {@code this} {@link VaultTransitRequestBuilder}.
*/
public VaultTransitRequestBuilder nonce(@Nullable byte[] nonce) {
public VaultTransitRequestBuilder nonce(byte[] nonce) {
Assert.notNull(nonce, "Nonce must not be null");
this.nonce = nonce;
return this;
}

View File

@@ -0,0 +1,60 @@
/*
* 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.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link VaultTransitContext}.
*
* @author Mark Paluch
*/
public class VaultTransitContextUnitTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsNullContext() {
VaultTransitContext.fromContext(null);
}
@Test
public void createsFromContext() {
byte[] bytes = new byte[] { 1 };
VaultTransitContext context = VaultTransitContext.fromContext(bytes);
assertThat(context.getContext()).isEqualTo(bytes);
assertThat(context.getNonce()).isEmpty();
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullNonce() {
VaultTransitContext.fromNonce(null);
}
@Test
public void createsFromNonce() {
byte[] bytes = new byte[] { 1 };
VaultTransitContext context = VaultTransitContext.fromNonce(bytes);
assertThat(context.getNonce()).isEqualTo(bytes);
assertThat(context.getContext()).isEmpty();
}
}