open-vault/vault/rekey.go
ncabatoff 1c98152fa0
Shamir seals now come in two varieties: legacy and new-style. (#7694)
Shamir seals now come in two varieties: legacy and new-style. Legacy
Shamir is automatically converted to new-style when a rekey operation
is performed. All new Vault initializations using Shamir are new-style.

New-style Shamir writes an encrypted master key to storage, just like
AutoUnseal. The stored master key is encrypted using the shared key that
is split via Shamir's algorithm. Thus when unsealing, we take the key
fragments given, combine them into a Key-Encryption-Key, and use that
to decrypt the master key on disk. Then the master key is used to read
the keyring that decrypts the barrier.
2019-10-18 14:46:00 -04:00

1022 lines
36 KiB
Go

package vault
import (
"bytes"
"context"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/shamir"
"github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
)
const (
// coreUnsealKeysBackupPath is the path used to backup encrypted unseal
// keys if specified during a rekey operation. This is outside of the
// barrier.
coreBarrierUnsealKeysBackupPath = "core/unseal-keys-backup"
// coreRecoveryUnsealKeysBackupPath is the path used to backup encrypted
// recovery keys if specified during a rekey operation. This is outside of
// the barrier.
coreRecoveryUnsealKeysBackupPath = "core/recovery-keys-backup"
)
// RekeyResult is used to provide the key parts back after
// they are generated as part of the rekey.
type RekeyResult struct {
SecretShares [][]byte
PGPFingerprints []string
Backup bool
RecoveryKey bool
VerificationRequired bool
VerificationNonce string
}
type RekeyVerifyResult struct {
Complete bool
Nonce string
}
// RekeyBackup stores the backup copy of PGP-encrypted keys
type RekeyBackup struct {
Nonce string
Keys map[string][]string
}
// RekeyThreshold returns the secret threshold for the current seal
// config. This threshold can either be the barrier key threshold or
// the recovery key threshold, depending on whether rekey is being
// performed on the recovery key, or whether the seal supports
// recovery keys.
func (c *Core) RekeyThreshold(ctx context.Context, recovery bool) (int, logical.HTTPCodedError) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return 0, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
var config *SealConfig
var err error
// If we are rekeying the recovery key, or if the seal supports
// recovery keys and we are rekeying the barrier key, we use the
// recovery config as the threshold instead.
if recovery || c.seal.RecoveryKeySupported() {
config, err = c.seal.RecoveryConfig(ctx)
} else {
config, err = c.seal.BarrierConfig(ctx)
}
if err != nil {
return 0, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("unable to look up config: {{err}}", err).Error())
}
if config == nil {
return 0, logical.CodedError(http.StatusBadRequest, ErrNotInit.Error())
}
return config.SecretThreshold, nil
}
// RekeyProgress is used to return the rekey progress (num shares).
func (c *Core) RekeyProgress(recovery, verification bool) (bool, int, logical.HTTPCodedError) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return false, 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return false, 0, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
var conf *SealConfig
if recovery {
conf = c.recoveryRekeyConfig
} else {
conf = c.barrierRekeyConfig
}
if conf == nil {
return false, 0, logical.CodedError(http.StatusBadRequest, "rekey operation not in progress")
}
if verification {
return len(conf.VerificationKey) > 0, len(conf.VerificationProgress), nil
}
return true, len(conf.RekeyProgress), nil
}
// RekeyConfig is used to read the rekey configuration
func (c *Core) RekeyConfig(recovery bool) (*SealConfig, logical.HTTPCodedError) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return nil, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Copy the seal config if any
var conf *SealConfig
if recovery {
if c.recoveryRekeyConfig != nil {
conf = c.recoveryRekeyConfig.Clone()
}
} else {
if c.barrierRekeyConfig != nil {
conf = c.barrierRekeyConfig.Clone()
}
}
return conf, nil
}
// RekeyInit will either initialize the rekey of barrier or recovery key.
// recovery determines whether this is a rekey on the barrier or recovery key.
func (c *Core) RekeyInit(config *SealConfig, recovery bool) logical.HTTPCodedError {
if config.SecretThreshold > config.SecretShares {
return logical.CodedError(http.StatusBadRequest, "provided threshold greater than the total shares")
}
if recovery {
return c.RecoveryRekeyInit(config)
}
return c.BarrierRekeyInit(config)
}
// BarrierRekeyInit is used to initialize the rekey settings for the barrier key
func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
switch c.seal.BarrierType() {
case seal.Shamir:
// As of Vault 1.3 all seals use StoredShares==1. The one exception is
// legacy shamir seals, which we can read but not write (by design).
// So if someone does a rekey, regardless of their intention, we're going
// to migrate them to a non-legacy Shamir seal.
if config.StoredShares != 1 {
c.logger.Warn("shamir stored keys supported, forcing rekey shares/threshold to 1")
config.StoredShares = 1
}
default:
if config.StoredShares != 1 {
c.logger.Warn("stored keys supported, forcing rekey shares/threshold to 1")
config.StoredShares = 1
}
config.SecretShares = 1
config.SecretThreshold = 1
if len(config.PGPKeys) > 0 {
return logical.CodedError(http.StatusBadRequest, "PGP key encryption not supported when using stored keys")
}
if config.Backup {
return logical.CodedError(http.StatusBadRequest, "key backup not supported when using stored keys")
}
}
if c.seal.RecoveryKeySupported() {
if config.VerificationRequired {
return logical.CodedError(http.StatusBadRequest, "requiring verification not supported when rekeying the barrier key with recovery keys")
}
c.logger.Debug("using recovery seal configuration to rekey barrier key")
}
// Check if the seal configuration is valid
if err := config.Validate(); err != nil {
c.logger.Error("invalid rekey seal configuration", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("invalid rekey seal configuration: {{err}}", err).Error())
}
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Prevent multiple concurrent re-keys
if c.barrierRekeyConfig != nil {
return logical.CodedError(http.StatusBadRequest, "rekey already in progress")
}
// Copy the configuration
c.barrierRekeyConfig = config.Clone()
// Initialize the nonce
nonce, err := uuid.GenerateUUID()
if err != nil {
c.barrierRekeyConfig = nil
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error generating nonce for procedure: {{err}}", err).Error())
}
c.barrierRekeyConfig.Nonce = nonce
if c.logger.IsInfo() {
c.logger.Info("rekey initialized", "nonce", c.barrierRekeyConfig.Nonce, "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold, "validation_required", c.barrierRekeyConfig.VerificationRequired)
}
return nil
}
// RecoveryRekeyInit is used to initialize the rekey settings for the recovery key
func (c *Core) RecoveryRekeyInit(config *SealConfig) logical.HTTPCodedError {
if config.StoredShares > 0 {
return logical.CodedError(http.StatusBadRequest, "stored shares not supported by recovery key")
}
// Check if the seal configuration is valid
if err := config.Validate(); err != nil {
c.logger.Error("invalid recovery configuration", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("invalid recovery configuration: {{err}}", err).Error())
}
if !c.seal.RecoveryKeySupported() {
return logical.CodedError(http.StatusBadRequest, "recovery keys not supported")
}
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Prevent multiple concurrent re-keys
if c.recoveryRekeyConfig != nil {
return logical.CodedError(http.StatusBadRequest, "rekey already in progress")
}
// Copy the configuration
c.recoveryRekeyConfig = config.Clone()
// Initialize the nonce
nonce, err := uuid.GenerateUUID()
if err != nil {
c.recoveryRekeyConfig = nil
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error generating nonce for procedure: {{err}}", err).Error())
}
c.recoveryRekeyConfig.Nonce = nonce
if c.logger.IsInfo() {
c.logger.Info("rekey initialized", "nonce", c.recoveryRekeyConfig.Nonce, "shares", c.recoveryRekeyConfig.SecretShares, "threshold", c.recoveryRekeyConfig.SecretThreshold, "validation_required", c.recoveryRekeyConfig.VerificationRequired)
}
return nil
}
// RekeyUpdate is used to provide a new key part for the barrier or recovery key.
func (c *Core) RekeyUpdate(ctx context.Context, key []byte, nonce string, recovery bool) (*RekeyResult, logical.HTTPCodedError) {
if recovery {
return c.RecoveryRekeyUpdate(ctx, key, nonce)
}
return c.BarrierRekeyUpdate(ctx, key, nonce)
}
// BarrierRekeyUpdate is used to provide a new key part. Barrier rekey can be done
// with unseal keys, or recovery keys if that's supported and we are storing the barrier
// key.
//
// N.B.: If recovery keys are used to rekey, the new barrier key shares are not returned.
func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string) (*RekeyResult, logical.HTTPCodedError) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return nil, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is shorter than minimum %d bytes", min))
}
if len(key) > max {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is longer than maximum %d bytes", max))
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Get the seal configuration
var existingConfig *SealConfig
var err error
var useRecovery bool // Determines whether recovery key is being used to rekey the master key
if c.seal.StoredKeysSupported() == StoredKeysSupportedGeneric && c.seal.RecoveryKeySupported() {
existingConfig, err = c.seal.RecoveryConfig(ctx)
useRecovery = true
} else {
existingConfig, err = c.seal.BarrierConfig(ctx)
}
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to fetch existing config: {{err}}", err).Error())
}
// Ensure the barrier is initialized
if existingConfig == nil {
return nil, logical.CodedError(http.StatusBadRequest, ErrNotInit.Error())
}
// Ensure a rekey is in progress
if c.barrierRekeyConfig == nil {
return nil, logical.CodedError(http.StatusBadRequest, "no barrier rekey in progress")
}
if len(c.barrierRekeyConfig.VerificationKey) > 0 {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("rekey operation already finished; verification must be performed; nonce for the verification operation is %q", c.barrierRekeyConfig.VerificationNonce))
}
if nonce != c.barrierRekeyConfig.Nonce {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("incorrect nonce supplied; nonce for this rekey operation is %q", c.barrierRekeyConfig.Nonce))
}
// Check if we already have this piece
for _, existing := range c.barrierRekeyConfig.RekeyProgress {
if subtle.ConstantTimeCompare(existing, key) == 1 {
return nil, logical.CodedError(http.StatusBadRequest, "given key has already been provided during this generation operation")
}
}
// Store this key
c.barrierRekeyConfig.RekeyProgress = append(c.barrierRekeyConfig.RekeyProgress, key)
// Check if we don't have enough keys to unlock
if len(c.barrierRekeyConfig.RekeyProgress) < existingConfig.SecretThreshold {
if c.logger.IsDebug() {
c.logger.Debug("cannot rekey yet, not enough keys", "keys", len(c.barrierRekeyConfig.RekeyProgress), "threshold", existingConfig.SecretThreshold)
}
return nil, nil
}
// Recover the master key or recovery key
var recoveredKey []byte
if existingConfig.SecretThreshold == 1 {
recoveredKey = c.barrierRekeyConfig.RekeyProgress[0]
c.barrierRekeyConfig.RekeyProgress = nil
} else {
recoveredKey, err = shamir.Combine(c.barrierRekeyConfig.RekeyProgress)
c.barrierRekeyConfig.RekeyProgress = nil
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to compute master key: {{err}}", err).Error())
}
}
switch {
case useRecovery:
if err := c.seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
c.logger.Error("rekey recovery key verification failed", "error", err)
return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("recovery key verification failed: {{err}}", err).Error())
}
case c.seal.BarrierType() == seal.Shamir:
if c.seal.StoredKeysSupported() == StoredKeysSupportedShamirMaster {
testseal := NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("testseal")))
testseal.SetCore(c)
err = testseal.GetAccess().(*shamirseal.ShamirSeal).SetKey(recoveredKey)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to setup unseal key: {{err}}", err).Error())
}
cfg, err := c.seal.BarrierConfig(ctx)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to setup test barrier config: {{err}}", err).Error())
}
testseal.SetCachedBarrierConfig(cfg)
stored, err := testseal.GetStoredKeys(ctx)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to read master key: {{err}}", err).Error())
}
recoveredKey = stored[0]
}
if err := c.barrier.VerifyMaster(recoveredKey); err != nil {
c.logger.Error("master key verification failed", "error", err)
return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("master key verification failed: {{err}}", err).Error())
}
}
// Generate a new key: for AutoUnseal, this is a new master key; for Shamir,
// this is a new unseal key, and performBarrierRekey will also generate a
// new master key.
newKey, err := c.barrier.GenerateKey(c.secureRandomReader)
if err != nil {
c.logger.Error("failed to generate master key", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("master key generation failed: {{err}}", err).Error())
}
results := &RekeyResult{
Backup: c.barrierRekeyConfig.Backup,
}
if c.seal.StoredKeysSupported() != StoredKeysSupportedGeneric {
// Set result.SecretShares to the new key itself if only a single key
// part is used -- no Shamir split required.
if c.barrierRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newKey)
} else {
// Split the new key using the Shamir algorithm
shares, err := shamir.Split(newKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Error("failed to generate shares", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate shares: {{err}}", err).Error())
}
results.SecretShares = shares
}
}
// If PGP keys are passed in, encrypt shares with corresponding PGP keys.
if len(c.barrierRekeyConfig.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(results.SecretShares))
for i, _ := range results.SecretShares {
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
}
results.PGPFingerprints, results.SecretShares, err = pgpkeys.EncryptShares(hexEncodedShares, c.barrierRekeyConfig.PGPKeys)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to encrypt shares: {{err}}", err).Error())
}
// If backup is enabled, store backup info in vault.coreBarrierUnsealKeysBackupPath
if c.barrierRekeyConfig.Backup {
backupInfo := map[string][]string{}
for i := 0; i < len(results.PGPFingerprints); i++ {
encShare := bytes.NewBuffer(results.SecretShares[i])
if backupInfo[results.PGPFingerprints[i]] == nil {
backupInfo[results.PGPFingerprints[i]] = []string{hex.EncodeToString(encShare.Bytes())}
} else {
backupInfo[results.PGPFingerprints[i]] = append(backupInfo[results.PGPFingerprints[i]], hex.EncodeToString(encShare.Bytes()))
}
}
backupVals := &RekeyBackup{
Nonce: c.barrierRekeyConfig.Nonce,
Keys: backupInfo,
}
buf, err := json.Marshal(backupVals)
if err != nil {
c.logger.Error("failed to marshal unseal key backup", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to marshal unseal key backup: {{err}}", err).Error())
}
pe := &physical.Entry{
Key: coreBarrierUnsealKeysBackupPath,
Value: buf,
}
if err = c.physical.Put(ctx, pe); err != nil {
c.logger.Error("failed to save unseal key backup", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save unseal key backup: {{err}}", err).Error())
}
}
}
// If we are requiring validation, return now; otherwise rekey the barrier
if c.barrierRekeyConfig.VerificationRequired {
nonce, err := uuid.GenerateUUID()
if err != nil {
c.barrierRekeyConfig = nil
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate verification nonce: {{err}}", err).Error())
}
c.barrierRekeyConfig.VerificationNonce = nonce
c.barrierRekeyConfig.VerificationKey = newKey
results.VerificationRequired = true
results.VerificationNonce = nonce
return results, nil
}
if err := c.performBarrierRekey(ctx, newKey); err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform barrier rekey: {{err}}", err).Error())
}
c.barrierRekeyConfig = nil
return results, nil
}
func (c *Core) performBarrierRekey(ctx context.Context, newSealKey []byte) logical.HTTPCodedError {
legacyUpgrade := c.seal.StoredKeysSupported() == StoredKeysNotSupported
if legacyUpgrade {
// We won't be able to call SetStoredKeys without setting StoredShares=1.
existingConfig, err := c.seal.BarrierConfig(ctx)
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to fetch existing config: {{err}}", err).Error())
}
existingConfig.StoredShares = 1
c.seal.SetCachedBarrierConfig(existingConfig)
}
if c.seal.StoredKeysSupported() != StoredKeysSupportedGeneric {
err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(newSealKey)
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to update barrier seal key: {{err}}", err).Error())
}
}
newMasterKey, err := c.barrier.GenerateKey(c.secureRandomReader)
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform rekey: {{err}}", err).Error())
}
if err := c.seal.SetStoredKeys(ctx, [][]byte{newMasterKey}); err != nil {
c.logger.Error("failed to store keys", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to store keys: {{err}}", err).Error())
}
// Rekey the barrier
if err := c.barrier.Rekey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to rekey barrier", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to rekey barrier: {{err}}", err).Error())
}
if c.logger.IsInfo() {
c.logger.Info("security barrier rekeyed", "stored", c.barrierRekeyConfig.StoredShares, "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold)
}
if len(newSealKey) > 0 {
err := c.barrier.Put(ctx, &logical.StorageEntry{
Key: shamirKekPath,
Value: newSealKey,
})
if err != nil {
c.logger.Error("failed to store new seal key", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to store new seal key: {{err}}", err).Error())
}
}
c.barrierRekeyConfig.VerificationKey = nil
if err := c.seal.SetBarrierConfig(ctx, c.barrierRekeyConfig); err != nil {
c.logger.Error("error saving rekey seal configuration", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err).Error())
}
// Write to the canary path, which will force a synchronous truing during
// replication
if err := c.barrier.Put(ctx, &logical.StorageEntry{
Key: coreKeyringCanaryPath,
Value: []byte(c.barrierRekeyConfig.Nonce),
}); err != nil {
c.logger.Error("error saving keyring canary", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save keyring canary: {{err}}", err).Error())
}
c.barrierRekeyConfig.RekeyProgress = nil
return nil
}
// RecoveryRekeyUpdate is used to provide a new key part
func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string) (*RekeyResult, logical.HTTPCodedError) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return nil, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is shorter than minimum %d bytes", min))
}
if len(key) > max {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is longer than maximum %d bytes", max))
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Get the seal configuration
existingConfig, err := c.seal.RecoveryConfig(ctx)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to fetch existing recovery config: {{err}}", err).Error())
}
// Ensure the seal is initialized
if existingConfig == nil {
return nil, logical.CodedError(http.StatusBadRequest, ErrNotInit.Error())
}
// Ensure a rekey is in progress
if c.recoveryRekeyConfig == nil {
return nil, logical.CodedError(http.StatusBadRequest, "no recovery rekey in progress")
}
if len(c.recoveryRekeyConfig.VerificationKey) > 0 {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("rekey operation already finished; verification must be performed; nonce for the verification operation is %q", c.recoveryRekeyConfig.VerificationNonce))
}
if nonce != c.recoveryRekeyConfig.Nonce {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("incorrect nonce supplied; nonce for this rekey operation is %q", c.recoveryRekeyConfig.Nonce))
}
// Check if we already have this piece
for _, existing := range c.recoveryRekeyConfig.RekeyProgress {
if subtle.ConstantTimeCompare(existing, key) == 1 {
return nil, logical.CodedError(http.StatusBadRequest, "given key has already been provided during this rekey operation")
}
}
// Store this key
c.recoveryRekeyConfig.RekeyProgress = append(c.recoveryRekeyConfig.RekeyProgress, key)
// Check if we don't have enough keys to unlock
if len(c.recoveryRekeyConfig.RekeyProgress) < existingConfig.SecretThreshold {
if c.logger.IsDebug() {
c.logger.Debug("cannot rekey yet, not enough keys", "keys", len(c.recoveryRekeyConfig.RekeyProgress), "threshold", existingConfig.SecretThreshold)
}
return nil, nil
}
// Recover the master key
var recoveryKey []byte
if existingConfig.SecretThreshold == 1 {
recoveryKey = c.recoveryRekeyConfig.RekeyProgress[0]
c.recoveryRekeyConfig.RekeyProgress = nil
} else {
recoveryKey, err = shamir.Combine(c.recoveryRekeyConfig.RekeyProgress)
c.recoveryRekeyConfig.RekeyProgress = nil
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to compute recovery key: {{err}}", err).Error())
}
}
// Verify the recovery key
if err := c.seal.VerifyRecoveryKey(ctx, recoveryKey); err != nil {
c.logger.Error("recovery key verification failed", "error", err)
return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("recovery key verification failed: {{err}}", err).Error())
}
// Generate a new master key
newMasterKey, err := c.barrier.GenerateKey(c.secureRandomReader)
if err != nil {
c.logger.Error("failed to generate recovery key", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("recovery key generation failed: {{err}}", err).Error())
}
// Return the master key if only a single key part is used
results := &RekeyResult{
Backup: c.recoveryRekeyConfig.Backup,
}
if c.recoveryRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newMasterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(newMasterKey, c.recoveryRekeyConfig.SecretShares, c.recoveryRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Error("failed to generate shares", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate shares: {{err}}", err).Error())
}
results.SecretShares = shares
}
if len(c.recoveryRekeyConfig.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(results.SecretShares))
for i, _ := range results.SecretShares {
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
}
results.PGPFingerprints, results.SecretShares, err = pgpkeys.EncryptShares(hexEncodedShares, c.recoveryRekeyConfig.PGPKeys)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to encrypt shares: {{err}}", err).Error())
}
if c.recoveryRekeyConfig.Backup {
backupInfo := map[string][]string{}
for i := 0; i < len(results.PGPFingerprints); i++ {
encShare := bytes.NewBuffer(results.SecretShares[i])
if backupInfo[results.PGPFingerprints[i]] == nil {
backupInfo[results.PGPFingerprints[i]] = []string{hex.EncodeToString(encShare.Bytes())}
} else {
backupInfo[results.PGPFingerprints[i]] = append(backupInfo[results.PGPFingerprints[i]], hex.EncodeToString(encShare.Bytes()))
}
}
backupVals := &RekeyBackup{
Nonce: c.recoveryRekeyConfig.Nonce,
Keys: backupInfo,
}
buf, err := json.Marshal(backupVals)
if err != nil {
c.logger.Error("failed to marshal recovery key backup", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to marshal recovery key backup: {{err}}", err).Error())
}
pe := &physical.Entry{
Key: coreRecoveryUnsealKeysBackupPath,
Value: buf,
}
if err = c.physical.Put(ctx, pe); err != nil {
c.logger.Error("failed to save unseal key backup", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save unseal key backup: {{err}}", err).Error())
}
}
}
// If we are requiring validation, return now; otherwise save the recovery
// key
if c.recoveryRekeyConfig.VerificationRequired {
nonce, err := uuid.GenerateUUID()
if err != nil {
c.recoveryRekeyConfig = nil
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate verification nonce: {{err}}", err).Error())
}
c.recoveryRekeyConfig.VerificationNonce = nonce
c.recoveryRekeyConfig.VerificationKey = newMasterKey
results.VerificationRequired = true
results.VerificationNonce = nonce
return results, nil
}
if err := c.performRecoveryRekey(ctx, newMasterKey); err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform recovery rekey: {{err}}", err).Error())
}
c.recoveryRekeyConfig = nil
return results, nil
}
func (c *Core) performRecoveryRekey(ctx context.Context, newMasterKey []byte) logical.HTTPCodedError {
if err := c.seal.SetRecoveryKey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to set recovery key", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to set recovery key: {{err}}", err).Error())
}
c.recoveryRekeyConfig.VerificationKey = nil
if err := c.seal.SetRecoveryConfig(ctx, c.recoveryRekeyConfig); err != nil {
c.logger.Error("error saving rekey seal configuration", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err).Error())
}
// Write to the canary path, which will force a synchronous truing during
// replication
if err := c.barrier.Put(ctx, &logical.StorageEntry{
Key: coreKeyringCanaryPath,
Value: []byte(c.recoveryRekeyConfig.Nonce),
}); err != nil {
c.logger.Error("error saving keyring canary", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to save keyring canary: {{err}}", err).Error())
}
c.recoveryRekeyConfig.RekeyProgress = nil
return nil
}
func (c *Core) RekeyVerify(ctx context.Context, key []byte, nonce string, recovery bool) (ret *RekeyVerifyResult, retErr logical.HTTPCodedError) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return nil, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is shorter than minimum %d bytes", min))
}
if len(key) > max {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("key is longer than maximum %d bytes", max))
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
config := c.barrierRekeyConfig
if recovery {
config = c.recoveryRekeyConfig
}
// Ensure a rekey is in progress
if config == nil {
return nil, logical.CodedError(http.StatusBadRequest, "no rekey in progress")
}
if len(config.VerificationKey) == 0 {
return nil, logical.CodedError(http.StatusBadRequest, "no rekey verification in progress")
}
if nonce != config.VerificationNonce {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("incorrect nonce supplied; nonce for this verify operation is %q", config.VerificationNonce))
}
// Check if we already have this piece
for _, existing := range config.VerificationProgress {
if subtle.ConstantTimeCompare(existing, key) == 1 {
return nil, logical.CodedError(http.StatusBadRequest, "given key has already been provided during this verify operation")
}
}
// Store this key
config.VerificationProgress = append(config.VerificationProgress, key)
// Check if we don't have enough keys to unlock
if len(config.VerificationProgress) < config.SecretThreshold {
if c.logger.IsDebug() {
c.logger.Debug("cannot verify yet, not enough keys", "keys", len(config.VerificationProgress), "threshold", config.SecretThreshold)
}
return nil, nil
}
// Schedule the progress for forgetting and rotate the nonce if possible
defer func() {
config.VerificationProgress = nil
if ret != nil && ret.Complete {
return
}
// Not complete, so rotate nonce
nonce, err := uuid.GenerateUUID()
if err == nil {
config.VerificationNonce = nonce
if ret != nil {
ret.Nonce = nonce
}
}
}()
// Recover the master key or recovery key
var recoveredKey []byte
if config.SecretThreshold == 1 {
recoveredKey = config.VerificationProgress[0]
} else {
var err error
recoveredKey, err = shamir.Combine(config.VerificationProgress)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to compute key for verification: {{err}}", err).Error())
}
}
if subtle.ConstantTimeCompare(recoveredKey, config.VerificationKey) != 1 {
c.logger.Error("rekey verification failed")
return nil, logical.CodedError(http.StatusBadRequest, "rekey verification failed; incorrect key shares supplied")
}
switch recovery {
case false:
if err := c.performBarrierRekey(ctx, recoveredKey); err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform rekey: {{err}}", err).Error())
}
c.barrierRekeyConfig = nil
default:
if err := c.performRecoveryRekey(ctx, recoveredKey); err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform recovery key rekey: {{err}}", err).Error())
}
c.recoveryRekeyConfig = nil
}
res := &RekeyVerifyResult{
Nonce: config.VerificationNonce,
Complete: true,
}
return res, nil
}
// RekeyCancel is used to cancel an in-progress rekey
func (c *Core) RekeyCancel(recovery bool) logical.HTTPCodedError {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Clear any progress or config
if recovery {
c.recoveryRekeyConfig = nil
} else {
c.barrierRekeyConfig = nil
}
return nil
}
// RekeyVerifyRestart is used to start the verification process over
func (c *Core) RekeyVerifyRestart(recovery bool) logical.HTTPCodedError {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.Sealed() {
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Attempt to generate a new nonce, but don't bail if it doesn't succeed
// (which is extraordinarily unlikely)
nonce, nonceErr := uuid.GenerateUUID()
// Clear any progress or config
if recovery {
c.recoveryRekeyConfig.VerificationProgress = nil
if nonceErr == nil {
c.recoveryRekeyConfig.VerificationNonce = nonce
}
} else {
c.barrierRekeyConfig.VerificationProgress = nil
if nonceErr == nil {
c.barrierRekeyConfig.VerificationNonce = nonce
}
}
return nil
}
// RekeyRetrieveBackup is used to retrieve any backed-up PGP-encrypted unseal
// keys
func (c *Core) RekeyRetrieveBackup(ctx context.Context, recovery bool) (*RekeyBackup, logical.HTTPCodedError) {
if c.Sealed() {
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return nil, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
var entry *physical.Entry
var err error
if recovery {
entry, err = c.physical.Get(ctx, coreRecoveryUnsealKeysBackupPath)
} else {
entry, err = c.physical.Get(ctx, coreBarrierUnsealKeysBackupPath)
}
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error getting keys from backup: {{err}}", err).Error())
}
if entry == nil {
return nil, nil
}
ret := &RekeyBackup{}
err = jsonutil.DecodeJSON(entry.Value, ret)
if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error decoding backup keys: {{err}}", err).Error())
}
return ret, nil
}
// RekeyDeleteBackup is used to delete any backed-up PGP-encrypted unseal keys
func (c *Core) RekeyDeleteBackup(ctx context.Context, recovery bool) logical.HTTPCodedError {
if c.Sealed() {
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
if recovery {
err := c.physical.Delete(ctx, coreRecoveryUnsealKeysBackupPath)
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error deleting backup keys: {{err}}", err).Error())
}
return nil
}
err := c.physical.Delete(ctx, coreBarrierUnsealKeysBackupPath)
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("error deleting backup keys: {{err}}", err).Error())
}
return nil
}