open-vault/builtin/logical/transit/path_byok.go
hc-github-team-secure-vault-core a486b13957
backport of commit 63ccb60b9a6dadf717e6813f9789c7194a2375de (#20877)
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-05-30 23:49:24 +00:00

207 lines
5.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package transit
import (
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"errors"
"fmt"
"strconv"
"strings"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/keysutil"
"github.com/hashicorp/vault/sdk/logical"
)
func (b *backend) pathBYOKExportKeys() *framework.Path {
return &framework.Path{
Pattern: "byok-export/" + framework.GenericNameRegex("destination") + "/" + framework.GenericNameRegex("source") + framework.OptionalParamRegex("version"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTransit,
OperationVerb: "byok",
OperationSuffix: "key|key-version",
},
Fields: map[string]*framework.FieldSchema{
"destination": {
Type: framework.TypeString,
Description: "Destination key to export to; usually the public wrapping key of another Transit instance.",
},
"source": {
Type: framework.TypeString,
Description: "Source key to export; could be any present key within Transit.",
},
"version": {
Type: framework.TypeString,
Description: "Optional version of the key to export, else all key versions are exported.",
},
"hash": {
Type: framework.TypeString,
Description: "Hash function to use for inner OAEP encryption. Defaults to SHA256.",
Default: "SHA256",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathPolicyBYOKExportRead,
},
HelpSynopsis: pathBYOKExportHelpSyn,
HelpDescription: pathBYOKExportHelpDesc,
}
}
func (b *backend) pathPolicyBYOKExportRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
dst := d.Get("destination").(string)
src := d.Get("source").(string)
version := d.Get("version").(string)
hash := d.Get("hash").(string)
dstP, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage,
Name: dst,
}, b.GetRandomReader())
if err != nil {
return nil, err
}
if dstP == nil {
return nil, fmt.Errorf("no such destination key to export to")
}
if !b.System().CachingDisabled() {
dstP.Lock(false)
}
defer dstP.Unlock()
srcP, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage,
Name: src,
}, b.GetRandomReader())
if err != nil {
return nil, err
}
if srcP == nil {
return nil, fmt.Errorf("no such source key for export")
}
if !b.System().CachingDisabled() {
srcP.Lock(false)
}
defer srcP.Unlock()
if !srcP.Exportable {
return logical.ErrorResponse("key is not exportable"), nil
}
retKeys := map[string]string{}
switch version {
case "":
for k, v := range srcP.Keys {
exportKey, err := getBYOKExportKey(dstP, srcP, &v, hash)
if err != nil {
return nil, err
}
retKeys[k] = exportKey
}
default:
var versionValue int
if version == "latest" {
versionValue = srcP.LatestVersion
} else {
version = strings.TrimPrefix(version, "v")
versionValue, err = strconv.Atoi(version)
if err != nil {
return logical.ErrorResponse("invalid key version"), logical.ErrInvalidRequest
}
}
if versionValue < srcP.MinDecryptionVersion {
return logical.ErrorResponse("version for export is below minimum decryption version"), logical.ErrInvalidRequest
}
key, ok := srcP.Keys[strconv.Itoa(versionValue)]
if !ok {
return logical.ErrorResponse("version does not exist or cannot be found"), logical.ErrInvalidRequest
}
exportKey, err := getBYOKExportKey(dstP, srcP, &key, hash)
if err != nil {
return nil, err
}
retKeys[strconv.Itoa(versionValue)] = exportKey
}
resp := &logical.Response{
Data: map[string]interface{}{
"name": srcP.Name,
"type": srcP.Type.String(),
"keys": retKeys,
},
}
return resp, nil
}
func getBYOKExportKey(dstP *keysutil.Policy, srcP *keysutil.Policy, key *keysutil.KeyEntry, hash string) (string, error) {
if dstP == nil || srcP == nil {
return "", errors.New("nil policy provided")
}
var targetKey interface{}
switch srcP.Type {
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305, keysutil.KeyType_HMAC:
targetKey = key.Key
case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096:
targetKey = key.RSAKey
case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ECDSA_P384, keysutil.KeyType_ECDSA_P521:
var curve elliptic.Curve
switch srcP.Type {
case keysutil.KeyType_ECDSA_P384:
curve = elliptic.P384()
case keysutil.KeyType_ECDSA_P521:
curve = elliptic.P521()
default:
curve = elliptic.P256()
}
pubKey := ecdsa.PublicKey{
Curve: curve,
X: key.EC_X,
Y: key.EC_Y,
}
targetKey = &ecdsa.PrivateKey{
PublicKey: pubKey,
D: key.EC_D,
}
case keysutil.KeyType_ED25519:
targetKey = ed25519.PrivateKey(key.Key)
default:
return "", fmt.Errorf("unable to export to unknown key type: %v", srcP.Type)
}
hasher, err := parseHashFn(hash)
if err != nil {
return "", err
}
return dstP.WrapKey(0, targetKey, srcP.Type, hasher)
}
const pathBYOKExportHelpSyn = `Securely export named encryption or signing key`
const pathBYOKExportHelpDesc = `
This path is used to export the named keys that are configured as
exportable.
Unlike the regular /export/:name[/:version] paths, this path uses
the same encryption specification /import, allowing secure migration
of keys between clusters to enable workloads to communicate between
them.
Presently this only works for RSA destination keys.
`