vault/builtin/logical/transit/path_encrypt.go
Vault Automation caf642b7d2
Backport Vault 42177 Add Backend Field into ce/main (#12152)
* Vault 42177 Add Backend Field (#12092)

* add a new struct for the total number of successful requests for transit and transform

* implement tracking for encrypt path

* implement tracking in encrypt path

* add tracking in rewrap

* add tracking to datakey path

* add tracking to  hmac path

* add tracking to sign  path

* add tracking to verify path

* unit tests for verify path

* add tracking to cmac path

* reset the global counter in each unit test

* add tracking to hmac verify

* add methods to retrieve and flush transit count

* modify the methods that store and update data protection call counts

* update the methods

* add a helper method to combine replicated and local data call counts

* add tracking to the endpoint

* fix some formatting errors

* add unit tests to path encrypt for tracking

* add unit tests to decrypt path

* fix linter error

* add unit tests to test update and store methods for data protection calls

* stub fix: do not create separate files

* fix the tracking by coordinating replicated and local data, add unit tests

* update all reference to the new data struct

* revert to previous design with just one global counter for all calls for each cluster

* complete external test

* no need to check if current count is greater than 0, remove it

* feedback: remove unnacassary comments about atomic addition, standardize comments

* leave jira id on todo comment, remove unused method

* rename mathods by removing HWM and max in names, update jira id in todo comment, update response field key name

* feedback: remove explicit counter in cmac tests, instead put in the expected number

* feedback: remove explicit tracking in the rest of the tests

* feedback: separate transit testing into its own external test

* Update vault/consumption_billing_util_test.go

Co-authored-by: divyaac <divya.chandrasekaran@hashicorp.com>

* update comment after test name change

* fix comments

* fix comments in test

* another comment fix

* feedback: remove incorrect comment

* fix a CE test

* fix the update method: instead of storing max, increment by the current count value

* update the unit test, remove local prefix as argument to the methods since we store only to non-replicated paths

* update the external test

* Adds a field to backend to track billing data

removed file

* Changed implementation to use a map instead

* Some more comments

* Add more implementation

* Edited grpc server backend

* Refactored a bit

* Fix one more test

* Modified map:

* Revert "Modified map:"

This reverts commit 1730fe1f358b210e6abae43fbdca09e585aaaaa8.

* Removed some other things

* Edited consumption billing files a bit

* Testing function

* Fix transit stuff and make sure tests pass

* Changes

* More changes

* More changes

* Edited external test

* Edited some more tests

* Edited and fixed tests

* One more fix

* Fix some more tests

* Moved some testing structures around and added error checking

* Fixed some nits

* Update builtin/logical/transit/path_sign_verify.go

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>

* Edited some errors

* Fixed error logs

* Edited one more thing

* Decorate the error

* Update vault/consumption_billing.go

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>

---------

Co-authored-by: Amir Aslamov <amir.aslamov@hashicorp.com>
Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>

* Edited stub function

---------

Co-authored-by: divyaac <divya.chandrasekaran@hashicorp.com>
Co-authored-by: Amir Aslamov <amir.aslamov@hashicorp.com>
Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
Co-authored-by: divyaac <divyaac@berkeley.edu>
2026-02-03 22:48:12 +00:00

740 lines
24 KiB
Go

// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1
package transit
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"github.com/hashicorp/vault/helper/constants"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/helper/keysutil"
"github.com/hashicorp/vault/sdk/logical"
"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 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"`
// Ciphertext for decryption
Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"`
// PaddingScheme for encryption/decryption
PaddingScheme string `json:"padding_scheme" structs:"padding_scheme" mapstructure:"padding_scheme"`
// Nonce to be used when v1 convergent encryption is used
Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"`
// The key version to be used for encryption
KeyVersion int `json:"key_version" structs:"key_version" mapstructure:"key_version"`
// DecodedNonce is the base64 decoded version of Nonce
DecodedNonce []byte
// Associated Data for AEAD ciphers
AssociatedData string `json:"associated_data" struct:"associated_data" mapstructure:"associated_data"`
// Reference is an arbitrary caller supplied string value that will be placed on the
// batch response to ease correlation between inputs and outputs
Reference string `json:"reference" structs:"reference" mapstructure:"reference"`
// IV is the user-supplied IV to be used for AES-CBC encryption
IV string `json:"iv" structs:"iv" mapstructure:"iv"`
// DecodedIV is the base64 decoded version of IV
DecodedIV []byte
}
// EncryptBatchResponseItem represents a response item for batch processing
type EncryptBatchResponseItem struct {
// Ciphertext for the plaintext present in the corresponding batch
// request item
Ciphertext string `json:"ciphertext,omitempty" structs:"ciphertext" mapstructure:"ciphertext"`
// KeyVersion defines the key version used to encrypt plaintext.
KeyVersion int `json:"key_version,omitempty" structs:"key_version" mapstructure:"key_version"`
// Error, if set represents a failure encountered while encrypting a
// corresponding batch request item
Error string `json:"error,omitempty" structs:"error" mapstructure:"error"`
// Reference is an arbitrary caller supplied string value that will be placed on the
// batch response to ease correlation between inputs and outputs
Reference string `json:"reference"`
}
type AssocDataFactory struct {
Encoded string
}
func (a AssocDataFactory) GetAssociatedData() ([]byte, error) {
return base64.StdEncoding.DecodeString(a.Encoded)
}
type ManagedKeyFactory struct {
managedKeyParams keysutil.ManagedKeyParameters
}
func (m ManagedKeyFactory) GetManagedKeyParameters() keysutil.ManagedKeyParameters {
return m.managedKeyParams
}
func (b *backend) pathEncrypt() *framework.Path {
return &framework.Path{
Pattern: "encrypt/" + framework.GenericNameRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTransit,
OperationVerb: "encrypt",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the key",
},
"plaintext": {
Type: framework.TypeString,
Description: "Base64 encoded plaintext value to be encrypted",
},
"padding_scheme": {
Type: framework.TypeString,
Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types.
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
},
"context": {
Type: framework.TypeString,
Description: "Base64 encoded context for key derivation. Required if key derivation is enabled",
},
"nonce": {
Type: framework.TypeString,
Description: `
Base64 encoded nonce value. 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+. The value must be exactly 96 bits (12 bytes) long
and the user must ensure that for any given context (and thus, any given
encryption key) this nonce value is **never reused**.
`,
},
"type": {
Type: framework.TypeString,
Default: "aes256-gcm96",
Description: `
This parameter is required when encryption key is expected to be created.
When performing an upsert operation, the type of key to create. Currently,
"aes128-gcm96" (symmetric) and "aes256-gcm96" (symmetric) are the only types supported. Defaults to "aes256-gcm96".`,
},
"convergent_encryption": {
Type: framework.TypeBool,
Description: `
This parameter will only be used when a key is expected to be created. Whether
to support convergent encryption. This is only supported when using a key with
key derivation enabled and will require all requests to carry both a context
and 96-bit (12-byte) nonce. The given nonce will be used in place of a randomly
generated nonce. As a result, when the same context and nonce are supplied, the
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.`,
},
"key_version": {
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.`,
},
"partial_failure_response_code": {
Type: framework.TypeInt,
Description: `
Ordinarily, if a batch item fails to encrypt due to a bad input, but other batch items succeed,
the HTTP response code is 400 (Bad Request). Some applications may want to treat partial failures differently.
Providing the parameter returns the given response code integer instead of a 400 in this case. If all values fail
HTTP 400 is still returned.`,
},
"associated_data": {
Type: framework.TypeString,
Description: `
When using an AEAD cipher mode, such as AES-GCM, this parameter allows
passing associated data (AD/AAD) into the encryption function; this data
must be passed on subsequent decryption requests but can be transited in
plaintext. On successful decryption, both the ciphertext and the associated
data are attested not to have been tampered with.
`,
},
"batch_input": {
Type: framework.TypeSlice,
Description: `
Specifies a 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. Any batch output will preserve the order of the batch input.`,
},
"iv": {
Type: framework.TypeString,
Description: `
Specifies a base64-encoded IV to use with AES-CBC. The length of the IV must
be 16 bytes (128 bits).'`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.CreateOperation: b.pathEncryptWrite,
logical.UpdateOperation: b.pathEncryptWrite,
},
ExistenceCheck: b.pathEncryptExistenceCheck,
HelpSynopsis: pathEncryptHelpSyn,
HelpDescription: pathEncryptHelpDesc,
}
}
func decodeEncryptBatchRequestItems(src interface{}, dst *[]BatchRequestItem) error {
return decodeBatchRequestItems(src, true, false, dst)
}
func decodeDecryptBatchRequestItems(src interface{}, dst *[]BatchRequestItem) error {
return decodeBatchRequestItems(src, false, true, dst)
}
// decodeBatchRequestItems is a fast path alternative to mapstructure.Decode to decode []BatchRequestItem.
// It aims to behave as closely possible to the original mapstructure.Decode and will return the same errors.
// Note, however, that an error will also be returned if one of the required fields is missing.
// https://github.com/hashicorp/vault/pull/8775/files#r437709722
func decodeBatchRequestItems(src interface{}, requirePlaintext bool, requireCiphertext bool, dst *[]BatchRequestItem) error {
if src == nil || dst == nil {
return nil
}
items, ok := src.([]interface{})
if !ok {
return fmt.Errorf("source data must be an array or slice, got %T", src)
}
// Early return should happen before allocating the array if the batch is empty.
// However to comply with mapstructure output it's needed to allocate an empty array.
sitems := len(items)
*dst = make([]BatchRequestItem, sitems)
if sitems == 0 {
return nil
}
// To comply with mapstructure output the same error type is needed.
var errs mapstructure.Error
for i, iitem := range items {
item, ok := iitem.(map[string]interface{})
if !ok {
return fmt.Errorf("[%d] expected a map, got '%T'", i, iitem)
}
if v, has := item["context"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].Context = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].context' expected type 'string', got unconvertible type '%T'", i, item["context"]))
}
}
if v, has := item["ciphertext"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].Ciphertext = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].ciphertext' expected type 'string', got unconvertible type '%T'", i, item["ciphertext"]))
}
} else if requireCiphertext {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].ciphertext' missing ciphertext to decrypt", i))
}
if v, has := item["plaintext"]; has {
if casted, ok := v.(string); ok {
(*dst)[i].Plaintext = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].plaintext' expected type 'string', got unconvertible type '%T'", i, item["plaintext"]))
}
} else if requirePlaintext {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].plaintext' missing plaintext to encrypt", i))
}
if v, has := item["padding_scheme"]; has {
if casted, ok := v.(string); ok {
(*dst)[i].PaddingScheme = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].padding_scheme' expected type 'string', got unconvertible type '%T'", i, item["padding_scheme"]))
}
}
if v, has := item["nonce"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].Nonce = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].nonce' expected type 'string', got unconvertible type '%T'", i, item["nonce"]))
}
}
if v, has := item["key_version"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(int); ok {
(*dst)[i].KeyVersion = casted
} else if js, ok := v.(json.Number); ok {
// https://github.com/hashicorp/vault/issues/10232
// Because API server parses json request with UseNumber=true, logical.Request.Data can include json.Number for a number field.
if casted, err := js.Int64(); err == nil {
(*dst)[i].KeyVersion = int(casted)
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf(`error decoding %T into [%d].key_version: strconv.ParseInt: parsing "%s": invalid syntax`, v, i, v))
}
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].key_version' expected type 'int', got unconvertible type '%T'", i, item["key_version"]))
}
}
if v, has := item["associated_data"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].AssociatedData = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].associated_data' expected type 'string', got unconvertible type '%T'", i, item["associated_data"]))
}
}
if v, has := item["reference"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].Reference = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].reference' expected type 'string', got unconvertible type '%T'", i, item["reference"]))
}
}
if v, has := item["iv"]; has {
if !reflect.ValueOf(v).IsValid() {
} else if casted, ok := v.(string); ok {
(*dst)[i].IV = casted
} else {
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].iv' expected type 'string', got unconvertible type '%T'", i, item["iv"]))
}
}
}
if len(errs.Errors) > 0 {
return &errs
}
return nil
}
func (b *backend) pathEncryptExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
name := d.Get("name").(string)
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage,
Name: name,
}, b.GetRandomReader())
if err != nil {
return false, err
}
if p != nil && b.System().CachingDisabled() {
p.Unlock()
}
return p != nil, nil
}
func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
var err error
batchInputRaw := d.Raw["batch_input"]
var batchInputItems []BatchRequestItem
if batchInputRaw != nil {
err = decodeEncryptBatchRequestItems(batchInputRaw, &batchInputItems)
if err != nil {
return nil, fmt.Errorf("failed to parse batch input: %w", err)
}
if len(batchInputItems) == 0 {
return logical.ErrorResponse("missing batch input to process"), logical.ErrInvalidRequest
}
} else {
valueRaw, ok, err := d.GetOkErr("plaintext")
if err != nil {
return nil, err
}
if !ok {
return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
}
batchInputItems = make([]BatchRequestItem, 1)
batchInputItems[0] = BatchRequestItem{
Plaintext: valueRaw.(string),
Context: d.Get("context").(string),
Nonce: d.Get("nonce").(string),
KeyVersion: d.Get("key_version").(int),
AssociatedData: d.Get("associated_data").(string),
IV: d.Get("iv").(string),
}
if psRaw, ok := d.GetOk("padding_scheme"); ok {
if ps, ok := psRaw.(string); ok {
batchInputItems[0].PaddingScheme = ps
} else {
return logical.ErrorResponse("padding_scheme was not a string"), logical.ErrInvalidRequest
}
}
}
batchResponseItems := make([]EncryptBatchResponseItem, len(batchInputItems))
contextSet := len(batchInputItems[0].Context) != 0
userErrorInBatch := false
internalErrorInBatch := false
// Before processing the batch request items, get the policy. If the
// policy is supposed to be upserted, then determine if 'derived' is to
// be set or not, based on the presence of 'context' field in all the
// input items.
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
}
_, err := base64.StdEncoding.DecodeString(item.Plaintext)
if err != nil {
userErrorInBatch = true
batchResponseItems[i].Error = err.Error()
continue
}
// Decode the context
if len(item.Context) != 0 {
batchInputItems[i].DecodedContext, err = base64.StdEncoding.DecodeString(item.Context)
if err != nil {
userErrorInBatch = true
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 {
userErrorInBatch = true
batchResponseItems[i].Error = err.Error()
continue
}
}
// Decode the IV
if len(item.IV) > 0 {
batchInputItems[i].DecodedIV, err = base64.StdEncoding.DecodeString(item.IV)
if err != nil {
userErrorInBatch = true
batchResponseItems[i].Error = err.Error()
continue
}
}
}
// Get the policy
var p *keysutil.Policy
var upserted bool
var polReq keysutil.PolicyRequest
if req.Operation == logical.CreateOperation {
convergent := d.Get("convergent_encryption").(bool)
if convergent && !contextSet {
return logical.ErrorResponse("convergent encryption requires derivation to be enabled, so context is required"), nil
}
cfg, err := b.readConfigKeys(ctx, req)
if err != nil {
return nil, err
}
polReq = keysutil.PolicyRequest{
Upsert: !cfg.DisableUpsert,
Storage: req.Storage,
Name: name,
Derived: contextSet,
Convergent: convergent,
}
keyType := d.Get("type").(string)
switch keyType {
case "aes128-gcm96":
polReq.KeyType = keysutil.KeyType_AES128_GCM96
case "aes256-gcm96":
polReq.KeyType = keysutil.KeyType_AES256_GCM96
case "chacha20-poly1305":
polReq.KeyType = keysutil.KeyType_ChaCha20_Poly1305
case "rsa-2048":
polReq.KeyType = keysutil.KeyType_RSA2048
case "rsa-3072":
polReq.KeyType = keysutil.KeyType_RSA3072
case "rsa-4096":
polReq.KeyType = keysutil.KeyType_RSA4096
case "ecdsa-p256", "ecdsa-p384", "ecdsa-p521":
return logical.ErrorResponse(fmt.Sprintf("key type %v not supported for this operation", keyType)), logical.ErrInvalidRequest
case "managed_key":
polReq.KeyType = keysutil.KeyType_MANAGED_KEY
case "aes128-cbc":
polReq.KeyType = keysutil.KeyType_AES128_CBC
case "aes256-cbc":
polReq.KeyType = keysutil.KeyType_AES256_CBC
default:
return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
}
} else {
polReq = keysutil.PolicyRequest{
Storage: req.Storage,
Name: name,
}
}
p, upserted, err = b.GetPolicy(ctx, polReq, b.GetRandomReader())
if err != nil {
return nil, err
}
if p == nil {
return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest
}
if !b.System().CachingDisabled() {
p.Lock(false)
}
defer p.Unlock()
// Process batch request items. If encryption of any request
// item fails, respectively mark the error in the response
// collection and continue to process other items.
warnAboutNonceUsage := false
successesInBatch := false
successfulRequests := 0
for i, item := range batchInputItems {
if batchResponseItems[i].Error != "" {
userErrorInBatch = true
continue
}
if item.Nonce != "" && !nonceAllowed(p) {
userErrorInBatch = true
batchResponseItems[i].Error = ErrNonceNotAllowed.Error()
continue
}
if !warnAboutNonceUsage && shouldWarnAboutNonceUsage(p, item.DecodedNonce) {
warnAboutNonceUsage = true
}
var factories []any
if item.PaddingScheme != "" {
paddingScheme, err := parsePaddingSchemeArg(p.Type, item.PaddingScheme)
if err != nil {
batchResponseItems[i].Error = fmt.Sprintf("'[%d].padding_scheme' invalid: %s", i, err.Error())
continue
}
factories = append(factories, paddingScheme)
}
if item.AssociatedData != "" {
if !p.Type.AssociatedDataSupported() {
batchResponseItems[i].Error = fmt.Sprintf("'[%d].associated_data' provided for non-AEAD cipher suite %v", i, p.Type.String())
continue
}
factories = append(factories, AssocDataFactory{item.AssociatedData})
}
if p.Type == keysutil.KeyType_MANAGED_KEY {
managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView)
if !ok {
batchResponseItems[i].Error = errors.New("unsupported system view").Error()
}
factories = append(factories, ManagedKeyFactory{
managedKeyParams: keysutil.ManagedKeyParameters{
ManagedKeySystemView: managedKeySystemView,
BackendUUID: b.backendUUID,
Context: ctx,
},
})
}
opts := keysutil.EncryptionOptions{
KeyVersion: item.KeyVersion,
Context: item.DecodedContext,
Nonce: item.DecodedNonce,
IV: item.DecodedIV,
}
ciphertext, err := p.EncryptWithOptions(opts, item.Plaintext, factories...)
if err != nil {
switch err.(type) {
case errutil.InternalError:
internalErrorInBatch = true
default:
userErrorInBatch = true
}
batchResponseItems[i].Error = err.Error()
continue
}
if ciphertext == "" {
userErrorInBatch = true
batchResponseItems[i].Error = fmt.Sprintf("empty ciphertext returned for input item %d", i)
continue
}
successesInBatch = true
keyVersion := item.KeyVersion
if keyVersion == 0 {
keyVersion = p.LatestVersion
}
batchResponseItems[i].Ciphertext = ciphertext
batchResponseItems[i].KeyVersion = keyVersion
successfulRequests++
}
resp := &logical.Response{}
if batchInputRaw != nil {
// Copy the references
for i := range batchInputItems {
batchResponseItems[i].Reference = batchInputItems[i].Reference
}
resp.Data = map[string]interface{}{
"batch_results": batchResponseItems,
}
} else {
if batchResponseItems[0].Error != "" {
if internalErrorInBatch {
return nil, errutil.InternalError{Err: batchResponseItems[0].Error}
}
return logical.ErrorResponse(batchResponseItems[0].Error), logical.ErrInvalidRequest
}
resp.Data = map[string]interface{}{
"ciphertext": batchResponseItems[0].Ciphertext,
"key_version": batchResponseItems[0].KeyVersion,
}
}
if constants.IsFIPS() && warnAboutNonceUsage {
resp.AddWarning("A provided nonce value was used within FIPS mode, this violates FIPS 140 compliance.")
}
if req.Operation == logical.CreateOperation && !upserted {
resp.AddWarning("Attempted creation of the key during the encrypt operation, but it was created beforehand")
}
// Increment the counter for successful operations
if err = b.incrementBillingCounts(ctx, uint64(successfulRequests)); err != nil {
b.Logger().Error("failed to track transit encrypt request count", "error", err.Error())
}
return batchRequestResponse(d, resp, req, successesInBatch, userErrorInBatch, internalErrorInBatch)
}
func nonceAllowed(p *keysutil.Policy) bool {
var supportedKeyType bool
switch p.Type {
case keysutil.KeyType_MANAGED_KEY:
return true
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305:
supportedKeyType = true
default:
supportedKeyType = false
}
if supportedKeyType && p.ConvergentEncryption && p.ConvergentVersion == 1 {
// We only use the user supplied nonce for v1 convergent encryption keys
return true
}
return false
}
// Depending on the errors in the batch, different status codes should be returned. User errors
// will return a 400 and precede internal errors which return a 500. The reasoning behind this is
// that user errors are non-retryable without making changes to the request, and should be surfaced
// to the user first.
func batchRequestResponse(d *framework.FieldData, resp *logical.Response, req *logical.Request, successesInBatch, userErrorInBatch, internalErrorInBatch bool) (*logical.Response, error) {
if userErrorInBatch || internalErrorInBatch {
var code int
switch {
case userErrorInBatch:
code = http.StatusBadRequest
case internalErrorInBatch:
code = http.StatusInternalServerError
}
if codeRaw, ok := d.GetOk("partial_failure_response_code"); ok && successesInBatch {
newCode := codeRaw.(int)
if newCode < 1 || newCode > 599 {
resp.AddWarning(fmt.Sprintf("invalid HTTP response code override from partial_failure_response_code, reverting to %d", code))
} else {
code = newCode
}
}
return logical.RespondWithStatusCode(resp, req, code)
}
return resp, nil
}
// shouldWarnAboutNonceUsage attempts to determine if we will use a provided nonce or not. Ideally this
// would be information returned through p.Encrypt but that would require an SDK api change and this is
// transit specific
func shouldWarnAboutNonceUsage(p *keysutil.Policy, userSuppliedNonce []byte) bool {
if len(userSuppliedNonce) == 0 {
return false
}
var supportedKeyType bool
switch p.Type {
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305:
supportedKeyType = true
default:
supportedKeyType = false
}
if supportedKeyType && p.ConvergentEncryption && p.ConvergentVersion == 1 {
// We only use the user supplied nonce for v1 convergent encryption keys
return true
}
if supportedKeyType && !p.ConvergentEncryption {
return true
}
return false
}
const pathEncryptHelpSyn = `Encrypt a plaintext value or a batch of plaintext
blocks using a named key`
const pathEncryptHelpDesc = `
This path uses the named key from the request path to encrypt a user provided
plaintext or a batch of plaintext blocks. The plaintext must be base64 encoded.
`