transit: change batch input format (#2331)

* transit: change batch input format

* transit: no json-in-json for batch response

* docs: transit: update batch input format

* transit: fix tests after changing response format
This commit is contained in:
Vishal Nayak
2017-02-06 14:56:16 -05:00
committed by GitHub
parent b541d7d9a1
commit a9121ff733
7 changed files with 221 additions and 332 deletions

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"github.com/hashicorp/vault/helper/errutil"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/mitchellh/mapstructure"
)
func (b *backend) pathDecrypt() *framework.Path {
@@ -39,27 +39,6 @@ Base64 encoded nonce value used during encryption. Must be provided if
convergent encryption is enabled for this key and the key was generated with
Vault 0.6.1. Not required for keys created in 0.6.2+.`,
},
"batch_input": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Base64 encoded list of items to be decrypted in a single batch. When this
parameter is set, if the parameters 'ciphertext', 'context' and 'nonce' are
also set, they will be ignored. JSON format for the input (which should be
base64 encoded) goes like this:
[
{
"context": "c2FtcGxlY29udGV4dA==",
"ciphertext": "vault:v1:/DupSiSbX/ATkGmKAmhqD0tvukByrx6gmps7dVI="
},
{
"context": "YW5vdGhlcnNhbXBsZWNvbnRleHQ=",
"ciphertext": "vault:v1:XjsPWPjqPrBi1N2Ms2s1QM798YyFWnO4TR4lsFA="
},
...
]`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@@ -73,18 +52,13 @@ base64 encoded) goes like this:
func (b *backend) pathDecryptWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
batchInputRaw := d.Get("batch_input").(string)
batchInputRaw := d.Raw["batch_input"]
var batchInputItems []BatchRequestItem
var batchInput []byte
var err error
if len(batchInputRaw) != 0 {
batchInput, err = base64.StdEncoding.DecodeString(batchInputRaw)
if batchInputRaw != nil {
err = mapstructure.Decode(batchInputRaw, &batchInputItems)
if err != nil {
return logical.ErrorResponse("failed to base64-decode batch input"), logical.ErrInvalidRequest
}
if err := jsonutil.DecodeJSON([]byte(batchInput), &batchInputItems); err != nil {
return nil, fmt.Errorf("invalid input: %v", err)
return nil, fmt.Errorf("failed to parse batch input: %v", err)
}
if len(batchInputItems) == 0 {
@@ -99,24 +73,8 @@ func (b *backend) pathDecryptWrite(
batchInputItems = make([]BatchRequestItem, 1)
batchInputItems[0] = BatchRequestItem{
Ciphertext: ciphertext,
}
// Decode the context
contextRaw := d.Get("context").(string)
if len(contextRaw) != 0 {
batchInputItems[0].Context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
}
}
// Decode the nonce
nonceRaw := d.Get("nonce").(string)
if len(nonceRaw) != 0 {
batchInputItems[0].Nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode nonce"), logical.ErrInvalidRequest
}
Context: d.Get("context").(string),
Nonce: d.Get("nonce").(string),
}
}
@@ -132,6 +90,24 @@ func (b *backend) pathDecryptWrite(
batchResponseItems[i].Error = "missing ciphertext to decrypt"
continue
}
// Decode the context
if len(item.Context) != 0 {
batchInputItems[i].DecodedContext, err = base64.StdEncoding.DecodeString(item.Context)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
// Decode the nonce
if len(item.Nonce) != 0 {
batchInputItems[i].DecodedNonce, err = base64.StdEncoding.DecodeString(item.Nonce)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
}
// Get the policy
@@ -151,7 +127,7 @@ func (b *backend) pathDecryptWrite(
continue
}
plaintext, err := p.Decrypt(item.Context, item.Nonce, item.Ciphertext)
plaintext, err := p.Decrypt(item.DecodedContext, item.DecodedNonce, item.Ciphertext)
if err != nil {
switch err.(type) {
case errutil.UserError:
@@ -165,13 +141,9 @@ func (b *backend) pathDecryptWrite(
}
resp := &logical.Response{}
if len(batchInputRaw) != 0 {
batchResponseJSON, err := jsonutil.EncodeJSON(batchResponseItems)
if err != nil {
return nil, fmt.Errorf("failed to JSON encode batch response")
}
if batchInputRaw != nil {
resp.Data = map[string]interface{}{
"batch_results": string(batchResponseJSON),
"batch_results": batchResponseItems,
}
} else {
if batchResponseItems[0].Error != "" {

View File

@@ -1,10 +1,8 @@
package transit
import (
"encoding/base64"
"testing"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
)
@@ -15,10 +13,13 @@ func TestTransit_BatchDecryptionCase1(t *testing.T) {
b, s := createBackendWithStorage(t)
batchEncryptionInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"Cg=="}]`
batchEncryptionInputB64 := base64.StdEncoding.EncodeToString([]byte(batchEncryptionInput))
batchEncryptionInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "Cg=="},
}
batchEncryptionData := map[string]interface{}{
"batch_input": batchEncryptionInputB64,
"batch_input": batchEncryptionInput,
}
batchEncryptionReq := &logical.Request{
@@ -32,9 +33,8 @@ func TestTransit_BatchDecryptionCase1(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchDecryptionInput := resp.Data["batch_results"].(string)
batchDecryptionData := map[string]interface{}{
"batch_input": batchDecryptionInput,
"batch_input": resp.Data["batch_results"],
}
batchDecryptionReq := &logical.Request{
@@ -56,10 +56,12 @@ func TestTransit_BatchDecryptionCase2(t *testing.T) {
b, s := createBackendWithStorage(t)
batchEncryptionInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"Cg=="}]`
batchEncryptionInputB64 := base64.StdEncoding.EncodeToString([]byte(batchEncryptionInput))
batchEncryptionInput := []interface{}{
map[string]interface{}{"plaintext": "Cg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
batchEncryptionData := map[string]interface{}{
"batch_input": batchEncryptionInputB64,
"batch_input": batchEncryptionInput,
}
batchEncryptionReq := &logical.Request{
@@ -73,10 +75,13 @@ func TestTransit_BatchDecryptionCase2(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchDecryptionInput := resp.Data["batch_results"].(string)
batchDecryptionInputB64 := base64.StdEncoding.EncodeToString([]byte(batchDecryptionInput))
batchResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
batchDecryptionInput := make([]interface{}, len(batchResponseItems))
for i, item := range batchResponseItems {
batchDecryptionInput[i] = map[string]interface{}{"ciphertext": item.Ciphertext}
}
batchDecryptionData := map[string]interface{}{
"batch_input": batchDecryptionInputB64,
"batch_input": batchDecryptionInput,
}
batchDecryptionReq := &logical.Request{
@@ -90,14 +95,11 @@ func TestTransit_BatchDecryptionCase2(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchDecryptionResponseArray []BatchResponseItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchDecryptionResponseArray); err != nil {
t.Fatal(err)
}
batchDecryptionResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
plaintext1 := "dGhlIHF1aWNrIGJyb3duIGZveA=="
plaintext2 := "Cg=="
for _, item := range batchDecryptionResponseArray {
for _, item := range batchDecryptionResponseItems {
if item.Plaintext != plaintext1 && item.Plaintext != plaintext2 {
t.Fatalf("bad: plaintext: %q", item.Plaintext)
}
@@ -127,13 +129,13 @@ func TestTransit_BatchDecryptionCase3(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dGVzdGNvbnRleHQ="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dGVzdGNvbnRleHQ="}]`
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dGVzdGNvbnRleHQ="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dGVzdGNvbnRleHQ="},
}
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -146,24 +148,15 @@ func TestTransit_BatchDecryptionCase3(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var decryptionRequestItems []BatchRequestItem
var batchResponseArray []BatchRequestItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchResponseArray); err != nil {
t.Fatal(err)
}
for _, item := range batchResponseArray {
item.Context = []byte("testcontext")
decryptionRequestItems = append(decryptionRequestItems, item)
batchDecryptionInputItems := resp.Data["batch_results"].([]BatchResponseItem)
batchDecryptionInput := make([]interface{}, len(batchDecryptionInputItems))
for i, item := range batchDecryptionInputItems {
batchDecryptionInput[i] = map[string]interface{}{"ciphertext": item.Ciphertext, "context": "dGVzdGNvbnRleHQ="}
}
batchDecryptionInput, err := jsonutil.EncodeJSON(decryptionRequestItems)
if err != nil {
t.Fatalf("failed to encode batch decryption input")
}
batchDecryptionInputB64 := base64.StdEncoding.EncodeToString(batchDecryptionInput)
batchDecryptionData := map[string]interface{}{
"batch_input": batchDecryptionInputB64,
"batch_input": batchDecryptionInput,
}
batchDecryptionReq := &logical.Request{
@@ -177,13 +170,10 @@ func TestTransit_BatchDecryptionCase3(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchDecryptionResponseArray []BatchResponseItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchDecryptionResponseArray); err != nil {
t.Fatal(err)
}
batchDecryptionResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchDecryptionResponseArray {
for _, item := range batchDecryptionResponseItems {
if item.Plaintext != plaintext {
t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, item.Plaintext)
}

View File

@@ -6,16 +6,19 @@ import (
"sync"
"github.com/hashicorp/vault/helper/errutil"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/keysutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/mitchellh/mapstructure"
)
// BatchRequestItem represents a request item for batch processing
type BatchRequestItem struct {
// Context for key derivation. This is required for derived keys.
Context []byte `json:"context" structs:"context" mapstructure:"context"`
Context string `json:"context" structs:"context" mapstructure:"context"`
// DecodedContext is the base64 decoded version of Context
DecodedContext []byte
// Plaintext for encryption
Plaintext string `json:"plaintext" structs:"plaintext" mapstructure:"plaintext"`
@@ -24,7 +27,10 @@ type BatchRequestItem struct {
Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"`
// Nonce to be used when v1 convergent encryption is used
Nonce []byte `json:"nonce" structs:"nonce" mapstructure:"nonce"`
Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"`
// DecodedNonce is the base64 decoded version of Nonce
DecodedNonce []byte
}
// BatchResponseItem represents a response item for batch processing
@@ -94,27 +100,6 @@ same ciphertext is generated. It is *very important* when using this mode that
you ensure that all nonces are unique for a given context. Failing to do so
will severely impact the ciphertext's security.`,
},
"batch_input": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Base64 encoded list of items to be encrypted in a single batch. When this
parameter is set, if the parameters 'plaintext', 'context' and 'nonce' are also
set, they will be ignored. JSON format for the input (which should be base64
encoded) goes like this:
[
{
"context": "c2FtcGxlY29udGV4dA==",
"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="
},
{
"context": "YW5vdGhlcnNhbXBsZWNvbnRleHQ=",
"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="
},
...
]`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@@ -147,17 +132,12 @@ func (b *backend) pathEncryptWrite(
name := d.Get("name").(string)
var err error
batchInputRaw := d.Get("batch_input").(string)
var batchInput []byte
batchInputRaw := d.Raw["batch_input"]
var batchInputItems []BatchRequestItem
if len(batchInputRaw) != 0 {
batchInput, err = base64.StdEncoding.DecodeString(batchInputRaw)
if batchInputRaw != nil {
err = mapstructure.Decode(batchInputRaw, &batchInputItems)
if err != nil {
return logical.ErrorResponse("failed to base64-decode batch input"), logical.ErrInvalidRequest
}
if err := jsonutil.DecodeJSON([]byte(batchInput), &batchInputItems); err != nil {
return nil, fmt.Errorf("invalid input: %v", err)
return nil, fmt.Errorf("failed to parse batch input: %v", err)
}
if len(batchInputItems) == 0 {
@@ -172,24 +152,8 @@ func (b *backend) pathEncryptWrite(
batchInputItems = make([]BatchRequestItem, 1)
batchInputItems[0] = BatchRequestItem{
Plaintext: valueRaw.(string),
}
// Decode the context
contextRaw := d.Get("context").(string)
if len(contextRaw) != 0 {
batchInputItems[0].Context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
}
}
// Decode the nonce
nonceRaw := d.Get("nonce").(string)
if len(nonceRaw) != 0 {
batchInputItems[0].Nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode nonce"), logical.ErrInvalidRequest
}
Context: d.Get("context").(string),
Nonce: d.Get("nonce").(string),
}
}
@@ -210,6 +174,24 @@ func (b *backend) pathEncryptWrite(
batchResponseItems[i].Error = "failed to base64-decode plaintext"
continue
}
// Decode the context
if len(item.Context) != 0 {
batchInputItems[i].DecodedContext, err = base64.StdEncoding.DecodeString(item.Context)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
// Decode the nonce
if len(item.Nonce) != 0 {
batchInputItems[i].DecodedNonce, err = base64.StdEncoding.DecodeString(item.Nonce)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
}
// Get the policy
@@ -262,7 +244,7 @@ func (b *backend) pathEncryptWrite(
continue
}
ciphertext, err := p.Encrypt(item.Context, item.Nonce, item.Plaintext)
ciphertext, err := p.Encrypt(item.DecodedContext, item.DecodedNonce, item.Plaintext)
if err != nil {
switch err.(type) {
case errutil.UserError:
@@ -281,13 +263,9 @@ func (b *backend) pathEncryptWrite(
}
resp := &logical.Response{}
if len(batchInputRaw) != 0 {
batchResponseJSON, err := jsonutil.EncodeJSON(batchResponseItems)
if err != nil {
return nil, fmt.Errorf("failed to JSON encode batch response")
}
if batchInputRaw != nil {
resp.Data = map[string]interface{}{
"batch_results": string(batchResponseJSON),
"batch_results": batchResponseItems,
}
} else {
if batchResponseItems[0].Error != "" {

View File

@@ -1,10 +1,8 @@
package transit
import (
"encoding/base64"
"testing"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/mapstructure"
)
@@ -14,6 +12,7 @@ import (
func TestTransit_BatchEncryptionCase1(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the policy
@@ -128,7 +127,7 @@ func TestTransit_BatchEncryptionCase3(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[ {"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchData := map[string]interface{}{
"batch_input": batchInput,
}
@@ -162,10 +161,13 @@ func TestTransit_BatchEncryptionCase4(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -178,10 +180,7 @@ func TestTransit_BatchEncryptionCase4(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchResponseArray []BatchResponseItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchResponseArray); err != nil {
t.Fatal(err)
}
batchResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -191,7 +190,7 @@ func TestTransit_BatchEncryptionCase4(t *testing.T) {
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchResponseArray {
for _, item := range batchResponseItems {
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
}
@@ -229,14 +228,15 @@ func TestTransit_BatchEncryptionCase5(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
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",
@@ -248,10 +248,7 @@ func TestTransit_BatchEncryptionCase5(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchResponseArray []BatchResponseItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchResponseArray); err != nil {
t.Fatal(err)
}
batchResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -261,7 +258,7 @@ func TestTransit_BatchEncryptionCase5(t *testing.T) {
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchResponseArray {
for _, item := range batchResponseItems {
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
"context": "dmlzaGFsCg==",
@@ -284,10 +281,13 @@ func TestTransit_BatchEncryptionCase6(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
@@ -300,10 +300,7 @@ func TestTransit_BatchEncryptionCase6(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchResponseArray []interface{}
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchResponseArray); err != nil {
t.Fatal(err)
}
batchResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -313,7 +310,7 @@ func TestTransit_BatchEncryptionCase6(t *testing.T) {
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, responseItem := range batchResponseArray {
for _, responseItem := range batchResponseItems {
var item BatchResponseItem
if err := mapstructure.Decode(responseItem, &item); err != nil {
t.Fatal(err)
@@ -339,13 +336,13 @@ func TestTransit_BatchEncryptionCase7(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="}]`
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
}
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
@@ -358,10 +355,7 @@ func TestTransit_BatchEncryptionCase7(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchResponseArray []BatchResponseItem
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchResponseArray); err != nil {
t.Fatal(err)
}
batchResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
decReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -371,7 +365,7 @@ func TestTransit_BatchEncryptionCase7(t *testing.T) {
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
for _, item := range batchResponseArray {
for _, item := range batchResponseItems {
decReq.Data = map[string]interface{}{
"ciphertext": item.Ciphertext,
"context": "dmlzaGFsCg==",
@@ -405,10 +399,11 @@ func TestTransit_BatchEncryptionCase8(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
batchInput := `[{"plaintext":"simple_plaintext"}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchInput := []interface{}{
map[string]interface{}{"plaintext": "simple_plaintext"},
}
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.UpdateOperation,
@@ -447,11 +442,13 @@ func TestTransit_BatchEncryptionCase9(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
"plaintext": plaintext,
}
batchReq := &logical.Request{
@@ -477,13 +474,13 @@ func TestTransit_BatchEncryptionCase10(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="
},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="}]`
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
}
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
@@ -498,19 +495,19 @@ func TestTransit_BatchEncryptionCase10(t *testing.T) {
}
}
// Case11: Incorrect inputs for context and nonce should not be ignored
// 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 := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"dmlzaGFsCg=="},{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA==",
"context":"not-encoded"}]`
batchInput := []interface{}{
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "not-encoded"},
}
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
@@ -519,30 +516,23 @@ func TestTransit_BatchEncryptionCase11(t *testing.T) {
Data: batchData,
}
_, err = b.HandleRequest(batchReq)
if err == nil {
t.Fatalf("expected an error")
if err != nil {
t.Fatal(err)
}
}
// Case12: Invalid batch input
func TestTransit_BatchEncryptionCase12(t *testing.T) {
var err error
b, s := createBackendWithStorage(t)
batchInput := `{
"randomjson": [{
"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==",
"context": "dmlzaGFsCg=="
}, {
"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==",
"context": "not-encoded"
}]
}`
batchInput := []interface{}{
map[string]interface{}{},
"unexpected_interface",
}
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
"batch_input": batchInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"github.com/hashicorp/vault/helper/errutil"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/mitchellh/mapstructure"
)
func (b *backend) pathRewrap() *framework.Path {
@@ -33,27 +33,6 @@ func (b *backend) pathRewrap() *framework.Path {
Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used",
},
"batch_input": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Base64 encoded list of items to be rewrapped in a single batch. When this
parameter is set, if the parameters 'ciphertext', 'context' and 'nonce' are
also set, they will be ignored. JSON format for the input (which should be
bae64 encoded) goes like this:
[
{
"context": "c2FtcGxlY29udGV4dA==",
"ciphertext": "vault:v1:/DupSiSbX/ATkGmKAmhqD0tvukByrx6gmps7dVI="
},
{
"context": "YW5vdGhlcnNhbXBsZWNvbnRleHQ=",
"ciphertext": "vault:v1:XjsPWPjqPrBi1N2Ms2s1QM798YyFWnO4TR4lsFA="
},
...
]`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@@ -67,18 +46,13 @@ bae64 encoded) goes like this:
func (b *backend) pathRewrapWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
batchInputRaw := d.Get("batch_input").(string)
batchInputRaw := d.Raw["batch_input"]
var batchInputItems []BatchRequestItem
var batchInput []byte
var err error
if len(batchInputRaw) != 0 {
batchInput, err = base64.StdEncoding.DecodeString(batchInputRaw)
if batchInputRaw != nil {
err = mapstructure.Decode(batchInputRaw, &batchInputItems)
if err != nil {
return logical.ErrorResponse("failed to base64-decode batch input"), logical.ErrInvalidRequest
}
if err := jsonutil.DecodeJSON([]byte(batchInput), &batchInputItems); err != nil {
return nil, fmt.Errorf("invalid input: %v", err)
return nil, fmt.Errorf("failed to parse batch input: %v", err)
}
if len(batchInputItems) == 0 {
@@ -93,24 +67,8 @@ func (b *backend) pathRewrapWrite(
batchInputItems = make([]BatchRequestItem, 1)
batchInputItems[0] = BatchRequestItem{
Ciphertext: ciphertext,
}
// Decode the context
contextRaw := d.Get("context").(string)
if len(contextRaw) != 0 {
batchInputItems[0].Context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
}
}
// Decode the nonce
nonceRaw := d.Get("nonce").(string)
if len(nonceRaw) != 0 {
batchInputItems[0].Nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to base64-decode nonce"), logical.ErrInvalidRequest
}
Context: d.Get("context").(string),
Nonce: d.Get("nonce").(string),
}
}
@@ -126,6 +84,24 @@ func (b *backend) pathRewrapWrite(
batchResponseItems[i].Error = "missing ciphertext to decrypt"
continue
}
// Decode the context
if len(item.Context) != 0 {
batchInputItems[i].DecodedContext, err = base64.StdEncoding.DecodeString(item.Context)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
// Decode the nonce
if len(item.Nonce) != 0 {
batchInputItems[i].DecodedNonce, err = base64.StdEncoding.DecodeString(item.Nonce)
if err != nil {
batchResponseItems[i].Error = err.Error()
continue
}
}
}
// Get the policy
@@ -145,7 +121,7 @@ func (b *backend) pathRewrapWrite(
continue
}
plaintext, err := p.Decrypt(item.Context, item.Nonce, item.Ciphertext)
plaintext, err := p.Decrypt(item.DecodedContext, item.DecodedNonce, item.Ciphertext)
if err != nil {
switch err.(type) {
case errutil.UserError:
@@ -156,7 +132,7 @@ func (b *backend) pathRewrapWrite(
}
}
ciphertext, err := p.Encrypt(item.Context, item.Nonce, plaintext)
ciphertext, err := p.Encrypt(item.DecodedContext, item.DecodedNonce, plaintext)
if err != nil {
switch err.(type) {
case errutil.UserError:
@@ -177,13 +153,9 @@ func (b *backend) pathRewrapWrite(
}
resp := &logical.Response{}
if len(batchInputRaw) != 0 {
batchResponseJSON, err := jsonutil.EncodeJSON(batchResponseItems)
if err != nil {
return nil, fmt.Errorf("failed to JSON encode batch response")
}
if batchInputRaw != nil {
resp.Data = map[string]interface{}{
"batch_results": string(batchResponseJSON),
"batch_results": batchResponseItems,
}
} else {
if batchResponseItems[0].Error != "" {

View File

@@ -1,13 +1,10 @@
package transit
import (
"encoding/base64"
"strings"
"testing"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/mapstructure"
)
// Check the normal flow of rewrap
@@ -205,30 +202,33 @@ func TestTransit_BatchRewrapCase3(t *testing.T) {
b, s := createBackendWithStorage(t)
batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="},{"plaintext":"dmlzaGFsCg=="}]`
batchInputB64 := base64.StdEncoding.EncodeToString([]byte(batchInput))
batchData := map[string]interface{}{
"batch_input": batchInputB64,
batchEncryptionInput := []interface{}{
map[string]interface{}{"plaintext": "dmlzaGFsCg=="},
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
}
batchEncryptionData := map[string]interface{}{
"batch_input": batchEncryptionInput,
}
batchReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "encrypt/upserted_key",
Storage: s,
Data: batchData,
Data: batchEncryptionData,
}
resp, err = b.HandleRequest(batchReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchEncryptionResponseArray []interface{}
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchEncryptionResponseArray); err != nil {
t.Fatal(err)
batchEncryptionResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
batchRewrapInput := make([]interface{}, len(batchEncryptionResponseItems))
for i, item := range batchEncryptionResponseItems {
batchRewrapInput[i] = map[string]interface{}{"ciphertext": item.Ciphertext}
}
batchInputB64 = base64.StdEncoding.EncodeToString([]byte(resp.Data["batch_results"].(string)))
rewrapData := map[string]interface{}{
"batch_input": batchInputB64,
batchRewrapData := map[string]interface{}{
"batch_input": batchRewrapInput,
}
rotateReq := &logical.Request{
@@ -245,7 +245,7 @@ func TestTransit_BatchRewrapCase3(t *testing.T) {
Operation: logical.UpdateOperation,
Path: "rewrap/upserted_key",
Storage: s,
Data: rewrapData,
Data: batchRewrapData,
}
resp, err = b.HandleRequest(rewrapReq)
@@ -253,13 +253,10 @@ func TestTransit_BatchRewrapCase3(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
var batchRewrapResponseArray []interface{}
if err := jsonutil.DecodeJSON([]byte(resp.Data["batch_results"].(string)), &batchRewrapResponseArray); err != nil {
t.Fatal(err)
}
batchRewrapResponseItems := resp.Data["batch_results"].([]BatchResponseItem)
if len(batchRewrapResponseArray) != len(batchEncryptionResponseArray) {
t.Fatalf("bad: length of input and output or rewrap are not matching; expected: %d, actual: %d", len(batchEncryptionResponseArray), len(batchRewrapResponseArray))
if len(batchRewrapResponseItems) != len(batchEncryptionResponseItems) {
t.Fatalf("bad: length of input and output or rewrap are not matching; expected: %d, actual: %d", len(batchEncryptionResponseItems), len(batchRewrapResponseItems))
}
decReq := &logical.Request{
@@ -268,27 +265,19 @@ func TestTransit_BatchRewrapCase3(t *testing.T) {
Storage: s,
}
for i, responseItem := range batchEncryptionResponseArray {
var input BatchRequestItem
if err := mapstructure.Decode(responseItem, &input); err != nil {
t.Fatal(err)
}
for i, eItem := range batchEncryptionResponseItems {
rItem := batchRewrapResponseItems[i]
var output BatchResponseItem
if err := mapstructure.Decode(batchRewrapResponseArray[i], &output); err != nil {
t.Fatal(err)
}
if input.Ciphertext == output.Ciphertext {
if eItem.Ciphertext == rItem.Ciphertext {
t.Fatalf("bad: rewrap input and output are the same")
}
if !strings.HasPrefix(output.Ciphertext, "vault:v2") {
t.Fatalf("bad: invalid version of ciphertext in rewrap response; expected: 'vault:v2', actual: %s", output.Ciphertext)
if !strings.HasPrefix(rItem.Ciphertext, "vault:v2") {
t.Fatalf("bad: invalid version of ciphertext in rewrap response; expected: 'vault:v2', actual: %s", rItem.Ciphertext)
}
decReq.Data = map[string]interface{}{
"ciphertext": output.Ciphertext,
"ciphertext": rItem.Ciphertext,
}
resp, err = b.HandleRequest(decReq)

View File

@@ -479,10 +479,10 @@ only encrypt or decrypt using the named keys they need access to.
<li>
<span class="param">batch_input</span>
<span class="param-flags">optional</span>
Base64 encoded list of items to be encrypted in a single batch. When
List of items to be encrypted in a single batch. When
this parameter is set, if the parameters 'plaintext', 'context' and
'nonce' are also set, they will be ignored. JSON format for the input
(which should be base64 encoded) goes like this:
'nonce' are also set, they will be ignored. Format for the input
goes like this:
```javascript
[
@@ -582,10 +582,9 @@ only encrypt or decrypt using the named keys they need access to.
<li>
<span class="param">batch_input</span>
<span class="param-flags">optional</span>
Base64 encoded list of items to be decrypted in a single batch. When
this parameter is set, if the parameters 'ciphertext', 'context' and
'nonce' are also set, they will be ignored. JSON format for the input
(which should be base64 encoded) goes like this:
List of items to be decrypted in a single batch. When this parameter is
set, if the parameters 'ciphertext', 'context' and 'nonce' are also
set, they will be ignored. Format for the input goes like this:
```javascript
[
@@ -660,10 +659,9 @@ only encrypt or decrypt using the named keys they need access to.
<li>
<span class="param">batch_input</span>
<span class="param-flags">optional</span>
Base64 encoded list of items to be rewrapped in a single batch. When
this parameter is set, if the parameters 'ciphertext', 'context' and
'nonce' are also set, they will be ignored. JSON format for the input
(which should be bae64 encoded) goes like this:
List of items to be rewrapped in a single batch. When this parameter is
set, if the parameters 'ciphertext', 'context' and 'nonce' are also
set, they will be ignored. Format for the input goes like this:
```javascript
[