From a9121ff733f2eae798bc1225432f6769719e5a0e Mon Sep 17 00:00:00 2001 From: Vishal Nayak Date: Mon, 6 Feb 2017 14:56:16 -0500 Subject: [PATCH] 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 --- builtin/logical/transit/path_decrypt.go | 84 ++++------- builtin/logical/transit/path_decrypt_test.go | 76 +++++----- builtin/logical/transit/path_encrypt.go | 94 +++++------- builtin/logical/transit/path_encrypt_test.go | 134 ++++++++---------- builtin/logical/transit/path_rewrap.go | 86 ++++------- builtin/logical/transit/path_rewrap_test.go | 59 ++++---- .../source/docs/secrets/transit/index.html.md | 20 ++- 7 files changed, 221 insertions(+), 332 deletions(-) diff --git a/builtin/logical/transit/path_decrypt.go b/builtin/logical/transit/path_decrypt.go index dee2305465..c66931d44f 100644 --- a/builtin/logical/transit/path_decrypt.go +++ b/builtin/logical/transit/path_decrypt.go @@ -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 != "" { diff --git a/builtin/logical/transit/path_decrypt_test.go b/builtin/logical/transit/path_decrypt_test.go index 0a7df6f3c5..e0d4b6ed1f 100644 --- a/builtin/logical/transit/path_decrypt_test.go +++ b/builtin/logical/transit/path_decrypt_test.go @@ -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) } diff --git a/builtin/logical/transit/path_encrypt.go b/builtin/logical/transit/path_encrypt.go index bc7b88b5ee..b4281d60e1 100644 --- a/builtin/logical/transit/path_encrypt.go +++ b/builtin/logical/transit/path_encrypt.go @@ -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 != "" { diff --git a/builtin/logical/transit/path_encrypt_test.go b/builtin/logical/transit/path_encrypt_test.go index b39c58ebd4..6ab20db271 100644 --- a/builtin/logical/transit/path_encrypt_test.go +++ b/builtin/logical/transit/path_encrypt_test.go @@ -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, diff --git a/builtin/logical/transit/path_rewrap.go b/builtin/logical/transit/path_rewrap.go index 2c30c73d1a..167656a6e8 100644 --- a/builtin/logical/transit/path_rewrap.go +++ b/builtin/logical/transit/path_rewrap.go @@ -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 != "" { diff --git a/builtin/logical/transit/path_rewrap_test.go b/builtin/logical/transit/path_rewrap_test.go index e43a7dcb17..ae4d002103 100644 --- a/builtin/logical/transit/path_rewrap_test.go +++ b/builtin/logical/transit/path_rewrap_test.go @@ -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) diff --git a/website/source/docs/secrets/transit/index.html.md b/website/source/docs/secrets/transit/index.html.md index 129eafb05b..b082d13eec 100644 --- a/website/source/docs/secrets/transit/index.html.md +++ b/website/source/docs/secrets/transit/index.html.md @@ -479,10 +479,10 @@ only encrypt or decrypt using the named keys they need access to.
  • batch_input optional - 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.
  • batch_input optional - 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.
  • batch_input optional - 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 [