// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package vault import ( "bytes" "encoding/json" "fmt" "time" "github.com/hashicorp/vault/sdk/helper/jsonutil" ) const ( // 10% shy of the NIST recommended maximum, leaving a buffer to account for // tracking losses. absoluteOperationMaximum = int64(3_865_470_566) absoluteOperationMinimum = int64(1_000_000) minimumRotationInterval = 24 * time.Hour ) var ( defaultRotationConfig = KeyRotationConfig{ MaxOperations: absoluteOperationMaximum, } disabledRotationConfig = KeyRotationConfig{ Disabled: true, } ) // Keyring is used to manage multiple encryption keys used by // the barrier. New keys can be installed and each has a sequential term. // The term used to encrypt a key is prefixed to the key written out. // All data is encrypted with the latest key, but storing the old keys // allows for decryption of keys written previously. Along with the encryption // keys, the keyring also tracks the root key. This is necessary so that // when a new key is added to the keyring, we can encrypt with the root key // and write out the new keyring. type Keyring struct { rootKey []byte keys map[uint32]*Key activeTerm uint32 rotationConfig KeyRotationConfig } // EncodedKeyring is used for serialization of the keyring type EncodedKeyring struct { MasterKey []byte Keys []*Key RotationConfig KeyRotationConfig } // Key represents a single term, along with the key used. type Key struct { Term uint32 Version int Value []byte InstallTime time.Time Encryptions uint64 `json:"encryptions,omitempty"` } type KeyRotationConfig struct { Disabled bool MaxOperations int64 Interval time.Duration } // Serialize is used to create a byte encoded key func (k *Key) Serialize() ([]byte, error) { return json.Marshal(k) } // DeserializeKey is used to deserialize and return a new key func DeserializeKey(buf []byte) (*Key, error) { k := new(Key) if err := jsonutil.DecodeJSON(buf, k); err != nil { return nil, fmt.Errorf("deserialization failed: %w", err) } return k, nil } // NewKeyring creates a new keyring func NewKeyring() *Keyring { k := &Keyring{ keys: make(map[uint32]*Key), activeTerm: 0, rotationConfig: defaultRotationConfig, } return k } // Clone returns a new copy of the keyring func (k *Keyring) Clone() *Keyring { clone := &Keyring{ rootKey: k.rootKey, keys: make(map[uint32]*Key, len(k.keys)), activeTerm: k.activeTerm, rotationConfig: k.rotationConfig, } for idx, key := range k.keys { clone.keys[idx] = key } return clone } // AddKey adds a new key to the keyring func (k *Keyring) AddKey(key *Key) (*Keyring, error) { // Ensure there is no conflict if exist, ok := k.keys[key.Term]; ok { if !bytes.Equal(key.Value, exist.Value) { return nil, fmt.Errorf("conflicting key for term %d already installed", key.Term) } return k, nil } // Add a time if none if key.InstallTime.IsZero() { key.InstallTime = time.Now() } // Make a new keyring clone := k.Clone() // Install the new key clone.keys[key.Term] = key // Update the active term if newer if key.Term > clone.activeTerm { clone.activeTerm = key.Term } // Zero out encryption estimates for previous terms for term, key := range clone.keys { if term != clone.activeTerm { key.Encryptions = 0 } } return clone, nil } // RemoveKey removes a key from the keyring func (k *Keyring) RemoveKey(term uint32) (*Keyring, error) { // Ensure this is not the active key if term == k.activeTerm { return nil, fmt.Errorf("cannot remove active key") } // Check if this term does not exist if _, ok := k.keys[term]; !ok { return k, nil } // Delete the key clone := k.Clone() delete(clone.keys, term) return clone, nil } // ActiveTerm returns the currently active term func (k *Keyring) ActiveTerm() uint32 { return k.activeTerm } // ActiveKey returns the active encryption key, or nil func (k *Keyring) ActiveKey() *Key { return k.keys[k.activeTerm] } // TermKey returns the key for the given term, or nil func (k *Keyring) TermKey(term uint32) *Key { return k.keys[term] } // SetRootKey is used to update the root key func (k *Keyring) SetRootKey(val []byte) *Keyring { valCopy := make([]byte, len(val)) copy(valCopy, val) clone := k.Clone() clone.rootKey = valCopy return clone } // RootKey returns the root key func (k *Keyring) RootKey() []byte { return k.rootKey } // Serialize is used to create a byte encoded keyring func (k *Keyring) Serialize() ([]byte, error) { // Create the encoded entry enc := EncodedKeyring{ MasterKey: k.rootKey, RotationConfig: k.rotationConfig, } for _, key := range k.keys { enc.Keys = append(enc.Keys, key) } // JSON encode the keyring buf, err := json.Marshal(enc) return buf, err } // DeserializeKeyring is used to deserialize and return a new keyring func DeserializeKeyring(buf []byte) (*Keyring, error) { // Deserialize the keyring var enc EncodedKeyring if err := jsonutil.DecodeJSON(buf, &enc); err != nil { return nil, fmt.Errorf("deserialization failed: %w", err) } // Create a new keyring k := NewKeyring() k.rootKey = enc.MasterKey k.rotationConfig = enc.RotationConfig k.rotationConfig.Sanitize() for _, key := range enc.Keys { k.keys[key.Term] = key if key.Term > k.activeTerm { k.activeTerm = key.Term } } return k, nil } // N.B.: // Since Go 1.5 these are not reliable; see the documentation around the memzero // function. These are best-effort. func (k *Keyring) Zeroize(keysToo bool) { if k == nil { return } if k.rootKey != nil { memzero(k.rootKey) } if !keysToo || k.keys == nil { return } for _, key := range k.keys { memzero(key.Value) } } func (c KeyRotationConfig) Clone() KeyRotationConfig { clone := KeyRotationConfig{ MaxOperations: c.MaxOperations, Interval: c.Interval, Disabled: c.Disabled, } clone.Sanitize() return clone } func (c *KeyRotationConfig) Sanitize() { if c.MaxOperations == 0 || c.MaxOperations > absoluteOperationMaximum { c.MaxOperations = absoluteOperationMaximum } if c.MaxOperations < absoluteOperationMinimum { c.MaxOperations = absoluteOperationMinimum } if c.Interval > 0 && c.Interval < minimumRotationInterval { c.Interval = minimumRotationInterval } } func (c *KeyRotationConfig) Equals(config KeyRotationConfig) bool { return c.MaxOperations == config.MaxOperations && c.Interval == config.Interval }