feat(KMS): add BsrKeys type and req. receiver functions
This commit is contained in:
1 parent
98d3e59372
commit
aa10d1dbfe
9 files changed
+2110
No files matched your search
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package is
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Nil checks if the interface is nil
|
||||
func Nil(i any) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
switch reflect.TypeOf(i).Kind() {
|
||||
case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Slice:
|
||||
return reflect.ValueOf(i).IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrUnknown represents an unknown error
|
||||
var ErrUnknown = errors.New("unknown error")
|
||||
|
||||
// ErrInvalidParameter represents an invalid parameter error
|
||||
var ErrInvalidParameter = errors.New("invalid parameter")
|
||||
|
||||
// ErrGenKey represents a key gen error
|
||||
var ErrGenKey = errors.New("error occurred during key generation")
|
||||
|
||||
// ErrEncrypt represents an encryption error
|
||||
var ErrEncrypt = errors.New("error occurred during encrypt")
|
||||
|
||||
// ErrDecrypt represents a decryption error
|
||||
var ErrDecrypt = errors.New("error occurred during decrypt")
|
||||
|
||||
// ErrEncode represents an encoding error
|
||||
var ErrEncode = errors.New("error occurred during encode")
|
||||
|
||||
// ErrDecode represents a decoding error
|
||||
var ErrDecode = errors.New("error occurred during decode")
|
||||
|
||||
// ErrSign represents a signing error
|
||||
var ErrSign = errors.New("error occurred during signing")
|
||||
@@ -0,0 +1,467 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/is"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
wrappingEd "github.com/hashicorp/go-kms-wrapping/v2/ed25519"
|
||||
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Keys are the keys required/associated with a BSR for crypto operations.
|
||||
//
|
||||
// Operations on this type are concurrently safe.
|
||||
type Keys struct {
|
||||
// WrappedBsrKey is the "bsr key" which is a wrapped AES-GCM key
|
||||
WrappedBsrKey *wrapping.KeyInfo
|
||||
|
||||
// WrappedPrivKey is the BSR's wrapped ed25519.PrivateKey
|
||||
WrappedPrivKey *wrapping.KeyInfo
|
||||
|
||||
// BsrKey is the plaintext "bsr key" which is an AES-GCM key
|
||||
BsrKey *wrapping.KeyInfo
|
||||
|
||||
// PrivKey is the BSR's plaintext ed25519.PrivateKey
|
||||
PrivKey *wrapping.KeyInfo
|
||||
|
||||
// PubKey is the BSR's plaintext ed25519.PublicKey
|
||||
PubKey *wrapping.KeyInfo
|
||||
|
||||
// PubKeySelfSignature is a self-signature of the BSR's plaintext
|
||||
// ed25519.PublicKey created with its ed25519.PrivateKey
|
||||
PubKeySelfSignature *wrapping.SigInfo
|
||||
|
||||
// PubKeyBsrSignature is a signature of the BSR's plaintext ed25519.Public
|
||||
// key created with the BsrKey (AES-GCM)
|
||||
PubKeyBsrSignature *wrapping.SigInfo
|
||||
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// CreateKeys creates new bsr keys, wrapping and signing keys as required
|
||||
// using the provided bsrWrapper. Supported options: WithRandomReader
|
||||
func CreateKeys(ctx context.Context, bsrWrapper wrapping.Wrapper, sessionId string, opt ...Option) (*Keys, error) {
|
||||
const op = "kms.CreateBsrKeys"
|
||||
switch {
|
||||
case sessionId == "":
|
||||
return nil, fmt.Errorf("%s: missing session id: %w", op, ErrInvalidParameter)
|
||||
case is.Nil(bsrWrapper):
|
||||
return nil, fmt.Errorf("%s: missing external bsr wrapper: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
if opts.withRandomReader == nil {
|
||||
opts.withRandomReader = rand.Reader
|
||||
}
|
||||
|
||||
retKeys := &Keys{}
|
||||
|
||||
bsrKey := make([]byte, 32)
|
||||
n, err := opts.withRandomReader.Read(bsrKey)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("%s: error reading random bytes for bsr: %w", op, ErrGenKey)
|
||||
case n != 32:
|
||||
return nil, fmt.Errorf("%s: wanted 32 bytes and got %d: %w", op, n, ErrGenKey)
|
||||
}
|
||||
bsrBlob, err := bsrWrapper.Encrypt(ctx, bsrKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to encrypt bsr key: %w", op, ErrEncrypt)
|
||||
}
|
||||
marshaledBsrBlob, err := proto.Marshal(bsrBlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to marshal bsr key %w: %w", op, err, ErrEncode)
|
||||
}
|
||||
retKeys.WrappedBsrKey = &wrapping.KeyInfo{
|
||||
KeyId: sessionId,
|
||||
WrappedKey: marshaledBsrBlob,
|
||||
KeyType: wrapping.KeyType_Aes256,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign, wrapping.KeyPurpose_Encrypt, wrapping.KeyPurpose_Decrypt},
|
||||
}
|
||||
retKeys.BsrKey = &wrapping.KeyInfo{
|
||||
KeyId: sessionId,
|
||||
Key: bsrKey,
|
||||
KeyType: wrapping.KeyType_Aes256,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign, wrapping.KeyPurpose_Encrypt, wrapping.KeyPurpose_Decrypt},
|
||||
}
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(opts.withRandomReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to generate bsr ed25519 key-pair %w: %w", op, err, ErrGenKey)
|
||||
}
|
||||
privBlob, err := bsrWrapper.Encrypt(ctx, priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to encrypt bsr ed25519 priv key %w: %w", op, err, ErrEncrypt)
|
||||
}
|
||||
marshaledPrivBlob, err := proto.Marshal(privBlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to marshal bsr ed25519 priv key %w: %w", op, err, ErrEncode)
|
||||
}
|
||||
retKeys.WrappedPrivKey = &wrapping.KeyInfo{
|
||||
KeyId: sessionId,
|
||||
WrappedKey: marshaledPrivBlob,
|
||||
KeyType: wrapping.KeyType_Ed25519,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign},
|
||||
}
|
||||
retKeys.PrivKey = &wrapping.KeyInfo{
|
||||
KeyId: sessionId,
|
||||
Key: priv,
|
||||
KeyType: wrapping.KeyType_Ed25519,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign},
|
||||
}
|
||||
retKeys.PubKey = &wrapping.KeyInfo{
|
||||
KeyId: sessionId,
|
||||
Key: pub,
|
||||
KeyType: wrapping.KeyType_Ed25519,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Verify},
|
||||
}
|
||||
privKeySigner, err := wrappingEd.NewSigner(ctx, wrappingEd.WithPrivKey(priv), wrapping.WithKeyId(sessionId))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
retKeys.PubKeySelfSignature, err = privKeySigner.Sign(ctx, pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w: %w", op, err, ErrSign)
|
||||
}
|
||||
|
||||
bsrKeyWrapper := aead.NewWrapper()
|
||||
if _, err := bsrKeyWrapper.SetConfig(ctx, wrapping.WithKeyId(sessionId)); err != nil {
|
||||
return nil, fmt.Errorf("%s: setting config on aead wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
if err := bsrKeyWrapper.SetAesGcmKeyBytes(bsrKey); err != nil {
|
||||
return nil, fmt.Errorf("%s: error setting key bytes on aead wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
marshaledSig, err := crypto.HmacSha256(ctx, pub, bsrKeyWrapper, crypto.WithMarshaledSigInfo())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error signing pub key with bsr key %w: %w", op, err, ErrSign)
|
||||
}
|
||||
retKeys.PubKeyBsrSignature = &wrapping.SigInfo{}
|
||||
if err := proto.Unmarshal([]byte(marshaledSig), retKeys.PubKeyBsrSignature); err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to unmarshal sig info %w: %w", op, err, ErrEncode)
|
||||
}
|
||||
|
||||
return retKeys, nil
|
||||
}
|
||||
|
||||
// UnwrapBsrKey will unwrap the bsr key (k.WrappedBsrKey which is an AES-GCM)
|
||||
// using the provide bsr kms. The k.BsrKey will be set to the unwrapped key
|
||||
// key and returned in the form of an aead.Wrapper
|
||||
//
|
||||
// This is a concurrently safe operation.
|
||||
func (k *Keys) UnwrapBsrKey(ctx context.Context, bsrWrapper wrapping.Wrapper, opt ...Option) (*aead.Wrapper, error) {
|
||||
const op = "kms.(BsrKeys).UnwrapBsrKey"
|
||||
if k == nil {
|
||||
return nil, fmt.Errorf("%s: nil bsr keys: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
|
||||
// support just using an existing lock...
|
||||
if !opts.withAlreadyLocked {
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
}
|
||||
|
||||
switch {
|
||||
case util.IsNil(bsrWrapper):
|
||||
return nil, fmt.Errorf("%s: missing bsr wrapper: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey == nil:
|
||||
return nil, fmt.Errorf("%s: missing wrapped bsr key: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey.KeyId == "":
|
||||
return nil, fmt.Errorf("%s: missing wrapped bsr key id: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey.WrappedKey == nil:
|
||||
return nil, fmt.Errorf("%s: missing wrapped bsr key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey.KeyType != wrapping.KeyType_Aes256:
|
||||
return nil, keyTypeError(ctx, op, wrapping.KeyType_Aes256, k.WrappedBsrKey.KeyType)
|
||||
case k.WrappedBsrKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return nil, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.WrappedBsrKey.KeyEncoding)
|
||||
}
|
||||
|
||||
var blob wrapping.BlobInfo
|
||||
|
||||
if err := proto.Unmarshal(k.WrappedBsrKey.WrappedKey, &blob); err != nil {
|
||||
return nil, fmt.Errorf("%s: error unmarshaling wrapped bsr key %w: %w", op, err, ErrDecode)
|
||||
}
|
||||
|
||||
keyBytes, err := bsrWrapper.Decrypt(ctx, &blob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error decrypting wrapped bsr key %w: %w", op, err, ErrDecrypt)
|
||||
}
|
||||
|
||||
bsrKeyWrapper := aead.NewWrapper()
|
||||
if _, err := bsrKeyWrapper.SetConfig(ctx, wrapping.WithKeyId(k.WrappedBsrKey.KeyId)); err != nil {
|
||||
return nil, fmt.Errorf("%s: error setting config on aead wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
if err := bsrKeyWrapper.SetAesGcmKeyBytes(keyBytes); err != nil {
|
||||
return nil, fmt.Errorf("%s: error setting key bytes on aead wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
|
||||
k.BsrKey = &wrapping.KeyInfo{
|
||||
Key: keyBytes,
|
||||
KeyId: k.WrappedBsrKey.KeyId,
|
||||
KeyType: k.WrappedBsrKey.KeyType,
|
||||
KeyPurposes: k.WrappedBsrKey.KeyPurposes,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
}
|
||||
|
||||
return bsrKeyWrapper, nil
|
||||
}
|
||||
|
||||
// UnwrapPrivKey will unwrap the priv key (k.WrappedPrivKey which is an
|
||||
// ed25519.PrivateKey) using the provide bsr kms. The k.PrivKey will be set to
|
||||
// the unwrapped key and returned in the form of an ed25519.PrivateKey
|
||||
//
|
||||
// This is a concurrently safe operation.
|
||||
func (k *Keys) UnwrapPrivKey(ctx context.Context, bsrWrapper wrapping.Wrapper) (ed25519.PrivateKey, error) {
|
||||
const op = "kms.(BsrKeys).UnwrapPrivKey"
|
||||
if k == nil {
|
||||
return nil, fmt.Errorf("%s: nil bsr keys: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
|
||||
switch {
|
||||
case util.IsNil(bsrWrapper):
|
||||
return nil, fmt.Errorf("%s: missing bsr wrapper: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedPrivKey == nil:
|
||||
return nil, fmt.Errorf("%s: missing wrapped priv key: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedPrivKey.KeyId == "":
|
||||
return nil, fmt.Errorf("%s: missing wrapped priv key id: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedPrivKey.WrappedKey == nil:
|
||||
return nil, fmt.Errorf("%s: missing wrapped priv key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedPrivKey.KeyType != wrapping.KeyType_Ed25519:
|
||||
return nil, keyTypeError(ctx, op, wrapping.KeyType_Ed25519, k.WrappedPrivKey.KeyType)
|
||||
case k.WrappedPrivKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return nil, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.WrappedPrivKey.WrappedKeyEncoding)
|
||||
}
|
||||
var privKeyBlob wrapping.BlobInfo
|
||||
if err := proto.Unmarshal(k.WrappedPrivKey.WrappedKey, &privKeyBlob); err != nil {
|
||||
return nil, fmt.Errorf("%s: error unmarshaling wrapped priv key %w: %w", op, err, ErrDecode)
|
||||
}
|
||||
privKeyBytes, err := bsrWrapper.Decrypt(ctx, &privKeyBlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error decrypting wrapped priv key %w: %w", op, err, ErrDecrypt)
|
||||
}
|
||||
if len(privKeyBytes) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("%s: priv key must be %d bytes; got %d: %w", op, ed25519.PrivateKeySize, len(privKeyBytes), ErrInvalidParameter)
|
||||
}
|
||||
|
||||
k.PrivKey = &wrapping.KeyInfo{
|
||||
Key: privKeyBytes,
|
||||
KeyId: k.WrappedPrivKey.KeyId,
|
||||
KeyType: k.WrappedPrivKey.KeyType,
|
||||
KeyPurposes: k.WrappedPrivKey.KeyPurposes,
|
||||
KeyEncoding: wrapping.KeyEncoding_Bytes,
|
||||
}
|
||||
|
||||
return ed25519.PrivateKey(privKeyBytes), nil
|
||||
}
|
||||
|
||||
// VerifyPubKeyBsrSignature will verify the pub key signature created with the
|
||||
// bsr key. It will first try to use k.BsrKey, if that's not available it will
|
||||
// attempt to unwrap k.WrappedBsrKey and use it for verification. Supported
|
||||
// options: WithBsrWrapper which is required if using k.WrappedBsrKey to verify
|
||||
// the signature.
|
||||
//
|
||||
// This is a concurrently safe operation.
|
||||
func (k *Keys) VerifyPubKeyBsrSignature(ctx context.Context, opt ...Option) (bool, error) {
|
||||
const op = "kms.(BsrKeys).VerifyPubKeyBsrSignature"
|
||||
if k == nil {
|
||||
return false, fmt.Errorf("%s: nil bsr keys: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
// needs a write lock since it may need to UnwrapBsrKey(...)
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
|
||||
switch {
|
||||
case k.BsrKey == nil && k.WrappedBsrKey == nil:
|
||||
return false, fmt.Errorf("%s: missing bsr key and wrapped bsr key: %w", op, ErrInvalidParameter)
|
||||
case k.BsrKey != nil && k.BsrKey.Key == nil:
|
||||
return false, fmt.Errorf("%s: missing bsr key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey != nil && k.WrappedBsrKey.WrappedKey == nil:
|
||||
return false, fmt.Errorf("%s: missing wrapped bsr key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.PubKey == nil:
|
||||
return false, fmt.Errorf("%s: missing pub key: %w", op, ErrInvalidParameter)
|
||||
case k.PubKeyBsrSignature == nil:
|
||||
return false, fmt.Errorf("%s: missing pub key signature: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
var bsrKeyWrapper *aead.Wrapper
|
||||
switch {
|
||||
case k.BsrKey != nil:
|
||||
switch {
|
||||
case k.BsrKey.KeyType != wrapping.KeyType_Aes256:
|
||||
return false, keyTypeError(ctx, op, wrapping.KeyType_Aes256, k.BsrKey.KeyType)
|
||||
case k.BsrKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return false, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.BsrKey.KeyEncoding)
|
||||
}
|
||||
bsrKeyWrapper = aead.NewWrapper()
|
||||
if _, err := bsrKeyWrapper.SetConfig(ctx, wrapping.WithKeyId(k.BsrKey.KeyId)); err != nil {
|
||||
return false, fmt.Errorf("%s: error setting config on bsr key wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
if err := bsrKeyWrapper.SetAesGcmKeyBytes(k.BsrKey.Key); err != nil {
|
||||
return false, fmt.Errorf("%s: error setting key bytes on bsr key wrapper %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
case k.WrappedBsrKey != nil:
|
||||
switch {
|
||||
case util.IsNil(opts.withBsrWrapper):
|
||||
return false, fmt.Errorf("%s: missing bsr wrapper: %w", op, ErrInvalidParameter)
|
||||
case k.WrappedBsrKey.KeyType != wrapping.KeyType_Aes256:
|
||||
return false, keyTypeError(ctx, op, wrapping.KeyType_Aes256, k.WrappedBsrKey.KeyType)
|
||||
case k.WrappedBsrKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return false, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.WrappedBsrKey.KeyEncoding)
|
||||
}
|
||||
var err error
|
||||
if bsrKeyWrapper, err = k.UnwrapBsrKey(ctx, opts.withBsrWrapper, withAlreadyLocked(true)); err != nil {
|
||||
return false, fmt.Errorf("%s: error unwrapping bsr key: %w", op, err)
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("%s: missing bsr key and wrapped bsr key: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
marshaledSigInfo, err := crypto.HmacSha256(ctx, ed25519.PublicKey(k.PubKey.Key), bsrKeyWrapper, crypto.WithMarshaledSigInfo())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: error signing pub key %w: %w", op, err, ErrSign)
|
||||
}
|
||||
var sigInfo wrapping.SigInfo
|
||||
if err = proto.Unmarshal([]byte(marshaledSigInfo), &sigInfo); err != nil {
|
||||
return false, fmt.Errorf("%s: error unmarshaling sig info %w: %w", op, err, ErrDecode)
|
||||
}
|
||||
bsrKeyId, err := bsrKeyWrapper.KeyId(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: error getting bsr key id %w: %w", op, err, ErrUnknown)
|
||||
}
|
||||
if k.PubKeyBsrSignature.KeyInfo.KeyId != bsrKeyId {
|
||||
return false, fmt.Errorf("%s: pub signature key id %q doesn't match verifying key id %q: %w", op, k.PubKeyBsrSignature.KeyInfo.KeyId, bsrKeyId, ErrInvalidParameter)
|
||||
}
|
||||
if bytes.Compare(k.PubKeyBsrSignature.Signature, sigInfo.Signature) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// VerifyPubKeySelfSignature will verify the self-signed pub key signature using
|
||||
// k.PubKey. Note: this will tell you the signature is correct, but not if the
|
||||
// public key is the right key; you want to use VerifyPubKeyBsrSignature(...)
|
||||
// for that.
|
||||
//
|
||||
// This is a concurrently safe operation.
|
||||
func (k *Keys) VerifyPubKeySelfSignature(ctx context.Context) (bool, error) {
|
||||
const op = "kms.(BsrKeys).VerifyPubKeySelfSignature"
|
||||
if k == nil {
|
||||
return false, fmt.Errorf("%s: bsr keys: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
k.l.RLock()
|
||||
defer k.l.RUnlock()
|
||||
|
||||
switch {
|
||||
case k.PubKey == nil:
|
||||
return false, fmt.Errorf("%s: missing pub key: %w", op, ErrInvalidParameter)
|
||||
case k.PubKey.Key == nil:
|
||||
return false, fmt.Errorf("%s: missing pub key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.PubKey.KeyType != wrapping.KeyType_Ed25519:
|
||||
return false, keyTypeError(ctx, op, wrapping.KeyType_Ed25519, k.PubKey.KeyType)
|
||||
case k.PubKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return false, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.PubKey.KeyEncoding)
|
||||
case len(k.PubKey.Key) != ed25519.PublicKeySize:
|
||||
return false, fmt.Errorf("%s: expected public key with %d bytes and got %d: %w", op, ed25519.PublicKeySize, len(k.PubKey.Key), ErrInvalidParameter)
|
||||
case k.PubKeySelfSignature == nil:
|
||||
return false, fmt.Errorf("%s: missing pub key self signature: %w", op, ErrInvalidParameter)
|
||||
case k.PubKeySelfSignature.KeyInfo.KeyId != k.PubKey.KeyId:
|
||||
return false, fmt.Errorf("%s: pub self signature key id %q doesn't match verifying pub key id %q: %w", op, k.PubKeySelfSignature.KeyInfo.KeyId, k.PubKey.KeyId, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
verifier, err := wrappingEd.NewVerifier(ctx, wrappingEd.WithPubKey(k.PubKey.Key))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
|
||||
ok, err := verifier.Verify(ctx, k.PubKey.Key, k.PubKeySelfSignature)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// SignWithPrivKey will sign the msg with the BsrKeys.PrivKey (aka the BSR's
|
||||
// ed25519.PrivateKey). Typical usage is to sign the BSR's checksums file.
|
||||
//
|
||||
// This is a concurrently safe operation.
|
||||
func (k *Keys) SignWithPrivKey(ctx context.Context, msg []byte) (*wrapping.SigInfo, error) {
|
||||
const op = "kms.(BsrKeys).SignWithPrivKey"
|
||||
if k == nil {
|
||||
return nil, fmt.Errorf("%s: nil bsr keys: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
k.l.RLock()
|
||||
defer k.l.RUnlock()
|
||||
|
||||
switch {
|
||||
case msg == nil:
|
||||
return nil, fmt.Errorf("%s: missing msg: %w", op, ErrInvalidParameter)
|
||||
case k.PrivKey == nil:
|
||||
return nil, fmt.Errorf("%s: missing priv key: %w", op, ErrInvalidParameter)
|
||||
case k.PrivKey.KeyId == "":
|
||||
return nil, fmt.Errorf("%s: missing priv key id: %w", op, ErrInvalidParameter)
|
||||
case k.PrivKey.Key == nil:
|
||||
return nil, fmt.Errorf("%s: missing priv key bytes: %w", op, ErrInvalidParameter)
|
||||
case k.PrivKey.KeyType != wrapping.KeyType_Ed25519:
|
||||
return nil, keyTypeError(ctx, op, wrapping.KeyType_Ed25519, k.PrivKey.KeyType)
|
||||
case k.PrivKey.KeyEncoding != wrapping.KeyEncoding_Bytes:
|
||||
return nil, keyEncodingError(ctx, op, wrapping.KeyEncoding_Bytes, k.PrivKey.KeyEncoding)
|
||||
case len(k.PrivKey.Key) != ed25519.PrivateKeySize:
|
||||
return nil, fmt.Errorf("%s: expected priv key with %d bytes and got %d: %w", op, ed25519.PrivateKeySize, len(k.PrivKey.Key), ErrInvalidParameter)
|
||||
}
|
||||
|
||||
s, err := wrappingEd.NewSigner(ctx,
|
||||
wrappingEd.WithPrivKey(k.PrivKey.Key),
|
||||
wrapping.WithKeyId(k.PrivKey.KeyId),
|
||||
wrapping.WithKeyEncoding(k.PrivKey.KeyEncoding),
|
||||
wrapping.WithKeyPurposes(k.PrivKey.KeyPurposes...),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to create priv key signer: %w", op, err)
|
||||
}
|
||||
si, err := s.Sign(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w: %w", op, err, ErrSign)
|
||||
}
|
||||
return si, nil
|
||||
}
|
||||
|
||||
func keyTypeError(ctx context.Context, fromOp string, want wrapping.KeyType, got wrapping.KeyType) error {
|
||||
return fmt.Errorf(
|
||||
"%s: unexpected key type %q; expected %q: %w",
|
||||
fromOp,
|
||||
wrapping.KeyType_name[int32(got)],
|
||||
wrapping.KeyType_name[int32(want)],
|
||||
ErrInvalidParameter,
|
||||
)
|
||||
}
|
||||
|
||||
func keyEncodingError(ctx context.Context, fromOp string, want wrapping.KeyEncoding, got wrapping.KeyEncoding) error {
|
||||
return fmt.Errorf(
|
||||
"%s: unexpected key encoding %q; expected %q: %w",
|
||||
fromOp,
|
||||
wrapping.KeyEncoding_name[int32(got)],
|
||||
wrapping.KeyEncoding_name[int32(want)],
|
||||
ErrInvalidParameter,
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test_GetOpts provides unit tests for GetOpts and all the options
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("WithRandomReader", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
opts := getOpts(WithRandomReader(rand.Reader))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withRandomReader = rand.Reader
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct
|
||||
func getOpts(opt ...Option) options {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Option - how Options are passed as arguments
|
||||
type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withBsrWrapper wrapping.Wrapper
|
||||
withRandomReader io.Reader
|
||||
withAlreadyLocked bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{}
|
||||
}
|
||||
|
||||
// WithBsrWrapper sets the external Bsr wrapper for a KMS
|
||||
func WithBsrWrapper(w wrapping.Wrapper) Option {
|
||||
return func(o *options) {
|
||||
o.withBsrWrapper = w
|
||||
}
|
||||
}
|
||||
|
||||
// WithRandomReader(...) option allows an optional random reader to be
|
||||
// provided. By default the reader from crypto/rand will be used.
|
||||
func WithRandomReader(randomReader io.Reader) Option {
|
||||
return func(o *options) {
|
||||
o.withRandomReader = randomReader
|
||||
}
|
||||
}
|
||||
|
||||
// not exported... just an internal option
|
||||
func withAlreadyLocked(isLockedAlready bool) Option {
|
||||
return func(o *options) {
|
||||
o.withAlreadyLocked = isLockedAlready
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
)
|
||||
|
||||
// TestWrapper initializes an AEAD wrapping.Wrapper for testing
|
||||
func TestWrapper(t testing.TB) wrapping.Wrapper {
|
||||
t.Helper()
|
||||
rootKey := make([]byte, 32)
|
||||
n, err := rand.Read(rootKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 32 {
|
||||
t.Fatal(n)
|
||||
}
|
||||
root := aead.NewWrapper()
|
||||
_, err = root.SetConfig(context.Background(), wrapping.WithKeyId(base64.StdEncoding.EncodeToString(rootKey)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := root.SetAesGcmKeyBytes(rootKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// MockReader provides a mock reader for testing
|
||||
type MockReader struct {
|
||||
readCount int
|
||||
|
||||
// WithMockReadOn determines which read attempt the mock read results should
|
||||
// be returned on.
|
||||
WithMockReadOn int
|
||||
|
||||
// WithError specifies a mock read result of the specified error
|
||||
WithError error
|
||||
|
||||
// WithBytesRead specifies a mock read result of the specified bytes read
|
||||
WithBytesRead int
|
||||
|
||||
// Reader is the underlying reader
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
// Read implements the mock read operation. Mock values supported: WithBytesRead, WithMockReadOn
|
||||
func (m *MockReader) Read(p []byte) (n int, err error) {
|
||||
m.readCount++
|
||||
if m.readCount == m.WithMockReadOn {
|
||||
_, _ = m.Reader.Read(p)
|
||||
return m.WithBytesRead, m.WithError
|
||||
}
|
||||
return m.Reader.Read(p)
|
||||
}
|
||||
|
||||
type MockWrapper struct {
|
||||
// Wrapper is the underlying wrapping.Wrapper which is used to provide the
|
||||
// mock's default behavior
|
||||
Wrapper wrapping.Wrapper
|
||||
|
||||
encryptCount int
|
||||
|
||||
// WithEncryptErrorOn determines which encrypt attempt the mock encrypt
|
||||
// error should be returned on.
|
||||
WithEncryptErrorOn int
|
||||
// EncryptErr is a mock value to return for the Encrypt(...) operation
|
||||
EncryptErr error
|
||||
|
||||
// DecryptErr is a mock value to return for the Decrypt(...) operation
|
||||
DecryptErr error
|
||||
|
||||
// KeyIdErr is a mock value to return for the KeyId(...) operation
|
||||
KeyIdErr error
|
||||
|
||||
// KeyIdReturned is a mock value to return for the KeyId(...) operation
|
||||
KeyIdReturned string
|
||||
|
||||
keyBytesCount int
|
||||
// WithKeyBytesErrorOn determines which key bytes attempt the mock key bytes
|
||||
// error should be returned on.
|
||||
WithKeyBytesErrorOn int
|
||||
}
|
||||
|
||||
// Type of the wrapper. No mock values supported
|
||||
func (w *MockWrapper) Type(ctx context.Context) (wrapping.WrapperType, error) {
|
||||
return w.Wrapper.Type(ctx)
|
||||
}
|
||||
|
||||
// KeyId is the id of the key currently used for encryption operations. Mock
|
||||
// values supported: KeyIdErr, KeyIdReturned
|
||||
func (w *MockWrapper) KeyId(ctx context.Context) (string, error) { // nolint
|
||||
switch {
|
||||
case w.KeyIdErr != nil:
|
||||
return "", w.KeyIdErr
|
||||
case w.KeyIdReturned != "":
|
||||
return w.KeyIdReturned, nil
|
||||
default:
|
||||
return w.Wrapper.KeyId(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfig applies the given options to a wrapper and returns
|
||||
// configuration information. No mock values supported.
|
||||
func (w *MockWrapper) SetConfig(ctx context.Context, options ...wrapping.Option) (*wrapping.WrapperConfig, error) {
|
||||
return w.Wrapper.SetConfig(ctx, options...)
|
||||
}
|
||||
|
||||
// Decrypt decrypts the given byte slice and stores the resulting information in
|
||||
// the returned byte slice. Mock values supported: DecryptErr
|
||||
func (w *MockWrapper) Decrypt(ctx context.Context, ciphertext *wrapping.BlobInfo, options ...wrapping.Option) ([]byte, error) {
|
||||
if w.DecryptErr != nil {
|
||||
return nil, w.DecryptErr
|
||||
}
|
||||
return w.Wrapper.Decrypt(ctx, ciphertext, options...)
|
||||
}
|
||||
|
||||
// Encrypt encrypts the given byte slice. Mock values supported: EncryptErr and WithEncryptErrorOn
|
||||
func (w *MockWrapper) Encrypt(ctx context.Context, plaintext []byte, options ...wrapping.Option) (*wrapping.BlobInfo, error) {
|
||||
w.encryptCount++
|
||||
if w.encryptCount == w.WithEncryptErrorOn {
|
||||
return nil, w.EncryptErr
|
||||
}
|
||||
return w.Wrapper.Encrypt(ctx, plaintext, options...)
|
||||
}
|
||||
@@ -77,6 +77,8 @@ const (
|
||||
Decode Code = 303 // Decode represents an error occurred during the underlying decoding/unmarshaling process
|
||||
GenKey Code = 304 // GenKey represents an error occurred during the underlying key generation process
|
||||
GenCert Code = 305 // GenCert represents an error occurred during the underlying certificate generation process
|
||||
Sign Code = 306 // Sign represents an error occurred during the underlying signing process
|
||||
Verify Code = 307 // Verify represents an error occurred during the underlying verification process
|
||||
|
||||
// General system errors are reserved Codes 400-599 and align with http
|
||||
// client and server error codes
|
||||
|
||||
@@ -143,6 +143,14 @@ var errorCodeInfo = map[Code]Info{
|
||||
Message: "error occurred during certification generation",
|
||||
Kind: Encryption,
|
||||
},
|
||||
Sign: {
|
||||
Message: "error occurred during signing",
|
||||
Kind: Encryption,
|
||||
},
|
||||
Verify: {
|
||||
Message: "error occurred during verification",
|
||||
Kind: Encryption,
|
||||
},
|
||||
Internal: {
|
||||
Message: "internal error",
|
||||
Kind: Other,
|
||||
|
||||
Reference in new issue
Block a user