open-vault/builtin/logical/transit/path_encrypt_test.go
Gabriel Santos 05f3236c15
Provide public key encryption via transit engine (#17934)
* import rsa and ecdsa public keys

* allow import_version to update public keys - wip

* allow import_version to update public keys

* move check key fields into func

* put private/public keys in same switch cases

* fix method in UpdateKeyVersion

* move asymmetrics keys switch to its own method - WIP

* test import public and update it with private counterpart

* test import public keys

* use public_key to encrypt if RSAKey is not present and failed to decrypt
if key version does not have a private key

* move key to KeyEntry parsing from Policy to KeyEntry method

* move extracting of key from input fields into helper function

* change back policy Import signature to keep backwards compatibility and
add new method to import private or public keys

* test import with imported public rsa and ecdsa keys

* descriptions and error messages

* error messages, remove comments and unused code

* changelog

* documentation - wip

* suggested changes - error messages/typos and unwrap public key passed

* fix unwrap key error

* fail if both key fields have been set

* fix in extractKeyFromFields, passing a PolicyRequest wouldn't not work

* checks for read, sign and verify endpoints so they don't return errors when a private key was not imported and tests

* handle panic on "export key" endpoint if imported key is public

* fmt

* remove 'isPrivateKey' argument from 'UpdateKeyVersion' and
'parseFromKey' methods

also: rename 'UpdateKeyVersion' method to 'ImportPrivateKeyForVersion' and 'IsPublicKeyImported' to 'IsPrivateKeyMissing'

* delete 'RSAPublicKey' when private key is imported

* path_export: return public_key for ecdsa and rsa when there's no private key imported

* allow signed data validation with pss algorithm

* remove NOTE comment

* fix typo in EC public key export where empty derBytes was being used

* export rsa public key in pkcs8 format instead of pkcs1 and improve test

* change logic on how check for is private key missing is calculated

---------

Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-05-11 11:56:46 +00:00

994 lines
28 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
}
}