2020-08-19 16:47:18 -04:00
package kms
import (
"context"
2021-04-08 20:54:32 -04:00
"crypto/sha256"
2020-08-19 16:47:18 -04:00
"fmt"
2021-04-08 20:54:32 -04:00
"io"
2020-08-19 16:47:18 -04:00
"sync"
2021-04-02 18:19:48 -04:00
"github.com/hashicorp/boundary/internal/db"
2021-01-12 08:50:19 -08:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-19 16:47:18 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/hashicorp/go-kms-wrapping/wrappers/multiwrapper"
2021-04-08 20:54:32 -04:00
"golang.org/x/crypto/hkdf"
2020-08-19 16:47:18 -04:00
)
// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its
// configuration file.
type ExternalWrappers struct {
m sync . RWMutex
root wrapping . Wrapper
workerAuth wrapping . Wrapper
2020-08-20 13:34:03 -04:00
recovery wrapping . Wrapper
2020-08-19 16:47:18 -04:00
}
// Root returns the wrapper for root keys
func ( e * ExternalWrappers ) Root () wrapping . Wrapper {
e . m . RLock ()
defer e . m . RUnlock ()
return e . root
}
// WorkerAuth returns the wrapper for worker authentication
func ( e * ExternalWrappers ) WorkerAuth () wrapping . Wrapper {
e . m . RLock ()
defer e . m . RUnlock ()
return e . workerAuth
}
2020-08-20 13:34:03 -04:00
// Recovery returns the wrapper for recovery operations
func ( e * ExternalWrappers ) Recovery () wrapping . Wrapper {
e . m . RLock ()
defer e . m . RUnlock ()
return e . recovery
}
2020-08-19 16:47:18 -04:00
// Kms is a way to access wrappers for a given scope and purpose. Since keys can
// never change, only be added or (eventually) removed, it opportunistically
// caches, going to the database as needed.
type Kms struct {
// scopePurposeCache holds a per-scope-purpose multiwrapper containing the
// current encrypting key and all previous key versions, for decryption
scopePurposeCache sync . Map
externalScopeCache map [ string ] * ExternalWrappers
externalScopeCacheMutex sync . RWMutex
2021-04-08 20:54:32 -04:00
derivedPurposeCache sync . Map
2020-08-19 16:47:18 -04:00
repo * Repository
}
2021-08-01 14:41:13 -04:00
// NewKms takes in a repo and returns a Kms.
2020-08-19 16:47:18 -04:00
func NewKms ( repo * Repository , opt ... Option ) ( * Kms , error ) {
2021-01-12 08:50:19 -08:00
const op = "kms.NewKms"
2020-08-19 16:47:18 -04:00
if repo == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "missing underlying repo" )
2020-08-19 16:47:18 -04:00
}
return & Kms {
externalScopeCache : make ( map [ string ] * ExternalWrappers ),
repo : repo ,
}, nil
}
// GetScopePurposeCache is used in test functions for validation. Since the
// tests need to be in a different package to avoid circular dependencies, this
// is exported.
func ( k * Kms ) GetScopePurposeCache () * sync . Map {
return & k . scopePurposeCache
}
2021-04-08 20:54:32 -04:00
func ( k * Kms ) GetDerivedPurposeCache () * sync . Map {
return & k . derivedPurposeCache
}
2020-08-19 16:47:18 -04:00
// AddExternalWrappers allows setting the external keys.
//
// TODO: If we support more than one, e.g. for encrypting against many in case
// of a key loss, there will need to be some refactoring here to have the values
// being stored in the struct be a multiwrapper, but that's for a later project.
func ( k * Kms ) AddExternalWrappers ( opt ... Option ) error {
2021-01-12 08:50:19 -08:00
const op = "kms.AddExternalWrappers"
2020-08-19 16:47:18 -04:00
k . externalScopeCacheMutex . Lock ()
defer k . externalScopeCacheMutex . Unlock ()
ext := k . externalScopeCache [ scope . Global . String ()]
if ext == nil {
ext = & ExternalWrappers {}
}
ext . m . Lock ()
defer ext . m . Unlock ()
opts := getOpts ( opt ... )
if opts . withRootWrapper != nil {
ext . root = opts . withRootWrapper
if ext . root . KeyID () == "" {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "root wrapper has no key ID" )
2020-08-19 16:47:18 -04:00
}
}
if opts . withWorkerAuthWrapper != nil {
ext . workerAuth = opts . withWorkerAuthWrapper
if ext . workerAuth . KeyID () == "" {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "worker auth wrapper has no key ID" )
2020-08-19 16:47:18 -04:00
}
}
2020-08-20 13:34:03 -04:00
if opts . withRecoveryWrapper != nil {
ext . recovery = opts . withRecoveryWrapper
if ext . recovery . KeyID () == "" {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "recovery wrapper has no key ID" )
2020-08-20 13:34:03 -04:00
}
}
2020-08-19 16:47:18 -04:00
k . externalScopeCache [ scope . Global . String ()] = ext
return nil
}
func ( k * Kms ) GetExternalWrappers () * ExternalWrappers {
k . externalScopeCacheMutex . RLock ()
defer k . externalScopeCacheMutex . RUnlock ()
ext := k . externalScopeCache [ scope . Global . String ()]
ext . m . RLock ()
defer ext . m . RUnlock ()
ret := & ExternalWrappers {
root : ext . root ,
workerAuth : ext . workerAuth ,
2020-08-20 13:34:03 -04:00
recovery : ext . recovery ,
2020-08-19 16:47:18 -04:00
}
return ret
}
// GetWrapper returns a wrapper for the given scope and purpose. When a keyId is
// passed, it will ensure that the returning wrapper has that key ID in the
// multiwrapper. This is not necesary for encryption but should be supplied for
// decryption.
func ( k * Kms ) GetWrapper ( ctx context . Context , scopeId string , purpose KeyPurpose , opt ... Option ) ( wrapping . Wrapper , error ) {
2021-01-12 08:50:19 -08:00
const op = "kms.GetWrapper"
2020-09-03 13:23:46 -04:00
if scopeId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing scope id" )
2020-09-03 13:23:46 -04:00
}
2020-08-19 16:47:18 -04:00
switch purpose {
2021-04-08 20:54:32 -04:00
case KeyPurposeOplog , KeyPurposeDatabase , KeyPurposeTokens , KeyPurposeSessions , KeyPurposeOidc :
2020-08-19 16:47:18 -04:00
case KeyPurposeUnknown :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing key purpose" )
2020-08-19 16:47:18 -04:00
default :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "unsupported purpose %q" , purpose ))
2020-08-19 16:47:18 -04:00
}
2020-09-03 13:23:46 -04:00
2020-08-19 16:47:18 -04:00
opts := getOpts ( opt ... )
// Fast-path: we have a valid key at the scope/purpose. Verify the key with
// that ID is in the multiwrapper; if not, fall through to reload from the
// DB.
val , ok := k . scopePurposeCache . Load ( scopeId + purpose . String ())
if ok {
wrapper := val .( * multiwrapper . MultiWrapper )
2021-03-03 11:23:00 -05:00
if opts . withKeyId == "" {
2020-08-19 16:47:18 -04:00
return wrapper , nil
}
2021-03-03 11:23:00 -05:00
if keyIdWrapper := wrapper . WrapperForKeyID ( opts . withKeyId ); keyIdWrapper != nil {
return keyIdWrapper , nil
}
2020-08-19 16:47:18 -04:00
// Fall through to refresh our multiwrapper for this scope/purpose from the DB
}
// We don't have it cached, so we'll need to read from the database. Get the
// root for the scope as we'll need it to decrypt the value coming from the
// DB. We don't cache the roots as we expect that after a few calls the
// scope-purpose cache will catch everything in steady-state.
2020-09-03 13:23:46 -04:00
rootWrapper , rootKeyId , err := k . loadRoot ( ctx , scopeId , opt ... )
2020-08-19 16:47:18 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error loading root key for scope %s" , scopeId )))
2020-08-19 16:47:18 -04:00
}
2020-09-03 13:23:46 -04:00
if rootWrapper == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "got nil root wrapper for scope %s" , scopeId ))
2020-09-03 13:23:46 -04:00
}
2020-08-19 16:47:18 -04:00
2020-09-03 13:23:46 -04:00
wrapper , err := k . loadDek ( ctx , scopeId , purpose , rootWrapper , rootKeyId , opt ... )
2020-08-19 16:47:18 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error loading %s for scope %s" , purpose . String (), scopeId )))
2020-08-19 16:47:18 -04:00
}
2020-09-03 13:23:46 -04:00
k . scopePurposeCache . Store ( scopeId + purpose . String (), wrapper )
2020-08-19 16:47:18 -04:00
2021-03-03 11:23:00 -05:00
if opts . withKeyId != "" {
if keyIdWrapper := wrapper . WrapperForKeyID ( opts . withKeyId ); keyIdWrapper != nil {
return keyIdWrapper , nil
}
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . KeyNotFound , op , "unable to find specified key ID" )
2021-03-03 11:23:00 -05:00
}
2020-09-03 13:23:46 -04:00
return wrapper , nil
2020-08-19 16:47:18 -04:00
}
2020-09-03 13:23:46 -04:00
func ( k * Kms ) loadRoot ( ctx context . Context , scopeId string , opt ... Option ) ( * multiwrapper . MultiWrapper , string , error ) {
2021-01-12 08:50:19 -08:00
const op = "kms.loadRoot"
2020-08-19 16:47:18 -04:00
opts := getOpts ( opt ... )
repo := opts . withRepository
if repo == nil {
repo = k . repo
}
rootKeys , err := repo . ListRootKeys ( ctx )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . Wrap ( ctx , err , op )
2020-08-19 16:47:18 -04:00
}
var rootKeyId string
for _ , k := range rootKeys {
if k . GetScopeId () == scopeId {
rootKeyId = k . GetPrivateId ()
break
}
}
if rootKeyId == "" {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . New ( ctx , errors . KeyNotFound , op , fmt . Sprintf ( "missing root key for scope %s" , scopeId ))
2020-08-19 16:47:18 -04:00
}
// Now: find the external KMS that can be used to decrypt the root values
// from the DB.
k . externalScopeCacheMutex . Lock ()
externalWrappers := k . externalScopeCache [ scope . Global . String ()]
k . externalScopeCacheMutex . Unlock ()
if externalWrappers == nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . New ( ctx , errors . KeyNotFound , op , "could not find kms information at either the needed scope or global fallback" )
2020-08-19 16:47:18 -04:00
}
externalWrappers . m . RLock ()
defer externalWrappers . m . RUnlock ()
if externalWrappers . root == nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "root key wrapper for scope %s is nil" , scopeId ))
2020-08-19 16:47:18 -04:00
}
2021-04-02 18:19:48 -04:00
rootKeyVersions , err := repo . ListRootKeyVersions ( ctx , externalWrappers . root , rootKeyId , WithOrderByVersion ( db . DescendingOrderBy ))
2020-08-19 16:47:18 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error looking up root key versions for scope %s with key ID %s" , scopeId , externalWrappers . root . KeyID ())))
2020-08-19 16:47:18 -04:00
}
if len ( rootKeyVersions ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . New ( ctx , errors . KeyNotFound , op , fmt . Sprintf ( "no root key versions found for scope %s" , scopeId ))
2020-08-19 16:47:18 -04:00
}
var multi * multiwrapper . MultiWrapper
for i , key := range rootKeyVersions {
wrapper := aead . NewWrapper ( nil )
if _ , err := wrapper . SetConfig ( map [ string ] string {
"key_id" : key . GetPrivateId (),
}); err != nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error setting config on aead root wrapper in scope %s" , scopeId )))
2020-08-19 16:47:18 -04:00
}
if err := wrapper . SetAESGCMKeyBytes ( key . GetKey ()); err != nil {
2021-07-29 18:52:56 -06:00
return nil , "" , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error setting key bytes on aead root wrapper in scope %s" , scopeId )))
2020-09-03 13:23:46 -04:00
}
if i == 0 {
multi = multiwrapper . NewMultiWrapper ( wrapper )
} else {
multi . AddWrapper ( wrapper )
}
}
2021-01-12 08:50:19 -08:00
return multi , rootKeyId , nil
2020-09-03 13:23:46 -04:00
}
// Dek is an interface wrapping dek types to allow a lot less switching in loadDek
type Dek interface {
GetRootKeyId () string
GetPrivateId () string
}
// DekVersion is an interface wrapping versioned dek types to allow a lot less switching in loadDek
type DekVersion interface {
GetPrivateId () string
GetKey () [] byte
}
func ( k * Kms ) loadDek ( ctx context . Context , scopeId string , purpose KeyPurpose , rootWrapper wrapping . Wrapper , rootKeyId string , opt ... Option ) ( * multiwrapper . MultiWrapper , error ) {
2021-01-12 08:50:19 -08:00
const op = "kms.loadDek"
2020-09-03 13:23:46 -04:00
if rootWrapper == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "nil root wrapper for scope %s" , scopeId ))
2020-09-03 13:23:46 -04:00
}
if rootKeyId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "missing root key ID for scope %s" , scopeId ))
2020-09-03 13:23:46 -04:00
}
opts := getOpts ( opt ... )
repo := opts . withRepository
if repo == nil {
repo = k . repo
}
var keys [] Dek
var err error
switch purpose {
case KeyPurposeDatabase :
keys , err = repo . ListDatabaseKeys ( ctx )
case KeyPurposeOplog :
keys , err = repo . ListOplogKeys ( ctx )
case KeyPurposeTokens :
keys , err = repo . ListTokenKeys ( ctx )
case KeyPurposeSessions :
keys , err = repo . ListSessionKeys ( ctx )
2021-04-08 20:54:32 -04:00
case KeyPurposeOidc :
keys , err = repo . ListOidcKeys ( ctx )
2021-03-03 11:23:00 -05:00
default :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "unknown or invalid DEK purpose specified" )
2020-09-03 13:23:46 -04:00
}
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error listing root keys" ))
2020-09-03 13:23:46 -04:00
}
var keyId string
for _ , k := range keys {
if k . GetRootKeyId () == rootKeyId {
keyId = k . GetPrivateId ()
break
}
}
if keyId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . KeyNotFound , op , fmt . Sprintf ( "error finding %s key for scope %s" , purpose . String (), scopeId ))
2020-09-03 13:23:46 -04:00
}
var keyVersions [] DekVersion
switch purpose {
case KeyPurposeDatabase :
2021-04-02 18:19:48 -04:00
keyVersions , err = repo . ListDatabaseKeyVersions ( ctx , rootWrapper , keyId , WithOrderByVersion ( db . DescendingOrderBy ))
2020-09-03 13:23:46 -04:00
case KeyPurposeOplog :
2021-04-02 18:19:48 -04:00
keyVersions , err = repo . ListOplogKeyVersions ( ctx , rootWrapper , keyId , WithOrderByVersion ( db . DescendingOrderBy ))
2020-09-03 13:23:46 -04:00
case KeyPurposeTokens :
2021-04-02 18:19:48 -04:00
keyVersions , err = repo . ListTokenKeyVersions ( ctx , rootWrapper , keyId , WithOrderByVersion ( db . DescendingOrderBy ))
2020-09-03 13:23:46 -04:00
case KeyPurposeSessions :
2021-04-02 18:19:48 -04:00
keyVersions , err = repo . ListSessionKeyVersions ( ctx , rootWrapper , keyId , WithOrderByVersion ( db . DescendingOrderBy ))
2021-04-08 20:54:32 -04:00
case KeyPurposeOidc :
keyVersions , err = repo . ListOidcKeyVersions ( ctx , rootWrapper , keyId , WithOrderByVersion ( db . DescendingOrderBy ))
2021-03-03 12:22:40 -05:00
default :
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "unknown or invalid DEK purpose specified" )
2020-09-03 13:23:46 -04:00
}
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error looking up %s key versions for scope %s with key ID %s" , purpose . String (), scopeId , rootWrapper . KeyID ())))
2020-09-03 13:23:46 -04:00
}
if len ( keyVersions ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . KeyNotFound , op , fmt . Sprintf ( "no %s key versions found for scope %s" , purpose . String (), scopeId ))
2020-09-03 13:23:46 -04:00
}
var multi * multiwrapper . MultiWrapper
for i , keyVersion := range keyVersions {
wrapper := aead . NewWrapper ( nil )
if _ , err := wrapper . SetConfig ( map [ string ] string {
"key_id" : keyVersion . GetPrivateId (),
}); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error setting config on aead %s wrapper in scope %s" , purpose . String (), scopeId )))
2020-09-03 13:23:46 -04:00
}
if err := wrapper . SetAESGCMKeyBytes ( keyVersion . GetKey ()); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error setting key bytes on aead %s wrapper in scope %s" , purpose . String (), scopeId )))
2020-08-19 16:47:18 -04:00
}
if i == 0 {
multi = multiwrapper . NewMultiWrapper ( wrapper )
} else {
multi . AddWrapper ( wrapper )
}
}
2021-01-12 08:50:19 -08:00
return multi , nil
2020-08-19 16:47:18 -04:00
}
2021-04-08 20:54:32 -04:00
// DerivedReader returns a reader from which keys can be read, using the
// given wrapper, reader length limit, salt and context info. Salt and info can
// be nil.
//
// Example:
// reader, _ := NewDerivedReader(wrapper, userId, jobId)
// key := ed25519.GenerateKey(reader)
func NewDerivedReader ( wrapper wrapping . Wrapper , lenLimit int64 , salt , info [] byte ) ( * io . LimitedReader , error ) {
const op = "kms.NewDerivedReader"
if wrapper == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "missing wrapper" )
2021-04-08 20:54:32 -04:00
}
if lenLimit < 20 {
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "lenLimit must be >= 20" )
2021-04-08 20:54:32 -04:00
}
var aeadWrapper * aead . Wrapper
switch w := wrapper .( type ) {
case * multiwrapper . MultiWrapper :
raw := w . WrapperForKeyID ( "__base__" )
var ok bool
if aeadWrapper , ok = raw .( * aead . Wrapper ); ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "unexpected wrapper type from multiwrapper base" )
2021-04-08 20:54:32 -04:00
}
case * aead . Wrapper :
if w . GetKeyBytes () == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "aead wrapper missing bytes" )
2021-04-08 20:54:32 -04:00
}
aeadWrapper = w
default :
2021-07-29 18:52:56 -06:00
return nil , errors . NewDeprecated ( errors . InvalidParameter , op , "unknown wrapper type" )
2021-04-08 20:54:32 -04:00
}
reader := hkdf . New ( sha256 . New , aeadWrapper . GetKeyBytes (), salt , info )
return & io . LimitedReader {
R : reader ,
N : lenLimit ,
}, nil
}