vault/builtin/logical/transit/path_encrypt_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

994 lines
28 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package transit
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
"github.com/hashicorp/vault/sdk/helper/keysutil"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/mapstructure"
)
func TestTransit_MissingPlaintext(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the policy
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
encReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: map[string]interface{}{},
}
resp, err = b.HandleRequest(context.Background(), encReq)
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to missing plaintext in request, err:%v resp:%#v", err, resp)
}
}
func TestTransit_MissingPlaintextInBatchInput(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the policy
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := []interface{}{
map[string]interface{}{}, // Note that there is no map entry for plaintext
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err == nil {
t.Fatalf("expected error due to missing plaintext in request, err:%v resp:%#v", err, resp)
}
}
// Case1: Ensure that batch encryption did not affect the normal flow of
// encrypting the plaintext with a pre-existing key.
func TestTransit_BatchEncryptionCase1(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the policy
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox"
encData := map[string]interface{}{
"plaintext": plaintext,
}
encReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: encData,
}
resp, err = b.HandleRequest(context.Background(), encReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
keyVersion := resp.Data["key_version"].(int)
if keyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", keyVersion, 1)
}
ciphertext := resp.Data["ciphertext"]
decData := map[string]interface{}{
"ciphertext": ciphertext,
}
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/existing_key",
Storage: s,
Data: decData,
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
}
// Case2: Ensure that batch encryption did not affect the normal flow of
// encrypting the plaintext with the key upserted.
func TestTransit_BatchEncryptionCase2(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Upsert the key and encrypt the data
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
encData := map[string]interface{}{
"plaintext": plaintext,
}
encReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: encData,
}
resp, err = b.HandleRequest(context.Background(), encReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
keyVersion := resp.Data["key_version"].(int)
if keyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", keyVersion, 1)
}
ciphertext := resp.Data["ciphertext"]
decData := map[string]interface{}{
"ciphertext": ciphertext,
}
policyReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "keys/upserted_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/upserted_key",
Storage: s,
Data: decData,
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
}
// Case3: If batch encryption input is not base64 encoded, it should fail.
func TestTransit_BatchEncryptionCase3(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
_, err = b.HandleRequest(context.Background(), batchReq)
if err == nil {
t.Fatal("expected an error")
}
}
// Case4: Test batch encryption with an existing key (and test references)
func TestTransit_BatchEncryptionCase4(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "reference": "b"},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "reference": "a"},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/existing_key",
Storage: s,
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for i, item := range batchResponseItems {
if item.KeyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
}
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
inputItem := batchInput[i].(map[string]interface{})
if item.Reference != inputItem["reference"] {
t.Fatalf("reference mismatch. Expected %s, Actual: %s", inputItem["reference"], item.Reference)
}
}
}
// Case5: Test batch encryption with an existing derived key
func TestTransit_BatchEncryptionCase5(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
policyData := map[string]interface{}{
"derived": true,
}
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
Data: policyData,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/existing_key",
Storage: s,
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchResponseItems {
if item.KeyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
}
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
"context": "dmlzaGFsCg==",
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
}
}
// Case6: Test batch encryption with an upserted non-derived key
func TestTransit_BatchEncryptionCase6(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/upserted_key",
Storage: s,
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, responseItem := range batchResponseItems {
var item EncryptBatchResponseItem
if err := mapstructure.Decode(responseItem, &item); err != nil {
t.Fatal(err)
}
if item.KeyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
}
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
}
}
// Case7: Test batch encryption with an upserted derived key
func TestTransit_BatchEncryptionCase7(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "decrypt/upserted_key",
Storage: s,
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchResponseItems {
if item.KeyVersion != 1 {
t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
}
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
"context": "dmlzaGFsCg==",
}
resp, err = b.HandleRequest(context.Background(), decReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Data["plaintext"] != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
}
}
}
// Case8: If plaintext is not base64 encoded, encryption should fail
func TestTransit_BatchEncryptionCase8(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the policy
policyReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/existing_key",
Storage: s,
}
resp, err = b.HandleRequest(context.Background(), policyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := []interface{}{
map[string]interface{}{"plaintext": "simple_plaintext"},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
plaintext := "simple plaintext"
encData := map[string]interface{}{
"plaintext": plaintext,
}
encReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "encrypt/existing_key",
Storage: s,
Data: encData,
}
resp, err = b.HandleRequest(context.Background(), encReq)
if err == nil {
t.Fatal("expected an error")
}
}
// Case9: If both plaintext and batch inputs are supplied, plaintext should be
// ignored.
func TestTransit_BatchEncryptionCase9(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
batchData := map[string]interface{}{
"batch_input": batchInput,
"plaintext": plaintext,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
resp, err = b.HandleRequest(context.Background(), batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
_, ok := resp.Data["ciphertext"]
if ok {
t.Fatal("ciphertext field should not be set")
}
}
// Case10: Inconsistent presence of 'context' in batch input should be caught
func TestTransit_BatchEncryptionCase10(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
_, err = b.HandleRequest(context.Background(), batchReq)
if err == nil {
t.Fatalf("expected an error")
}
}
// Case11: Incorrect inputs for context and nonce should not fail the operation
func TestTransit_BatchEncryptionCase11(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "not-encoded"},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
_, err = b.HandleRequest(context.Background(), batchReq)
if err != nil {
t.Fatal(err)
}
}
// Case12: Invalid batch input
func TestTransit_BatchEncryptionCase12(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{},
"unexpected_interface",
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
}
_, err = b.HandleRequest(context.Background(), batchReq)
if err == nil {
t.Fatalf("expected an error")
}
}
// Case13: Incorrect input for nonce when we aren't in convergent encryption should fail the operation
func TestTransit_BatchEncryptionCase13(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := []interface{}{
map[string]interface{}{"plaintext": "bXkgc2VjcmV0IGRhdGE=", "nonce": "YmFkbm9uY2U="},
}
batchData := map[string]interface{}{
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/my-key",
Storage: s,
Data: batchData,
}
_, err = b.HandleRequest(context.Background(), batchReq)
if err != nil {
t.Fatal(err)
}
}
// Test that the fast path function decodeBatchRequestItems behave like mapstructure.Decode() to decode []BatchRequestItem.
func TestTransit_decodeBatchRequestItems(t *testing.T) {
tests := []struct {
name string
src interface{}
requirePlaintext bool
requireCiphertext bool
dest []BatchRequestItem
wantErrContains string
}{
// basic edge cases of nil values
{name: "nil-nil", src: nil, dest: nil},
{name: "nil-empty", src: nil, dest: []BatchRequestItem{}},
{name: "empty-nil", src: []interface{}{}, dest: nil},
{
name: "src-nil",
src: []interface{}{map[string]interface{}{}},
dest: nil,
},
// empty src & dest
{
name: "src-dest",
src: []interface{}{map[string]interface{}{}},
dest: []BatchRequestItem{},
},
// empty src but with already populated dest, mapstructure discard pre-populated data.
{
name: "src-dest_pre_filled",
src: []interface{}{map[string]interface{}{}},
dest: []BatchRequestItem{{}},
},
// two test per properties to test valid and invalid input
{
name: "src_plaintext-dest",
src: []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="}},
dest: []BatchRequestItem{},
},
{
name: "src_plaintext_invalid-dest",
src: []interface{}{map[string]interface{}{"plaintext": 666}},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'string', got unconvertible type 'int'",
},
{
name: "src_ciphertext-dest",
src: []interface{}{map[string]interface{}{"ciphertext": "dGhlIHF1aWNrIGJyb3duIGZveA=="}},
dest: []BatchRequestItem{},
},
{
name: "src_ciphertext_invalid-dest",
src: []interface{}{map[string]interface{}{"ciphertext": 666}},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'string', got unconvertible type 'int'",
},
{
name: "src_key_version-dest",
src: []interface{}{map[string]interface{}{"key_version": 1}},
dest: []BatchRequestItem{},
},
{
name: "src_key_version_invalid-dest",
src: []interface{}{map[string]interface{}{"key_version": "666"}},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'int', got unconvertible type 'string'",
},
{
name: "src_key_version_invalid-number-dest",
src: []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "key_version": json.Number("1.1")}},
dest: []BatchRequestItem{},
wantErrContains: "error decoding json.Number into [0].key_version",
},
{
name: "src_nonce-dest",
src: []interface{}{map[string]interface{}{"nonce": "dGVzdGNvbnRleHQ="}},
dest: []BatchRequestItem{},
},
{
name: "src_nonce_invalid-dest",
src: []interface{}{map[string]interface{}{"nonce": 666}},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'string', got unconvertible type 'int'",
},
{
name: "src_context-dest",
src: []interface{}{map[string]interface{}{"context": "dGVzdGNvbnRleHQ="}},
dest: []BatchRequestItem{},
},
{
name: "src_context_invalid-dest",
src: []interface{}{map[string]interface{}{"context": 666}},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'string', got unconvertible type 'int'",
},
{
name: "src_multi_order-dest",
src: []interface{}{
map[string]interface{}{"context": "1"},
map[string]interface{}{"context": "2"},
map[string]interface{}{"context": "3"},
},
dest: []BatchRequestItem{},
},
{
name: "src_multi_with_invalid-dest",
src: []interface{}{
map[string]interface{}{"context": "1"},
map[string]interface{}{"context": "2", "key_version": "666"},
map[string]interface{}{"context": "3"},
},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'int', got unconvertible type 'string'",
},
{
name: "src_multi_with_multi_invalid-dest",
src: []interface{}{
map[string]interface{}{"context": "1"},
map[string]interface{}{"context": "2", "key_version": "666"},
map[string]interface{}{"context": "3", "key_version": "1337"},
},
dest: []BatchRequestItem{},
wantErrContains: "expected type 'int', got unconvertible type 'string'",
},
{
name: "src_plaintext-nil-nonce",
src: []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "nonce": "null"}},
dest: []BatchRequestItem{},
},
// required fields
{
name: "required_plaintext_present",
src: []interface{}{map[string]interface{}{"plaintext": ""}},
requirePlaintext: true,
dest: []BatchRequestItem{},
},
{
name: "required_plaintext_missing",
src: []interface{}{map[string]interface{}{}},
requirePlaintext: true,
dest: []BatchRequestItem{},
wantErrContains: "missing plaintext",
},
{
name: "required_ciphertext_present",
src: []interface{}{map[string]interface{}{"ciphertext": "dGhlIHF1aWNrIGJyb3duIGZveA=="}},
requireCiphertext: true,
dest: []BatchRequestItem{},
},
{
name: "required_ciphertext_missing",
src: []interface{}{map[string]interface{}{}},
requireCiphertext: true,
dest: []BatchRequestItem{},
wantErrContains: "missing ciphertext",
},
{
name: "required_plaintext_and_ciphertext_missing",
src: []interface{}{map[string]interface{}{}},
requirePlaintext: true,
requireCiphertext: true,
dest: []BatchRequestItem{},
wantErrContains: "missing ciphertext",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expectedDest := append(tt.dest[:0:0], tt.dest...) // copy of the dest state
expectedErr := mapstructure.Decode(tt.src, &expectedDest) != nil || tt.wantErrContains != ""
gotErr := decodeBatchRequestItems(tt.src, tt.requirePlaintext, tt.requireCiphertext, &tt.dest)
gotDest := tt.dest
if expectedErr {
if gotErr == nil {
t.Fatal("decodeBatchRequestItems unexpected error value; expected error but got none")
}
if tt.wantErrContains == "" {
t.Fatal("missing error condition")
}
if !strings.Contains(gotErr.Error(), tt.wantErrContains) {
t.Errorf("decodeBatchRequestItems unexpected error value, want err contains: '%v', got: '%v'", tt.wantErrContains, gotErr)
}
}
if !reflect.DeepEqual(expectedDest, gotDest) {
t.Errorf("decodeBatchRequestItems unexpected dest value, want: '%v', got: '%v'", expectedDest, gotDest)
}
})
}
}
func TestShouldWarnAboutNonceUsage(t *testing.T) {
tests := []struct {
name string
keyTypes []keysutil.KeyType
nonce []byte
convergentEncryption bool
convergentVersion int
expected bool
}{
{
name: "-NoConvergent-WithNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_AES256_GCM96, keysutil.KeyType_AES128_GCM96, keysutil.KeyType_ChaCha20_Poly1305},
nonce: []byte("testnonce"),
convergentEncryption: false,
convergentVersion: -1,
expected: true,
},
{
name: "-NoConvergent-NoNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_AES256_GCM96, keysutil.KeyType_AES128_GCM96, keysutil.KeyType_ChaCha20_Poly1305},
nonce: []byte{},
convergentEncryption: false,
convergentVersion: -1,
expected: false,
},
{
name: "-Convergentv1-WithNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_AES256_GCM96, keysutil.KeyType_AES128_GCM96, keysutil.KeyType_ChaCha20_Poly1305},
nonce: []byte("testnonce"),
convergentEncryption: true,
convergentVersion: 1,
expected: true,
},
{
name: "-Convergentv2-WithNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_AES256_GCM96, keysutil.KeyType_AES128_GCM96, keysutil.KeyType_ChaCha20_Poly1305},
nonce: []byte("testnonce"),
convergentEncryption: true,
convergentVersion: 2,
expected: false,
},
{
name: "-Convergentv3-WithNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_AES256_GCM96, keysutil.KeyType_AES128_GCM96, keysutil.KeyType_ChaCha20_Poly1305},
nonce: []byte("testnonce"),
convergentEncryption: true,
convergentVersion: 3,
expected: false,
},
{
name: "-NoConvergent-WithNonce",
keyTypes: []keysutil.KeyType{keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096},
nonce: []byte("testnonce"),
convergentEncryption: false,
convergentVersion: -1,
expected: false,
},
}
for _, tt := range tests {
for _, keyType := range tt.keyTypes {
testName := keyType.String() + tt.name
t.Run(testName, func(t *testing.T) {
p := keysutil.Policy{
ConvergentEncryption: tt.convergentEncryption,
ConvergentVersion: tt.convergentVersion,
Type: keyType,
}
actual := shouldWarnAboutNonceUsage(&p, tt.nonce)
if actual != tt.expected {
t.Errorf("Expected actual '%v' but got '%v'", tt.expected, actual)
}
})
}
}
}
func TestTransit_EncryptWithRSAPublicKey(t *testing.T) {
generateKeys(t)
b, s := createBackendWithStorage(t)
keyType := "rsa-2048"
keyID, err := uuid.GenerateUUID()
if err != nil {
t.Fatalf("failed to generate key ID: %s", err)
}
// Get key
privateKey := getKey(t, keyType)
publicKeyBytes, err := getPublicKey(privateKey, keyType)
if err != nil {
t.Fatal(err)
}
// Import key
req := &logical.Request{
Storage: s,
Operation: logical.UpdateOperation,
Path: fmt.Sprintf("keys/%s/import", keyID),
Data: map[string]interface{}{
"public_key": publicKeyBytes,
"type": keyType,
},
}
_, err = b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("failed to import public key: %s", err)
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: fmt.Sprintf("encrypt/%s", keyID),
Storage: s,
Data: map[string]interface{}{
"plaintext": "bXkgc2VjcmV0IGRhdGE=",
},
}
_, err = b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
}