09939f0ba9
* Allow passing AssociatedData factories in keysutil This allows the high-level, algorithm-agnostic Encrypt/Decrypt with Factory to pass in AssociatedData, and potentially take multiple factories (to allow KMS keys to work). On AEAD ciphers with a relevant factory, an AssociatedData factory will be used to populate the AdditionalData field of the SymmetricOpts struct, using it in the AEAD Seal process. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add associated_data to Transit Encrypt/Decrypt API This allows passing the associated_data (the last AD in AEAD) to Transit's encrypt/decrypt when using an AEAD cipher (currently aes128-gcm96, aes256-gcm96, and chacha20-poly1305). We err if this parameter is passed on non-AEAD ciphers presently. This associated data can be safely transited in plaintext, without risk of modifications. In the event of tampering with either the ciphertext or the associated data, decryption will fail. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add to documentation Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
562 lines
18 KiB
Go
562 lines
18 KiB
Go
package transit
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"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"`
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
type AssocDataFactory struct {
|
|
Encoded string
|
|
}
|
|
|
|
func (a AssocDataFactory) GetAssociatedData() ([]byte, error) {
|
|
return base64.StdEncoding.DecodeString(a.Encoded)
|
|
}
|
|
|
|
func (b *backend) pathEncrypt() *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "encrypt/" + framework.GenericNameRegex("name"),
|
|
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",
|
|
},
|
|
|
|
"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.
|
|
`,
|
|
},
|
|
},
|
|
|
|
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["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 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),
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
polReq = keysutil.PolicyRequest{
|
|
Upsert: true,
|
|
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 "ecdsa-p256", "ecdsa-p384", "ecdsa-p521":
|
|
return logical.ErrorResponse(fmt.Sprintf("key type %v not supported for this operation", keyType)), logical.ErrInvalidRequest
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
for i, item := range batchInputItems {
|
|
if batchResponseItems[i].Error != "" {
|
|
continue
|
|
}
|
|
|
|
if !warnAboutNonceUsage && shouldWarnAboutNonceUsage(p, item.DecodedNonce) {
|
|
warnAboutNonceUsage = true
|
|
}
|
|
|
|
var factory interface{}
|
|
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
|
|
}
|
|
|
|
factory = AssocDataFactory{item.AssociatedData}
|
|
}
|
|
|
|
ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, item.Plaintext, factory)
|
|
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
|
|
}
|
|
|
|
resp := &logical.Response{}
|
|
if batchInputRaw != nil {
|
|
resp.Data = map[string]interface{}{
|
|
"batch_results": batchResponseItems,
|
|
}
|
|
} else {
|
|
if batchResponseItems[0].Error != "" {
|
|
p.Unlock()
|
|
|
|
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")
|
|
}
|
|
|
|
p.Unlock()
|
|
|
|
return batchRequestResponse(d, resp, req, successesInBatch, userErrorInBatch, internalErrorInBatch)
|
|
}
|
|
|
|
// 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) {
|
|
switch {
|
|
case userErrorInBatch:
|
|
code := http.StatusBadRequest
|
|
if successesInBatch {
|
|
if codeRaw, ok := d.GetOk("partial_failure_response_code"); ok {
|
|
code = codeRaw.(int)
|
|
if code < 1 || code > 599 {
|
|
resp.AddWarning("invalid HTTP response code override from partial_failure_response_code, reverting to HTTP 400")
|
|
code = http.StatusBadRequest
|
|
}
|
|
}
|
|
}
|
|
return logical.RespondWithStatusCode(resp, req, code)
|
|
case internalErrorInBatch:
|
|
return logical.RespondWithStatusCode(resp, req, http.StatusInternalServerError)
|
|
}
|
|
|
|
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.
|
|
`
|