a486b13957
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
207 lines
5.5 KiB
Go
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.
|
|
`
|