package transit import ( "context" "encoding/base64" "fmt" "github.com/hashicorp/vault/helper/errutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" "github.com/mitchellh/mapstructure" ) func (b *backend) pathRewrap() *framework.Path { return &framework.Path{ Pattern: "rewrap/" + framework.GenericNameRegex("name"), Fields: map[string]*framework.FieldSchema{ "name": &framework.FieldSchema{ Type: framework.TypeString, Description: "Name of the key", }, "ciphertext": &framework.FieldSchema{ Type: framework.TypeString, Description: "Ciphertext value to rewrap", }, "context": &framework.FieldSchema{ Type: framework.TypeString, Description: "Base64 encoded context for key derivation. Required for derived keys.", }, "nonce": &framework.FieldSchema{ Type: framework.TypeString, Description: "Nonce for when convergent encryption is used", }, "key_version": &framework.FieldSchema{ Type: framework.TypeInt, Description: `The version of the key to use for encryption. Must be 0 (for latest) or a value greater than or equal to the min_encryption_version configured on the key.`, }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: b.pathRewrapWrite, }, HelpSynopsis: pathRewrapHelpSyn, HelpDescription: pathRewrapHelpDesc, } } func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { batchInputRaw := d.Raw["batch_input"] var batchInputItems []BatchRequestItem var err error if batchInputRaw != nil { err = mapstructure.Decode(batchInputRaw, &batchInputItems) if err != nil { return nil, fmt.Errorf("failed to parse batch input: %v", err) } if len(batchInputItems) == 0 { return logical.ErrorResponse("missing batch input to process"), logical.ErrInvalidRequest } } else { ciphertext := d.Get("ciphertext").(string) if len(ciphertext) == 0 { return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest } batchInputItems = make([]BatchRequestItem, 1) batchInputItems[0] = BatchRequestItem{ Ciphertext: ciphertext, Context: d.Get("context").(string), Nonce: d.Get("nonce").(string), KeyVersion: d.Get("key_version").(int), } } batchResponseItems := make([]BatchResponseItem, len(batchInputItems)) contextSet := len(batchInputItems[0].Context) != 0 for i, item := range batchInputItems { if (len(item.Context) == 0 && contextSet) || (len(item.Context) != 0 && !contextSet) { return logical.ErrorResponse("context should be set either in all the request blocks or in none"), logical.ErrInvalidRequest } if item.Ciphertext == "" { 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 p, lock, err := b.lm.GetPolicyShared(ctx, req.Storage, d.Get("name").(string)) if lock != nil { defer lock.RUnlock() } if err != nil { return nil, err } if p == nil { return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest } for i, item := range batchInputItems { if batchResponseItems[i].Error != "" { continue } plaintext, err := p.Decrypt(item.DecodedContext, item.DecodedNonce, item.Ciphertext) if err != nil { switch err.(type) { case errutil.UserError: batchResponseItems[i].Error = err.Error() continue default: return nil, err } } ciphertext, err := p.Encrypt(item.KeyVersion, item.DecodedContext, item.DecodedNonce, plaintext) if err != nil { switch err.(type) { case errutil.UserError: batchResponseItems[i].Error = err.Error() continue case errutil.InternalError: return nil, err default: return nil, err } } if ciphertext == "" { return nil, fmt.Errorf("empty ciphertext returned for input item %d", i) } batchResponseItems[i].Ciphertext = ciphertext } resp := &logical.Response{} if batchInputRaw != nil { resp.Data = map[string]interface{}{ "batch_results": batchResponseItems, } } else { if batchResponseItems[0].Error != "" { return logical.ErrorResponse(batchResponseItems[0].Error), logical.ErrInvalidRequest } resp.Data = map[string]interface{}{ "ciphertext": batchResponseItems[0].Ciphertext, } } return resp, nil } const pathRewrapHelpSyn = `Rewrap ciphertext` const pathRewrapHelpDesc = ` After key rotation, this function can be used to rewrap the given ciphertext or a batch of given ciphertext blocks with the latest version of the named key. If the given ciphertext is already using the latest version of the key, this function is a no-op. `