This commit is contained in:
Jeff Mitchell 2018-05-17 21:17:52 -04:00
parent cec2123a98
commit a9d8be3c4d
5 changed files with 457 additions and 59 deletions

View File

@ -80,8 +80,10 @@ func Handler(core *vault.Core) http.Handler {
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy)))
mux.Handle("/v1/sys/rekey/init", handleRequestForwarding(core, handleSysRekeyInit(core, false)))
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
mux.Handle("/v1/sys/rekey/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, false)))
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
mux.Handle("/v1/sys/rekey-recovery-key/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, true)))
mux.Handle("/v1/sys/wrapping/lookup", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/rewrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/unwrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))

View File

@ -90,6 +90,7 @@ func handleSysRekeyInitGet(ctx context.Context, core *vault.Core, recovery bool,
status.Started = true
status.T = rekeyConf.SecretThreshold
status.N = rekeyConf.SecretShares
status.VerificationRequired = rekeyConf.VerificationRequired
if rekeyConf.PGPKeys != nil && len(rekeyConf.PGPKeys) != 0 {
pgpFingerprints, err := pgpkeys.GetFingerprints(rekeyConf.PGPKeys, nil)
if err != nil {
@ -142,6 +143,7 @@ func handleSysRekeyInitPut(ctx context.Context, core *vault.Core, recovery bool,
StoredShares: req.StoredShares,
PGPKeys: req.PGPKeys,
Backup: req.Backup,
VerificationRequired: req.RequireVerification,
}, recovery)
if err != nil {
respondError(w, http.StatusBadRequest, err)
@ -214,6 +216,8 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler {
resp.Nonce = req.Nonce
resp.Backup = result.Backup
resp.PGPFingerprints = result.PGPFingerprints
resp.VerificationRequired = result.VerificationRequired
resp.VerificationNonce = result.VerificationNonce
// Encode the keys
keys := make([]string, 0, len(result.SecretShares))
@ -231,12 +235,149 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler {
})
}
func handleSysRekeyVerify(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
standby, _ := core.Standby()
if standby {
respondStandby(core, w, r.URL)
return
}
repState := core.ReplicationState()
if repState.HasState(consts.ReplicationPerformanceSecondary) {
respondError(w, http.StatusBadRequest,
fmt.Errorf("rekeying can only be performed on the primary cluster when replication is activated"))
return
}
ctx, cancel := core.GetContext()
defer cancel()
switch {
case recovery && !core.SealAccess().RecoveryKeySupported():
respondError(w, http.StatusBadRequest, fmt.Errorf("recovery rekeying not supported"))
case r.Method == "GET":
handleSysRekeyVerifyGet(ctx, core, recovery, w, r)
case r.Method == "POST" || r.Method == "PUT":
handleSysRekeyVerifyPut(ctx, core, recovery, w, r)
case r.Method == "DELETE":
handleSysRekeyVerifyDelete(core, recovery, w, r)
default:
respondError(w, http.StatusMethodNotAllowed, nil)
}
})
}
func handleSysRekeyVerifyGet(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
barrierConfig, err := core.SealAccess().BarrierConfig(ctx)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if barrierConfig == nil {
respondError(w, http.StatusBadRequest, fmt.Errorf("server is not yet initialized"))
return
}
// Get the rekey configuration
rekeyConf, err := core.RekeyConfig(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if rekeyConf == nil {
respondError(w, http.StatusBadRequest, errors.New("no rekey configuration found"))
return
}
// Get the progress
progress, err := core.RekeyVerifyProgress(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
// Format the status
status := &RekeyVerificationStatusResponse{
Nonce: rekeyConf.Nonce,
T: rekeyConf.SecretThreshold,
N: rekeyConf.SecretShares,
Progress: progress,
}
respondOk(w, status)
}
func handleSysRekeyVerifyDelete(core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
err := core.RekeyVerifyRestart(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
ctx, cancel := core.GetContext()
defer cancel()
handleSysRekeyVerifyGet(ctx, core, recovery, w, r)
}
func handleSysRekeyVerifyPut(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
// Parse the request
var req RekeyVerificationUpdateRequest
if err := parseRequest(r, w, &req); err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
if req.Key == "" {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be specified in request body as JSON"))
return
}
// Decode the key, which is base64 or hex encoded
min, max := core.BarrierKeyLength()
key, err := hex.DecodeString(req.Key)
// We check min and max here to ensure that a string that is base64
// encoded but also valid hex will not be valid and we instead base64
// decode it
if err != nil || len(key) < min || len(key) > max {
key, err = base64.StdEncoding.DecodeString(req.Key)
if err != nil {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be a valid hex or base64 string"))
return
}
}
ctx, cancel := core.GetContext()
defer cancel()
// Use the key to make progress on rekey
result, err := core.RekeyVerify(ctx, key, recovery)
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
// Format the response
resp := &RekeyVerificationUpdateResponse{}
if result != nil {
resp.Complete = true
resp.Nonce = result.Nonce
respondOk(w, resp)
} else {
handleSysRekeyVerifyGet(ctx, core, recovery, w, r)
}
}
type RekeyRequest struct {
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
StoredShares int `json:"stored_shares"`
PGPKeys []string `json:"pgp_keys"`
Backup bool `json:"backup"`
RequireVerification bool `json:"require_verification"`
}
type RekeyStatusResponse struct {
@ -248,6 +389,7 @@ type RekeyStatusResponse struct {
Required int `json:"required"`
PGPFingerprints []string `json:"pgp_fingerprints"`
Backup bool `json:"backup"`
VerificationRequired bool `json:"verification_required"`
}
type RekeyUpdateRequest struct {
@ -262,4 +404,22 @@ type RekeyUpdateResponse struct {
KeysB64 []string `json:"keys_base64"`
PGPFingerprints []string `json:"pgp_fingerprints"`
Backup bool `json:"backup"`
VerificationRequired bool `json:"verification_required"`
VerificationNonce string `json:"verification_nonce"`
}
type RekeyVerificationUpdateRequest struct {
Key string
}
type RekeyVerificationStatusResponse struct {
Nonce string `json:"nonce"`
T int `json:"t"`
N int `json:"n"`
Progress int `json:"progress"`
}
type RekeyVerificationUpdateResponse struct {
Nonce string `json:"nonce"`
Complete bool `json:"complete"`
}

View File

@ -212,8 +212,10 @@ type Core struct {
// used; this isn't time-critical so this shouldn't be a problem.
barrierRekeyConfig *SealConfig
barrierRekeyProgress [][]byte
barrierRekeyVerifyProgress [][]byte
recoveryRekeyConfig *SealConfig
recoveryRekeyProgress [][]byte
recoveryRekeyVerifyProgress [][]byte
rekeyLock sync.RWMutex
// mounts is loaded after unseal since it is a protected
@ -1762,8 +1764,10 @@ func (c *Core) preSeal() error {
// Clear any rekey progress
c.barrierRekeyConfig = nil
c.barrierRekeyProgress = nil
c.barrierRekeyVerifyProgress = nil
c.recoveryRekeyConfig = nil
c.recoveryRekeyProgress = nil
c.recoveryRekeyVerifyProgress = nil
if c.metricsCh != nil {
close(c.metricsCh)

View File

@ -3,8 +3,10 @@ package vault
import (
"bytes"
"context"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/hashicorp/errwrap"
@ -35,6 +37,12 @@ type RekeyResult struct {
PGPFingerprints []string
Backup bool
RecoveryKey bool
VerificationRequired bool
VerificationNonce string
}
type RekeyVerifyResult struct {
Nonce string
}
// RekeyBackup stores the backup copy of PGP-encrypted keys
@ -98,6 +106,27 @@ func (c *Core) RekeyProgress(recovery bool) (int, error) {
return len(c.barrierRekeyProgress), nil
}
// RekeyVerifyProgress is used to return the rekey progress (num shares) during
// verification.
func (c *Core) RekeyVerifyProgress(recovery bool) (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, consts.ErrSealed
}
if c.standby {
return 0, consts.ErrStandby
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
if recovery {
return len(c.recoveryRekeyVerifyProgress), nil
}
return len(c.barrierRekeyVerifyProgress), nil
}
// RekeyConfig is used to read the rekey configuration
func (c *Core) RekeyConfig(recovery bool) (*SealConfig, error) {
c.stateLock.RLock()
@ -152,6 +181,9 @@ func (c *Core) BarrierRekeyInit(config *SealConfig) error {
if c.seal.RecoveryKeySupported() && c.seal.RecoveryType() == config.Type {
c.logger.Debug("using recovery seal configuration to rekey barrier key")
if config.VerificationRequired {
return fmt.Errorf("requiring verification not supported when rekeying the barrier key with recovery keys")
}
}
// Check if the seal configuration is valid
@ -189,7 +221,7 @@ func (c *Core) BarrierRekeyInit(config *SealConfig) 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)
c.logger.Info("rekey initialized", "nonce", c.barrierRekeyConfig.Nonce, "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold, "validation_required", c.barrierRekeyConfig.VerificationRequired)
}
return nil
}
@ -239,7 +271,7 @@ func (c *Core) RecoveryRekeyInit(config *SealConfig) 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)
c.logger.Info("rekey initialized", "nonce", c.recoveryRekeyConfig.Nonce, "shares", c.recoveryRekeyConfig.SecretShares, "threshold", c.recoveryRekeyConfig.SecretThreshold, "validation_required", c.recoveryRekeyConfig.VerificationRequired)
}
return nil
}
@ -327,14 +359,17 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
return nil, nil
}
// Schedule the rekey progress for forgetting
defer func() {
c.barrierRekeyProgress = nil
}()
// Recover the master key or recovery key
var recoveredKey []byte
if existingConfig.SecretThreshold == 1 {
recoveredKey = c.barrierRekeyProgress[0]
c.barrierRekeyProgress = nil
} else {
recoveredKey, err = shamir.Combine(c.barrierRekeyProgress)
c.barrierRekeyProgress = nil
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key: {{err}}", err)
}
@ -436,17 +471,44 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
}
}
// 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, err
}
c.barrierRekeyConfig.VerificationNonce = nonce
c.barrierRekeyConfig.VerificationKey = newMasterKey
results.VerificationRequired = true
results.VerificationNonce = nonce
return results, nil
}
if err := c.performBarrierRekey(ctx, newMasterKey); err != nil {
return nil, err
}
c.barrierRekeyConfig = nil
return results, nil
}
func (c *Core) performBarrierRekey(ctx context.Context, newMasterKey []byte) error {
// Rekey the barrier
if err := c.barrier.Rekey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to rekey barrier", "error", err)
return nil, errwrap.Wrapf("failed to rekey barrier: {{err}}", err)
return errwrap.Wrapf("failed to rekey barrier: {{err}}", err)
}
if c.logger.IsInfo() {
c.logger.Info("security barrier rekeyed", "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold)
}
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 nil, errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err)
return errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err)
}
// Write to the canary path, which will force a synchronous truing during
@ -456,13 +518,10 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
Value: []byte(c.barrierRekeyConfig.Nonce),
}); err != nil {
c.logger.Error("error saving keyring canary", "error", err)
return nil, errwrap.Wrapf("failed to save keyring canary: {{err}}", err)
return errwrap.Wrapf("failed to save keyring canary: {{err}}", err)
}
// Done!
c.barrierRekeyProgress = nil
c.barrierRekeyConfig = nil
return results, nil
return nil
}
// RecoveryRekeyUpdate is used to provide a new key part
@ -528,14 +587,17 @@ func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string
return nil, nil
}
// Schedule the rekey progress for forgetting
defer func() {
c.recoveryRekeyProgress = nil
}()
// Recover the master key
var recoveryKey []byte
if existingConfig.SecretThreshold == 1 {
recoveryKey = c.recoveryRekeyProgress[0]
c.recoveryRekeyProgress = nil
} else {
recoveryKey, err = shamir.Combine(c.recoveryRekeyProgress)
c.recoveryRekeyProgress = nil
if err != nil {
return nil, errwrap.Wrapf("failed to compute recovery key: {{err}}", err)
}
@ -612,14 +674,41 @@ func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string
}
}
// 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, err
}
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, err
}
c.recoveryRekeyConfig = nil
return results, nil
}
func (c *Core) performRecoveryRekey(ctx context.Context, newMasterKey []byte) error {
if err := c.seal.SetRecoveryKey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to set recovery key", "error", err)
return nil, errwrap.Wrapf("failed to set recovery key: {{err}}", err)
return errwrap.Wrapf("failed to set recovery key: {{err}}", err)
}
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 nil, errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err)
return errwrap.Wrapf("failed to save rekey seal configuration: {{err}}", err)
}
// Write to the canary path, which will force a synchronous truing during
@ -629,13 +718,104 @@ func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string
Value: []byte(c.recoveryRekeyConfig.Nonce),
}); err != nil {
c.logger.Error("error saving keyring canary", "error", err)
return nil, errwrap.Wrapf("failed to save keyring canary: {{err}}", err)
return errwrap.Wrapf("failed to save keyring canary: {{err}}", err)
}
// Done!
c.recoveryRekeyProgress = nil
c.recoveryRekeyConfig = nil
return results, nil
return nil
}
func (c *Core) RekeyVerify(ctx context.Context, key []byte, recovery bool) (*RekeyVerifyResult, error) {
if recovery {
//return c.RecoveryRekeyVerify(ctx, key)
}
return c.BarrierRekeyVerify(ctx, key)
}
func (c *Core) BarrierRekeyVerify(ctx context.Context, key []byte) (*RekeyVerifyResult, error) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, consts.ErrSealed
}
if c.standby {
return nil, consts.ErrStandby
}
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
}
if len(key) > max {
return nil, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Ensure a rekey is in progress
if c.barrierRekeyConfig == nil {
return nil, fmt.Errorf("no rekey in progress")
}
// Check if we already have this piece
for _, existing := range c.barrierRekeyVerifyProgress {
if bytes.Equal(existing, key) {
return nil, fmt.Errorf("given key has already been provided during this verify operation")
}
}
// Store this key
c.barrierRekeyVerifyProgress = append(c.barrierRekeyVerifyProgress, key)
// Check if we don't have enough keys to unlock
if len(c.barrierRekeyVerifyProgress) < c.barrierRekeyConfig.SecretThreshold {
if c.logger.IsDebug() {
c.logger.Debug("cannot verify yet, not enough keys", "keys", len(c.barrierRekeyVerifyProgress), "threshold", c.barrierRekeyConfig.SecretThreshold)
}
return nil, nil
}
// Schedule the progress for forgetting and rotate the nonce if possible
defer func() {
c.barrierRekeyVerifyProgress = nil
if c.barrierRekeyConfig != nil {
nonce, err := uuid.GenerateUUID()
if err == nil {
c.barrierRekeyConfig.VerificationNonce = nonce
}
}
}()
// Recover the master key or recovery key
var recoveredKey []byte
var err error
if c.barrierRekeyConfig.SecretThreshold == 1 {
recoveredKey = c.barrierRekeyVerifyProgress[0]
} else {
recoveredKey, err = shamir.Combine(c.barrierRekeyVerifyProgress)
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key for verification: {{err}}", err)
}
}
if subtle.ConstantTimeCompare(recoveredKey, c.barrierRekeyConfig.VerificationKey) != 1 {
c.logger.Error("rekey verification failed")
return nil, errors.New("rekey verification failed")
}
if err := c.performBarrierRekey(ctx, recoveredKey); err != nil {
return nil, err
}
res := &RekeyVerifyResult{
Nonce: c.barrierRekeyConfig.VerificationNonce,
}
c.barrierRekeyConfig = nil
return res, nil
}
// RekeyCancel is used to cancel an inprogress rekey
@ -663,6 +843,40 @@ func (c *Core) RekeyCancel(recovery bool) error {
return nil
}
// RekeyVerifyCancel is used to start the verification process over
func (c *Core) RekeyVerifyRestart(recovery bool) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return consts.ErrSealed
}
if c.standby {
return consts.ErrStandby
}
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.recoveryRekeyVerifyProgress = nil
if nonceErr == nil {
c.recoveryRekeyConfig.VerificationNonce = nonce
}
} else {
c.barrierRekeyVerifyProgress = 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, error) {

View File

@ -272,6 +272,19 @@ type SealConfig struct {
// How many keys to store, for seals that support storage.
StoredShares int `json:"stored_shares"`
// VerificationRequired indicates that after a rekey validation must be
// performed (via providing shares from the new key) before the new key is
// actually installed. This is ommitted from JSON as we don't persist the
// new key, it lives only in memory.
VerificationRequired bool `json:"-"`
// VerificationKey is the new key that we will roll to after successful
// validation
VerificationKey []byte `json:"-"`
// VerificationNonce stores the current operation nonce for verification
VerificationNonce string `json:"-"`
}
// Validate is used to sanity check the seal configuration
@ -323,10 +336,15 @@ func (s *SealConfig) Clone() *SealConfig {
Nonce: s.Nonce,
Backup: s.Backup,
StoredShares: s.StoredShares,
VerificationRequired: s.VerificationRequired,
}
if len(s.PGPKeys) > 0 {
ret.PGPKeys = make([]string, len(s.PGPKeys))
copy(ret.PGPKeys, s.PGPKeys)
}
if len(s.VerificationKey) > 0 {
ret.VerificationKey = make([]byte, len(s.VerificationKey))
copy(ret.VerificationKey, s.VerificationKey)
}
return ret
}