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.
This commit is contained in:
ncabatoff
2019-10-18 14:46:00 -04:00
committed by GitHub
parent 8f8f05c598
commit afcba41190
30 changed files with 825 additions and 541 deletions

View File

@@ -325,7 +325,7 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
keys[len(keys)-1], // the last unseal key keys[len(keys)-1], // the last unseal key
}) })
if exp := 0; code != exp { if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp) t.Fatalf("expected %d to be %d, out=%q, err=%q", code, exp, ui.OutputWriter, ui.ErrorWriter)
} }
reToken := regexp.MustCompile(`Encoded Token\s+(.+)`) reToken := regexp.MustCompile(`Encoded Token\s+(.+)`)

View File

@@ -303,7 +303,7 @@ func TestOperatorInitCommand_Run(t *testing.T) {
"-root-token-pgp-key", pubFiles[0], "-root-token-pgp-key", pubFiles[0],
}) })
if exp := 0; code != exp { if exp := 0; code != exp {
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) t.Fatalf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
} }
re := regexp.MustCompile(`Unseal Key \d+: (.+)`) re := regexp.MustCompile(`Unseal Key \d+: (.+)`)

View File

@@ -20,7 +20,7 @@ import (
) )
func TestSealMigration(t *testing.T) { func TestSealMigration(t *testing.T) {
logger := logging.NewVaultLogger(hclog.Trace) logger := logging.NewVaultLogger(hclog.Trace).Named(t.Name())
phys, err := physInmem.NewInmem(nil, logger) phys, err := physInmem.NewInmem(nil, logger)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -47,8 +47,8 @@ func TestSealMigration(t *testing.T) {
var keys []string var keys []string
var rootToken string var rootToken string
// First: start up as normal with shamir seal, init it
{ {
logger.Info("integ: start up as normal with shamir seal, init it")
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
defer cluster.Cleanup() defer cluster.Cleanup()
@@ -73,9 +73,9 @@ func TestSealMigration(t *testing.T) {
cluster.Cores = nil cluster.Cores = nil
} }
// Second: start up as normal with shamir seal and unseal, make sure
// everything is normal
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: start up as normal with shamir seal and unseal, make sure everything is normal")
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
defer cluster.Cleanup() defer cluster.Cleanup()
@@ -103,8 +103,9 @@ func TestSealMigration(t *testing.T) {
var autoSeal vault.Seal var autoSeal vault.Seal
// Third: create an autoseal and activate migration
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: creating an autoseal and activating migration")
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
defer cluster.Cleanup() defer cluster.Cleanup()
@@ -147,8 +148,9 @@ func TestSealMigration(t *testing.T) {
cluster.Cores = nil cluster.Cores = nil
} }
// Fourth: verify autoseal and recovery key usage
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: verify autoseal and recovery key usage")
coreConfig.Seal = autoSeal coreConfig.Seal = autoSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
@@ -202,8 +204,9 @@ func TestSealMigration(t *testing.T) {
altTestSeal.Type = "test-alternate" altTestSeal.Type = "test-alternate"
altSeal := vault.NewAutoSeal(altTestSeal) altSeal := vault.NewAutoSeal(altTestSeal)
// Fifth: migrate from auto-seal to auto-seal
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: migrate from auto-seal to auto-seal")
coreConfig.Seal = autoSeal coreConfig.Seal = autoSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
@@ -239,9 +242,9 @@ func TestSealMigration(t *testing.T) {
cluster.Cores = nil cluster.Cores = nil
} }
// Sixth: create an Shamir seal and activate migration. Verify it doesn't work
// if disabled isn't set.
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: create a Shamir seal and activate migration; verify it doesn't work if disabled isn't set.")
coreConfig.Seal = altSeal coreConfig.Seal = altSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()
@@ -282,12 +285,9 @@ func TestSealMigration(t *testing.T) {
cluster.Cores = nil cluster.Cores = nil
} }
if entry, err := phys.Get(ctx, vault.StoredBarrierKeysPath); err != nil || entry != nil {
t.Fatalf("expected nil error and nil entry, got error %#v and entry %#v", err, entry)
}
// Seventh: verify autoseal is off and the expected key shares work
{ {
logger.SetLevel(hclog.Trace)
logger.Info("integ: verify autoseal is off and the expected key shares work")
coreConfig.Seal = shamirSeal coreConfig.Seal = shamirSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig) cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start() cluster.Start()

View File

@@ -1822,7 +1822,7 @@ func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig
} }
} }
if core.SealAccess().StoredKeysSupported() { if core.SealAccess().StoredKeysSupported() != vault.StoredKeysNotSupported {
barrierConfig.StoredShares = 1 barrierConfig.StoredShares = 1
} }
@@ -1836,7 +1836,7 @@ func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig
} }
// Handle unseal with stored keys // Handle unseal with stored keys
if core.SealAccess().StoredKeysSupported() { if core.SealAccess().StoredKeysSupported() == vault.StoredKeysSupportedGeneric {
err := core.UnsealWithStoredKeys(ctx) err := core.UnsealWithStoredKeys(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -87,7 +87,7 @@ func adjustCoreForSealMigration(logger log.Logger, core *vault.Core, barrierSeal
return errors.New("Migrating from autoseal to Shamir seal is not currently supported on Vault Enterprise") return errors.New("Migrating from autoseal to Shamir seal is not currently supported on Vault Enterprise")
} }
// If we're not cominng from Shamir we expect the previous seal to be // If we're not coming from Shamir we expect the previous seal to be
// in the config and disabled. // in the config and disabled.
existSeal = unwrapSeal existSeal = unwrapSeal
newSeal = barrierSeal newSeal = barrierSeal

View File

@@ -131,18 +131,32 @@ func EnsureCoreSealed(t testing.T, core *vault.TestClusterCore) {
func EnsureCoresUnsealed(t testing.T, c *vault.TestCluster) { func EnsureCoresUnsealed(t testing.T, c *vault.TestCluster) {
t.Helper() t.Helper()
for _, core := range c.Cores { for i, core := range c.Cores {
EnsureCoreUnsealed(t, c, core) err := AttemptUnsealCore(c, core)
if err != nil {
t.Fatalf("failed to unseal core %d: %v", i, err)
}
} }
} }
func EnsureCoreUnsealed(t testing.T, c *vault.TestCluster, core *vault.TestClusterCore) {
func AttemptUnsealCores(c *vault.TestCluster) error {
for i, core := range c.Cores {
err := AttemptUnsealCore(c, core)
if err != nil {
return fmt.Errorf("failed to unseal core %d: %v", i, err)
}
}
return nil
}
func AttemptUnsealCore(c *vault.TestCluster, core *vault.TestClusterCore) error {
if !core.Sealed() { if !core.Sealed() {
return return nil
} }
core.SealAccess().ClearCaches(context.Background()) core.SealAccess().ClearCaches(context.Background())
if err := core.UnsealWithStoredKeys(context.Background()); err != nil { if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
t.Fatal(err) return err
} }
client := core.Client client := core.Client
@@ -153,20 +167,22 @@ func EnsureCoreUnsealed(t testing.T, c *vault.TestCluster, core *vault.TestClust
// Sometimes when we get here it's already unsealed on its own // Sometimes when we get here it's already unsealed on its own
// and then this fails for DR secondaries so check again // and then this fails for DR secondaries so check again
if core.Sealed() { if core.Sealed() {
t.Fatal(err) return err
} else {
return nil
} }
break
} }
if statusResp == nil { if statusResp == nil {
t.Fatal("nil status response during unseal") return fmt.Errorf("nil status response during unseal")
} }
if !statusResp.Sealed { if !statusResp.Sealed {
break break
} }
} }
if core.Sealed() { if core.Sealed() {
t.Fatal("core is still sealed") return fmt.Errorf("core is still sealed")
} }
return nil
} }
func EnsureStableActiveNode(t testing.T, cluster *vault.TestCluster) { func EnsureStableActiveNode(t testing.T, cluster *vault.TestCluster) {
@@ -270,10 +286,16 @@ func WaitForActiveNode(t testing.T, cluster *vault.TestCluster) *vault.TestClust
return nil return nil
} }
func RekeyCluster(t testing.T, cluster *vault.TestCluster) { func RekeyCluster(t testing.T, cluster *vault.TestCluster, recovery bool) [][]byte {
t.Helper()
cluster.Logger.Info("rekeying cluster", "recovery", recovery)
client := cluster.Cores[0].Client client := cluster.Cores[0].Client
init, err := client.Sys().RekeyInit(&api.RekeyInitRequest{ initFunc := client.Sys().RekeyInit
if recovery {
initFunc = client.Sys().RekeyRecoveryKeyInit
}
init, err := initFunc(&api.RekeyInitRequest{
SecretShares: 5, SecretShares: 5,
SecretThreshold: 3, SecretThreshold: 3,
}) })
@@ -282,8 +304,17 @@ func RekeyCluster(t testing.T, cluster *vault.TestCluster) {
} }
var statusResp *api.RekeyUpdateResponse var statusResp *api.RekeyUpdateResponse
for j := 0; j < len(cluster.BarrierKeys); j++ { var keys = cluster.BarrierKeys
statusResp, err = client.Sys().RekeyUpdate(base64.StdEncoding.EncodeToString(cluster.BarrierKeys[j]), init.Nonce) if cluster.Cores[0].Core.SealAccess().RecoveryKeySupported() {
keys = cluster.RecoveryKeys
}
updateFunc := client.Sys().RekeyUpdate
if recovery {
updateFunc = client.Sys().RekeyRecoveryKeyUpdate
}
for j := 0; j < len(keys); j++ {
statusResp, err = updateFunc(base64.StdEncoding.EncodeToString(keys[j]), init.Nonce)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -294,20 +325,23 @@ func RekeyCluster(t testing.T, cluster *vault.TestCluster) {
break break
} }
} }
cluster.Logger.Info("cluster rekeyed", "recovery", recovery)
if cluster.Cores[0].Core.SealAccess().RecoveryKeySupported() && !recovery {
return nil
}
if len(statusResp.KeysB64) != 5 { if len(statusResp.KeysB64) != 5 {
t.Fatal("wrong number of keys") t.Fatal("wrong number of keys")
} }
newBarrierKeys := make([][]byte, 5) newKeys := make([][]byte, 5)
for i, key := range statusResp.KeysB64 { for i, key := range statusResp.KeysB64 {
newBarrierKeys[i], err = base64.StdEncoding.DecodeString(key) newKeys[i], err = base64.StdEncoding.DecodeString(key)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
return newKeys
cluster.BarrierKeys = newBarrierKeys
} }
type TestRaftServerAddressProvider struct { type TestRaftServerAddressProvider struct {

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt"
"net/http" "net/http"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
@@ -59,42 +58,6 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
PGPKeys: req.RecoveryPGPKeys, PGPKeys: req.RecoveryPGPKeys,
} }
// N.B. Although the core is capable of handling situations where some keys
// are stored and some aren't, in practice, replication + HSMs makes this
// extremely hard to reason about, to the point that it will probably never
// be supported. The reason is that each HSM needs to encode the master key
// separately, which means the shares must be generated independently,
// which means both that the shares will be different *AND* there would
// need to be a way to actually allow fetching of the generated keys by
// operators.
if core.SealAccess().StoredKeysSupported() {
if len(barrierConfig.PGPKeys) > 0 {
respondError(w, http.StatusBadRequest, fmt.Errorf("PGP keys not supported when storing shares"))
return
}
barrierConfig.SecretShares = 1
barrierConfig.SecretThreshold = 1
barrierConfig.StoredShares = 1
core.Logger().Warn("stored keys supported on init, forcing shares/threshold to 1")
} else {
if barrierConfig.StoredShares > 0 {
respondError(w, http.StatusBadRequest, fmt.Errorf("stored keys are not supported by the current seal type"))
return
}
}
if len(barrierConfig.PGPKeys) > 0 && len(barrierConfig.PGPKeys) != barrierConfig.SecretShares {
respondError(w, http.StatusBadRequest, fmt.Errorf("incorrect number of PGP keys"))
return
}
if core.SealAccess().RecoveryKeySupported() {
if len(recoveryConfig.PGPKeys) > 0 && len(recoveryConfig.PGPKeys) != recoveryConfig.SecretShares {
respondError(w, http.StatusBadRequest, fmt.Errorf("incorrect number of PGP keys for recovery"))
return
}
}
initParams := &vault.InitParams{ initParams := &vault.InitParams{
BarrierConfig: barrierConfig, BarrierConfig: barrierConfig,
RecoveryConfig: recoveryConfig, RecoveryConfig: recoveryConfig,

View File

@@ -55,6 +55,12 @@ const (
// keyring to discover the new master key. The new master key is then // keyring to discover the new master key. The new master key is then
// used to reload the keyring itself. // used to reload the keyring itself.
masterKeyPath = "core/master" masterKeyPath = "core/master"
// shamirKekPath is used with Shamir in v1.3+ to store a copy of the
// unseal key behind the barrier. As with masterKeyPath this is primarily
// used by standbys to handle rekeys. It also comes into play when restoring
// raft snapshots.
shamirKekPath = "core/shamir-kek"
) )
// SecurityBarrier is a critical component of Vault. It is used to wrap // SecurityBarrier is a critical component of Vault. It is used to wrap
@@ -69,8 +75,10 @@ type SecurityBarrier interface {
Initialized(ctx context.Context) (bool, error) Initialized(ctx context.Context) (bool, error)
// Initialize works only if the barrier has not been initialized // Initialize works only if the barrier has not been initialized
// and makes use of the given master key. // and makes use of the given master key. When sealKey is provided
Initialize(context.Context, []byte, io.Reader) error // it's because we're using a new-style Shamir seal, and masterKey
// is to be stored using sealKey to encrypt it.
Initialize(ctx context.Context, masterKey []byte, sealKey []byte, random io.Reader) error
// GenerateKey is used to generate a new key // GenerateKey is used to generate a new key
GenerateKey(io.Reader) ([]byte, error) GenerateKey(io.Reader) ([]byte, error)

View File

@@ -115,7 +115,7 @@ func (b *AESGCMBarrier) Initialized(ctx context.Context) (bool, error) {
// Initialize works only if the barrier has not been initialized // Initialize works only if the barrier has not been initialized
// and makes use of the given master key. // and makes use of the given master key.
func (b *AESGCMBarrier) Initialize(ctx context.Context, key []byte, reader io.Reader) error { func (b *AESGCMBarrier) Initialize(ctx context.Context, key, sealKey []byte, reader io.Reader) error {
// Verify the key size // Verify the key size
min, max := b.KeyLength() min, max := b.KeyLength()
if len(key) < min || len(key) > max { if len(key) < min || len(key) > max {
@@ -146,7 +146,28 @@ func (b *AESGCMBarrier) Initialize(ctx context.Context, key []byte, reader io.Re
if err != nil { if err != nil {
return errwrap.Wrapf("failed to create keyring: {{err}}", err) return errwrap.Wrapf("failed to create keyring: {{err}}", err)
} }
return b.persistKeyring(ctx, keyring)
err = b.persistKeyring(ctx, keyring)
if err != nil {
return err
}
if len(sealKey) > 0 {
primary, err := b.aeadFromKey(encrypt)
if err != nil {
return err
}
err = b.putInternal(ctx, 1, primary, &logical.StorageEntry{
Key: shamirKekPath,
Value: sealKey,
})
if err != nil {
return errwrap.Wrapf("failed to store new seal key: {{err}}", err)
}
}
return nil
} }
// persistKeyring is used to write out the keyring using the // persistKeyring is used to write out the keyring using the
@@ -720,6 +741,10 @@ func (b *AESGCMBarrier) Put(ctx context.Context, entry *logical.StorageEntry) er
return err return err
} }
return b.putInternal(ctx, term, primary, entry)
}
func (b *AESGCMBarrier) putInternal(ctx context.Context, term uint32, primary cipher.AEAD, entry *logical.StorageEntry) error {
value, err := b.encrypt(entry.Key, term, primary, entry.Value) value, err := b.encrypt(entry.Key, term, primary, entry.Value)
if err != nil { if err != nil {
return err return err

View File

@@ -31,7 +31,7 @@ func mockBarrier(t testing.TB) (physical.Backend, SecurityBarrier, []byte) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
return inm, b, key return inm, b, key
} }
@@ -207,7 +207,7 @@ func TestAESGCMBarrier_Confidential(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
// Put a logical entry // Put a logical entry
@@ -247,7 +247,7 @@ func TestAESGCMBarrier_Integrity(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
// Put a logical entry // Put a logical entry
@@ -286,7 +286,7 @@ func TestAESGCMBarrier_MoveIntegrityV1(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
err = b.Initialize(context.Background(), key, rand.Reader) err = b.Initialize(context.Background(), key, nil, rand.Reader)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@@ -330,7 +330,7 @@ func TestAESGCMBarrier_MoveIntegrityV2(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
err = b.Initialize(context.Background(), key, rand.Reader) err = b.Initialize(context.Background(), key, nil, rand.Reader)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@@ -374,7 +374,7 @@ func TestAESGCMBarrier_UpgradeV1toV2(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
err = b.Initialize(context.Background(), key, rand.Reader) err = b.Initialize(context.Background(), key, nil, rand.Reader)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@@ -427,7 +427,7 @@ func TestEncrypt_Unique(t *testing.T) {
} }
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
if b.keyring == nil { if b.keyring == nil {
@@ -466,19 +466,19 @@ func TestInitialize_KeyLength(t *testing.T) {
middle := []byte("ThisIsASecretKeyAndMore") middle := []byte("ThisIsASecretKeyAndMore")
short := []byte("Key") short := []byte("Key")
err = b.Initialize(context.Background(), long, rand.Reader) err = b.Initialize(context.Background(), long, nil, rand.Reader)
if err == nil { if err == nil {
t.Fatalf("key length protection failed") t.Fatalf("key length protection failed")
} }
err = b.Initialize(context.Background(), middle, rand.Reader) err = b.Initialize(context.Background(), middle, nil, rand.Reader)
if err == nil { if err == nil {
t.Fatalf("key length protection failed") t.Fatalf("key length protection failed")
} }
err = b.Initialize(context.Background(), short, rand.Reader) err = b.Initialize(context.Background(), short, nil, rand.Reader)
if err == nil { if err == nil {
t.Fatalf("key length protection failed") t.Fatalf("key length protection failed")
@@ -500,7 +500,7 @@ func TestEncrypt_BarrierEncryptor(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
cipher, err := b.Encrypt(context.Background(), "foo", []byte("quick brown fox")) cipher, err := b.Encrypt(context.Background(), "foo", []byte("quick brown fox"))
@@ -530,7 +530,7 @@ func TestAESGCMBarrier_ReloadKeyring(t *testing.T) {
// Initialize and unseal // Initialize and unseal
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
b.Unseal(context.Background(), key) b.Unseal(context.Background(), key)
keyringRaw, err := inm.Get(context.Background(), keyringPath) keyringRaw, err := inm.Get(context.Background(), keyringPath)

View File

@@ -70,12 +70,12 @@ func testBarrier(t *testing.T, b SecurityBarrier) {
} }
// Initialize the vault // Initialize the vault
if err := b.Initialize(context.Background(), key, rand.Reader); err != nil { if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// Double Initialize should fail // Double Initialize should fail
if err := b.Initialize(context.Background(), key, rand.Reader); err != ErrBarrierAlreadyInit { if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != ErrBarrierAlreadyInit {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@@ -247,7 +247,7 @@ func testBarrier(t *testing.T, b SecurityBarrier) {
func testBarrier_Rotate(t *testing.T, b SecurityBarrier) { func testBarrier_Rotate(t *testing.T, b SecurityBarrier) {
// Initialize the barrier // Initialize the barrier
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
err := b.Unseal(context.Background(), key) err := b.Unseal(context.Background(), key)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@@ -353,7 +353,7 @@ func testBarrier_Rotate(t *testing.T, b SecurityBarrier) {
func testBarrier_Rekey(t *testing.T, b SecurityBarrier) { func testBarrier_Rekey(t *testing.T, b SecurityBarrier) {
// Initialize the barrier // Initialize the barrier
key, _ := b.GenerateKey(rand.Reader) key, _ := b.GenerateKey(rand.Reader)
b.Initialize(context.Background(), key, rand.Reader) b.Initialize(context.Background(), key, nil, rand.Reader)
err := b.Unseal(context.Background(), key) err := b.Unseal(context.Background(), key)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@@ -433,7 +433,7 @@ func testBarrier_Rekey(t *testing.T, b SecurityBarrier) {
func testBarrier_Upgrade(t *testing.T, b1, b2 SecurityBarrier) { func testBarrier_Upgrade(t *testing.T, b1, b2 SecurityBarrier) {
// Initialize the barrier // Initialize the barrier
key, _ := b1.GenerateKey(rand.Reader) key, _ := b1.GenerateKey(rand.Reader)
b1.Initialize(context.Background(), key, rand.Reader) b1.Initialize(context.Background(), key, nil, rand.Reader)
err := b1.Unseal(context.Background(), key) err := b1.Unseal(context.Background(), key)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@@ -504,7 +504,7 @@ func testBarrier_Upgrade(t *testing.T, b1, b2 SecurityBarrier) {
func testBarrier_Upgrade_Rekey(t *testing.T, b1, b2 SecurityBarrier) { func testBarrier_Upgrade_Rekey(t *testing.T, b1, b2 SecurityBarrier) {
// Initialize the barrier // Initialize the barrier
key, _ := b1.GenerateKey(rand.Reader) key, _ := b1.GenerateKey(rand.Reader)
b1.Initialize(context.Background(), key, rand.Reader) b1.Initialize(context.Background(), key, nil, rand.Reader)
err := b1.Unseal(context.Background(), key) err := b1.Unseal(context.Background(), key)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)

View File

@@ -6,7 +6,6 @@ import (
"crypto/rand" "crypto/rand"
"crypto/subtle" "crypto/subtle"
"crypto/x509" "crypto/x509"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -932,9 +931,12 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
sealToUse := c.seal sealToUse := c.seal
if c.migrationSeal != nil { if c.migrationSeal != nil {
c.logger.Info("unsealing using migration seal")
sealToUse = c.migrationSeal sealToUse = c.migrationSeal
} }
// unsealPart returns either a master key (legacy shamir) or an unseal
// key (new-style shamir).
masterKey, err := c.unsealPart(ctx, sealToUse, key, useRecoveryKeys) masterKey, err := c.unsealPart(ctx, sealToUse, key, useRecoveryKeys)
if err != nil { if err != nil {
return false, err return false, err
@@ -942,15 +944,39 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
if masterKey != nil { if masterKey != nil {
if c.seal.BarrierType() == seal.Shamir { if c.seal.BarrierType() == seal.Shamir {
_, err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{ // If this is a legacy shamir seal this serves no purpose but it
"key": base64.StdEncoding.EncodeToString(masterKey), // doesn't hurt.
}) err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(masterKey)
if err != nil { if err != nil {
return false, err return false, err
} }
} }
if !c.isRaftUnseal() { if !c.isRaftUnseal() {
if c.seal.BarrierType() == seal.Shamir {
cfg, err := c.seal.BarrierConfig(ctx)
if err != nil {
return false, err
}
// If there is a stored key, retrieve it.
if cfg.StoredShares > 0 {
if err != nil {
return false, err
}
// Here's where we actually test that the provided unseal
// key is valid: can it decrypt the stored master key?
storedKeys, err := c.seal.GetStoredKeys(ctx)
if err != nil {
return false, err
}
if len(storedKeys) == 0 {
return false, fmt.Errorf("shamir seal with stored keys configured but no stored keys found")
}
masterKey = storedKeys[0]
}
}
return c.unsealInternal(ctx, masterKey) return c.unsealInternal(ctx, masterKey)
} }
@@ -964,8 +990,9 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
go func() { go func() {
keyringFound := false keyringFound := false
haveMasterKey := c.seal.StoredKeysSupported() != StoredKeysSupportedShamirMaster
defer func() { defer func() {
if keyringFound { if keyringFound && haveMasterKey {
_, err := c.unsealInternal(ctx, masterKey) _, err := c.unsealInternal(ctx, masterKey)
if err != nil { if err != nil {
c.logger.Error("failed to unseal", "error", err) c.logger.Error("failed to unseal", "error", err)
@@ -976,18 +1003,31 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
// Wait until we at least have the keyring before we attempt to // Wait until we at least have the keyring before we attempt to
// unseal the node. // unseal the node.
for { for {
keys, err := c.underlyingPhysical.List(ctx, keyringPrefix) if !keyringFound {
if err != nil { keys, err := c.underlyingPhysical.List(ctx, keyringPrefix)
c.logger.Error("failed to list physical keys", "error", err) if err != nil {
c.logger.Error("failed to list physical keys", "error", err)
return
}
if strutil.StrListContains(keys, "keyring") {
keyringFound = true
}
}
if !haveMasterKey {
keys, err := c.seal.GetStoredKeys(ctx)
if err != nil {
c.logger.Error("failed to read master key", "error", err)
return
}
if len(keys) > 0 {
haveMasterKey = true
masterKey = keys[0]
}
}
if keyringFound && haveMasterKey {
return return
} }
if strutil.StrListContains(keys, "keyring") { time.Sleep(1 * time.Second)
keyringFound = true
return
}
select {
case <-time.After(1 * time.Second):
}
} }
}() }()
@@ -1073,7 +1113,7 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
} }
if seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil) { if seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil) {
// Verify recovery key // Verify recovery key.
if err := seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil { if err := seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
return nil, err return nil, err
} }
@@ -1084,7 +1124,7 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
// keys setup, nor 2) seals that support recovery keys but not stored keys. // keys setup, nor 2) seals that support recovery keys but not stored keys.
// If insufficient shares are provided, shamir.Combine will error, and if // If insufficient shares are provided, shamir.Combine will error, and if
// no stored keys are found it will return masterKey as nil. // no stored keys are found it will return masterKey as nil.
if seal.StoredKeysSupported() { if seal.StoredKeysSupported() == StoredKeysSupportedGeneric {
masterKeyShares, err := seal.GetStoredKeys(ctx) masterKeyShares, err := seal.GetStoredKeys(ctx)
if err != nil { if err != nil {
return nil, errwrap.Wrapf("unable to retrieve stored keys: {{err}}", err) return nil, errwrap.Wrapf("unable to retrieve stored keys: {{err}}", err)
@@ -1105,9 +1145,21 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
} else { } else {
masterKey = recoveredKey masterKey = recoveredKey
} }
newRecoveryKey := masterKey
// If we have a migration seal, now's the time! // If we have a migration seal, now's the time!
if c.migrationSeal != nil { if c.migrationSeal != nil {
if seal.StoredKeysSupported() == StoredKeysSupportedShamirMaster {
err = seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(masterKey)
if err != nil {
return nil, errwrap.Wrapf("failed to set master key in seal: {{err}}", err)
}
storedKeys, err := seal.GetStoredKeys(ctx)
if err != nil {
return nil, errwrap.Wrapf("unable to retrieve stored keys: {{err}}", err)
}
masterKey = storedKeys[0]
}
// Unseal the barrier so we can rekey // Unseal the barrier so we can rekey
if err := c.barrier.Unseal(ctx, masterKey); err != nil { if err := c.barrier.Unseal(ctx, masterKey); err != nil {
return nil, errwrap.Wrapf("error unsealing barrier with constructed master key: {{err}}", err) return nil, errwrap.Wrapf("error unsealing barrier with constructed master key: {{err}}", err)
@@ -1145,14 +1197,13 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
} }
// We have recovery keys; we're going to use them as the new // We have recovery keys; we're going to use them as the new
// barrier key. // shamir KeK.
if err := c.barrier.Rekey(ctx, recoveryKey); err != nil { err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(recoveryKey)
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err) if err != nil {
return nil, errwrap.Wrapf("failed to set master key in seal: {{err}}", err)
} }
if err := c.seal.SetStoredKeys(ctx, [][]byte{masterKey}); err != nil {
if err := c.barrier.Delete(ctx, StoredBarrierKeysPath); err != nil { return nil, errwrap.Wrapf("error setting new barrier key information during migrate: {{err}}", err)
// Don't actually exit here as successful deletion isn't critical
c.logger.Error("error deleting stored barrier keys after migration; continuing anyways", "error", err)
} }
masterKey = recoveryKey masterKey = recoveryKey
@@ -1160,7 +1211,7 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
case c.seal.RecoveryKeySupported(): case c.seal.RecoveryKeySupported():
// The new seal will have recovery keys; we set it to the existing // The new seal will have recovery keys; we set it to the existing
// master key, so barrier key shares -> recovery key shares // master key, so barrier key shares -> recovery key shares
if err := c.seal.SetRecoveryKey(ctx, masterKey); err != nil { if err := c.seal.SetRecoveryKey(ctx, newRecoveryKey); err != nil {
return nil, errwrap.Wrapf("error setting new recovery key information: {{err}}", err) return nil, errwrap.Wrapf("error setting new recovery key information: {{err}}", err)
} }

View File

@@ -2,25 +2,66 @@ package api
import ( import (
"encoding/base64" "encoding/base64"
"strings" "fmt"
"testing" "testing"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/testhelpers" "github.com/hashicorp/vault/helper/testhelpers"
vaulthttp "github.com/hashicorp/vault/http" vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/shamir" "github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/physical/inmem"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
) )
func TestSysRekey_Verification(t *testing.T) { func TestSysRekey_Verification(t *testing.T) {
testSysRekey_Verification(t, false) testcases := []struct {
testSysRekey_Verification(t, true) recovery bool
legacyShamir bool
}{
{recovery: true, legacyShamir: false},
{recovery: false, legacyShamir: false},
{recovery: false, legacyShamir: true},
}
for _, tc := range testcases {
recovery, legacy := tc.recovery, tc.legacyShamir
t.Run(fmt.Sprintf("recovery=%v,legacyShamir=%v", recovery, legacy), func(t *testing.T) {
t.Parallel()
testSysRekey_Verification(t, recovery, legacy)
})
}
} }
func testSysRekey_Verification(t *testing.T, recovery bool) { func testSysRekey_Verification(t *testing.T, recovery bool, legacyShamir bool) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ opts := &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler, HandlerFunc: vaulthttp.Handler,
}) }
switch {
case recovery:
if legacyShamir {
panic("invalid case")
}
opts.SealFunc = func() vault.Seal {
return vault.NewTestSeal(t, &vault.TestSealOpts{
StoredKeys: vault.StoredKeysSupportedGeneric,
})
}
case legacyShamir:
opts.SealFunc = func() vault.Seal {
return vault.NewTestSeal(t, &vault.TestSealOpts{
StoredKeys: vault.StoredKeysNotSupported,
})
}
}
inm, err := inmem.NewInmemHA(nil, logging.NewVaultLogger(hclog.Debug))
if err != nil {
t.Fatal(err)
}
conf := vault.CoreConfig{
Physical: inm,
}
cluster := vault.NewTestCluster(t, &conf, opts)
cluster.Start() cluster.Start()
defer cluster.Cleanup() defer cluster.Cleanup()
@@ -41,48 +82,6 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
verificationCancelFunc = client.Sys().RekeyRecoveryKeyVerificationCancel verificationCancelFunc = client.Sys().RekeyRecoveryKeyVerificationCancel
} }
sealAccess := cluster.Cores[0].Core.SealAccess()
sealTestingParams := &vault.SealAccessTestingParams{}
// This first block verifies that if we are using recovery keys to force a
// rekey of a stored-shares barrier that verification is not allowed since
// the keys aren't returned
if !recovery {
sealTestingParams.PretendToAllowRecoveryKeys = true
sealTestingParams.PretendToAllowStoredShares = true
if err := sealAccess.SetTestingParams(sealTestingParams); err != nil {
t.Fatal(err)
}
_, err := initFunc(&api.RekeyInitRequest{
StoredShares: 1,
RequireVerification: true,
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "requiring verification not supported") {
t.Fatalf("unexpected error: %v", err)
}
// Now we set things back and start a normal rekey with the verification process
sealTestingParams.PretendToAllowRecoveryKeys = false
sealTestingParams.PretendToAllowStoredShares = false
if err := sealAccess.SetTestingParams(sealTestingParams); err != nil {
t.Fatal(err)
}
} else {
cluster.RecoveryKeys = cluster.BarrierKeys
sealTestingParams.PretendToAllowRecoveryKeys = true
recoveryKey, err := shamir.Combine(cluster.BarrierKeys)
if err != nil {
t.Fatal(err)
}
sealTestingParams.PretendRecoveryKey = recoveryKey
if err := sealAccess.SetTestingParams(sealTestingParams); err != nil {
t.Fatal(err)
}
}
var verificationNonce string var verificationNonce string
var newKeys []string var newKeys []string
doRekeyInitialSteps := func() { doRekeyInitialSteps := func() {
@@ -101,9 +100,13 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
t.Fatal("expected verification required") t.Fatal("expected verification required")
} }
keys := cluster.BarrierKeys
if recovery {
keys = cluster.RecoveryKeys
}
var resp *api.RekeyUpdateResponse var resp *api.RekeyUpdateResponse
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
resp, err = updateFunc(base64.StdEncoding.EncodeToString(cluster.BarrierKeys[i]), status.Nonce) resp, err = updateFunc(base64.StdEncoding.EncodeToString(keys[i]), status.Nonce)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -124,7 +127,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
doRekeyInitialSteps() doRekeyInitialSteps()
// We are still going, so should not be able to init again // We are still going, so should not be able to init again
_, err := initFunc(&api.RekeyInitRequest{ _, err = initFunc(&api.RekeyInitRequest{
SecretShares: 5, SecretShares: 5,
SecretThreshold: 3, SecretThreshold: 3,
RequireVerification: true, RequireVerification: true,
@@ -136,7 +139,12 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
// Sealing should clear state, so after this we should be able to perform // Sealing should clear state, so after this we should be able to perform
// the above again // the above again
cluster.EnsureCoresSealed(t) cluster.EnsureCoresSealed(t)
cluster.UnsealCores(t) if recovery {
cluster.UnsealWithStoredKeys(t)
} else {
cluster.UnsealCores(t)
}
vault.TestWaitActive(t, cluster.Cores[0].Core)
doRekeyInitialSteps() doRekeyInitialSteps()
doStartVerify := func() { doStartVerify := func() {
@@ -211,6 +219,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
// still be the old keys (which are still currently set) // still be the old keys (which are still currently set)
cluster.EnsureCoresSealed(t) cluster.EnsureCoresSealed(t)
cluster.UnsealCores(t) cluster.UnsealCores(t)
vault.TestWaitActive(t, cluster.Cores[0].Core)
// Should be able to init again and get back to where we were // Should be able to init again and get back to where we were
doRekeyInitialSteps() doRekeyInitialSteps()
@@ -237,6 +246,18 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
// Seal and unseal -- it should fail to unseal because the key has now been // Seal and unseal -- it should fail to unseal because the key has now been
// rotated // rotated
cluster.EnsureCoresSealed(t) cluster.EnsureCoresSealed(t)
// Simulate restarting Vault rather than just a seal/unseal, because
// the standbys may not have had time to learn about the new key before
// we sealed them. We could sleep, but that's unreliable.
oldKeys := cluster.BarrierKeys
opts.SkipInit = true
opts.SealFunc = nil // post rekey we should use the barrier config on disk
cluster = vault.NewTestCluster(t, &conf, opts)
cluster.BarrierKeys = oldKeys
cluster.Start()
defer cluster.Cleanup()
if err := cluster.UnsealCoresWithError(); err == nil { if err := cluster.UnsealCoresWithError(); err == nil {
t.Fatal("expected error") t.Fatal("expected error")
} }
@@ -252,7 +273,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
} }
cluster.BarrierKeys = newKeyBytes cluster.BarrierKeys = newKeyBytes
if err := cluster.UnsealCoresWithError(); err != nil { if err := cluster.UnsealCoresWithError(); err != nil {
t.Fatal("expected error") t.Fatal(err)
} }
} else { } else {
// The old keys should no longer work // The old keys should no longer work
@@ -261,7 +282,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
t.Fatal("expected error") t.Fatal("expected error")
} }
// Put tne new keys in place and run again // Put the new keys in place and run again
cluster.RecoveryKeys = nil cluster.RecoveryKeys = nil
for _, key := range newKeys { for _, key := range newKeys {
dec, err := base64.StdEncoding.DecodeString(key) dec, err := base64.StdEncoding.DecodeString(key)

View File

@@ -373,7 +373,7 @@ func TestRaft_SnapshotAPI_RekeyRotate_Backward(t *testing.T) {
if tCaseLocal.Rekey { if tCaseLocal.Rekey {
// Rekey // Rekey
testhelpers.RekeyCluster(t, cluster) cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
} }
if tCaseLocal.Rekey { if tCaseLocal.Rekey {
@@ -520,7 +520,7 @@ func TestRaft_SnapshotAPI_RekeyRotate_Forward(t *testing.T) {
if tCaseLocal.Rekey { if tCaseLocal.Rekey {
// Rekey // Rekey
testhelpers.RekeyCluster(t, cluster) cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
} }
if tCaseLocal.Rotate { if tCaseLocal.Rotate {
// Set the key clean up to 0 so it's cleaned immediately. This // Set the key clean up to 0 so it's cleaned immediately. This
@@ -588,8 +588,8 @@ func TestRaft_SnapshotAPI_RekeyRotate_Forward(t *testing.T) {
} }
// Parse Response // Parse Response
apiResp := api.Response{Response: resp} apiResp := api.Response{Response: resp}
if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") { if apiResp.Error() == nil || !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
t.Fatal(apiResp.Error()) t.Fatalf("expected error verifying hash file, got %v", apiResp.Error())
} }
} }

View File

@@ -6,12 +6,14 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor" "github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/shamir" "github.com/hashicorp/vault/shamir"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
) )
const coreDROperationTokenPath = "core/dr-operation-token" const coreDROperationTokenPath = "core/dr-operation-token"
@@ -304,44 +306,47 @@ func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string,
// If we are in recovery mode, then retrieve // If we are in recovery mode, then retrieve
// the stored keys and unseal the barrier // the stored keys and unseal the barrier
if c.recoveryMode { if c.recoveryMode {
if !c.seal.StoredKeysSupported() { storedKeys, err := c.seal.GetStoredKeys(ctx)
c.logger.Error("root generation aborted, recovery key verified but stored keys unsupported")
return nil, errors.New("recovery key verified but stored keys unsupported")
}
masterKeyShares, err := c.seal.GetStoredKeys(ctx)
if err != nil { if err != nil {
return nil, errwrap.Wrapf("unable to retrieve stored keys in recovery mode: {{err}}", err) return nil, errwrap.Wrapf("unable to retrieve stored keys in recovery mode: {{err}}", err)
} }
switch len(masterKeyShares) {
case 0:
return nil, errors.New("seal returned no master key shares in recovery mode")
case 1:
combinedKey = masterKeyShares[0]
default:
combinedKey, err = shamir.Combine(masterKeyShares)
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key in recovery mode: {{err}}", err)
}
}
// Use the retrieved master key to unseal the barrier // Use the retrieved master key to unseal the barrier
if err := c.barrier.Unseal(ctx, combinedKey); err != nil { if err := c.barrier.Unseal(ctx, storedKeys[0]); err != nil {
c.logger.Error("root generation aborted, recovery operation token verification failed", "error", err) c.logger.Error("root generation aborted, recovery operation token verification failed", "error", err)
return nil, err return nil, err
} }
} }
default: default:
masterKey := combinedKey
if c.seal.StoredKeysSupported() == StoredKeysSupportedShamirMaster {
testseal := NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("testseal")))
testseal.SetCore(c)
cfg, err := c.seal.BarrierConfig(ctx)
if err != nil {
return nil, errwrap.Wrapf("failed to setup test barrier config: {{err}}", err)
}
testseal.SetCachedBarrierConfig(cfg)
err = testseal.GetAccess().(*shamirseal.ShamirSeal).SetKey(combinedKey)
if err != nil {
return nil, errwrap.Wrapf("failed to setup unseal key: {{err}}", err)
}
stored, err := testseal.GetStoredKeys(ctx)
if err != nil {
return nil, errwrap.Wrapf("failed to read master key: {{err}}", err)
}
masterKey = stored[0]
}
switch { switch {
case c.recoveryMode: case c.recoveryMode:
// If we are in recovery mode, being able to unseal // If we are in recovery mode, being able to unseal
// the barrier is how we establish authentication // the barrier is how we establish authentication
if err := c.barrier.Unseal(ctx, combinedKey); err != nil { if err := c.barrier.Unseal(ctx, masterKey); err != nil {
c.logger.Error("root generation aborted, recovery operation token verification failed", "error", err) c.logger.Error("root generation aborted, recovery operation token verification failed", "error", err)
return nil, err return nil, err
} }
default: default:
if err := c.barrier.VerifyMaster(combinedKey); err != nil { if err := c.barrier.VerifyMaster(masterKey); err != nil {
c.logger.Error("root generation aborted, master key verification failed", "error", err) c.logger.Error("root generation aborted, master key verification failed", "error", err)
return nil, err return nil, err
} }

View File

@@ -82,7 +82,8 @@ func TestCore_GenerateRoot_Init(t *testing.T) {
c, _, _ := TestCoreUnsealed(t) c, _, _ := TestCoreUnsealed(t)
testCore_GenerateRoot_Init_Common(t, c) testCore_GenerateRoot_Init_Common(t, c)
bc, rc := TestSealDefConfigs() bc := &SealConfig{SecretShares: 5, SecretThreshold: 3, StoredShares: 1}
rc := &SealConfig{SecretShares: 5, SecretThreshold: 3}
c, _, _, _ = TestCoreUnsealedWithConfigs(t, bc, rc) c, _, _, _ = TestCoreUnsealedWithConfigs(t, bc, rc)
testCore_GenerateRoot_Init_Common(t, c) testCore_GenerateRoot_Init_Common(t, c)
} }

View File

@@ -4,17 +4,17 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/x509" "crypto/x509"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/hashicorp/vault/vault/seal/shamir"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
metrics "github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/physical/raft" "github.com/hashicorp/vault/physical/raft"
"github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/certutil"
@@ -22,8 +22,6 @@ import (
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
"github.com/oklog/run" "github.com/oklog/run"
) )
@@ -772,22 +770,35 @@ func (c *Core) reloadMasterKey(ctx context.Context) error {
if err := c.barrier.ReloadMasterKey(ctx); err != nil { if err := c.barrier.ReloadMasterKey(ctx); err != nil {
return errwrap.Wrapf("error reloading master key: {{err}}", err) return errwrap.Wrapf("error reloading master key: {{err}}", err)
} }
return nil
}
if c.seal.BarrierType() == seal.Shamir { func (c *Core) reloadShamirKey(ctx context.Context) error {
_ = c.seal.SetBarrierConfig(ctx, nil)
if cfg, _ := c.seal.BarrierConfig(ctx); cfg == nil {
return nil
}
var shamirKey []byte
switch c.seal.StoredKeysSupported() {
case StoredKeysSupportedGeneric:
return nil
case StoredKeysSupportedShamirMaster:
entry, err := c.barrier.Get(ctx, shamirKekPath)
if err != nil {
return err
}
if entry == nil {
return nil
}
shamirKey = entry.Value
case StoredKeysNotSupported:
keyring, err := c.barrier.Keyring() keyring, err := c.barrier.Keyring()
if err != nil { if err != nil {
return errwrap.Wrapf("failed to update seal access: {{err}}", err) return errwrap.Wrapf("failed to update seal access: {{err}}", err)
} }
shamirKey = keyring.masterKey
_, err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
"key": base64.StdEncoding.EncodeToString(keyring.MasterKey()),
})
if err != nil {
return errwrap.Wrapf("failed to update seal access: {{err}}", err)
}
} }
return c.seal.GetAccess().(*shamir.ShamirSeal).SetKey(shamirKey)
return nil
} }
func (c *Core) performKeyUpgrades(ctx context.Context) error { func (c *Core) performKeyUpgrades(ctx context.Context) error {
@@ -803,6 +814,10 @@ func (c *Core) performKeyUpgrades(ctx context.Context) error {
return errwrap.Wrapf("error reloading keyring: {{err}}", err) return errwrap.Wrapf("error reloading keyring: {{err}}", err)
} }
if err := c.reloadShamirKey(ctx); err != nil {
return errwrap.Wrapf("error reloading shamir kek key: {{err}}", err)
}
if err := c.scheduleUpgradeCleanup(ctx); err != nil { if err := c.scheduleUpgradeCleanup(ctx); err != nil {
return errwrap.Wrapf("error scheduling upgrade cleanup: {{err}}", err) return errwrap.Wrapf("error scheduling upgrade cleanup: {{err}}", err)
} }

View File

@@ -15,6 +15,8 @@ import (
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/shamir" "github.com/hashicorp/vault/shamir"
"github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
) )
// InitParams keeps the init function from being littered with too many // InitParams keeps the init function from being littered with too many
@@ -23,6 +25,9 @@ type InitParams struct {
BarrierConfig *SealConfig BarrierConfig *SealConfig
RecoveryConfig *SealConfig RecoveryConfig *SealConfig
RootTokenPGPKey string RootTokenPGPKey string
// LegacyShamirSeal should only be used in test code, we don't want to
// give the user a way to create legacy shamir seals.
LegacyShamirSeal bool
} }
// InitResult is used to provide the key parts back after // InitResult is used to provide the key parts back after
@@ -132,6 +137,41 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
barrierConfig := initParams.BarrierConfig barrierConfig := initParams.BarrierConfig
recoveryConfig := initParams.RecoveryConfig recoveryConfig := initParams.RecoveryConfig
// N.B. Although the core is capable of handling situations where some keys
// are stored and some aren't, in practice, replication + HSMs makes this
// extremely hard to reason about, to the point that it will probably never
// be supported. The reason is that each HSM needs to encode the master key
// separately, which means the shares must be generated independently,
// which means both that the shares will be different *AND* there would
// need to be a way to actually allow fetching of the generated keys by
// operators.
if c.SealAccess().StoredKeysSupported() == StoredKeysSupportedGeneric {
if len(barrierConfig.PGPKeys) > 0 {
return nil, fmt.Errorf("PGP keys not supported when storing shares")
}
barrierConfig.SecretShares = 1
barrierConfig.SecretThreshold = 1
if barrierConfig.StoredShares != 1 {
c.Logger().Warn("stored keys supported on init, forcing shares/threshold to 1")
}
}
if initParams.LegacyShamirSeal {
barrierConfig.StoredShares = 0
} else {
barrierConfig.StoredShares = 1
}
if len(barrierConfig.PGPKeys) > 0 && len(barrierConfig.PGPKeys) != barrierConfig.SecretShares {
return nil, fmt.Errorf("incorrect number of PGP keys")
}
if c.SealAccess().RecoveryKeySupported() {
if len(recoveryConfig.PGPKeys) > 0 && len(recoveryConfig.PGPKeys) != recoveryConfig.SecretShares {
return nil, fmt.Errorf("incorrect number of PGP keys for recovery")
}
}
if c.seal.RecoveryKeySupported() { if c.seal.RecoveryKeySupported() {
if recoveryConfig == nil { if recoveryConfig == nil {
return nil, fmt.Errorf("recovery configuration must be supplied") return nil, fmt.Errorf("recovery configuration must be supplied")
@@ -201,24 +241,34 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
return nil, errwrap.Wrapf("error initializing seal: {{err}}", err) return nil, errwrap.Wrapf("error initializing seal: {{err}}", err)
} }
barrierKey, barrierUnsealKeys, err := c.generateShares(barrierConfig)
if err != nil {
c.logger.Error("error generating shares", "error", err)
return nil, err
}
initPTCleanup := initPTFunc(c) initPTCleanup := initPTFunc(c)
if initPTCleanup != nil { if initPTCleanup != nil {
defer initPTCleanup() defer initPTCleanup()
} }
barrierKey, barrierKeyShares, err := c.generateShares(barrierConfig)
if err != nil {
c.logger.Error("error generating shares", "error", err)
return nil, err
}
var sealKey []byte
var sealKeyShares [][]byte
if barrierConfig.StoredShares == 1 && c.seal.BarrierType() == seal.Shamir {
sealKey, sealKeyShares, err = c.generateShares(barrierConfig)
if err != nil {
c.logger.Error("error generating shares", "error", err)
return nil, err
}
}
// Initialize the barrier // Initialize the barrier
if err := c.barrier.Initialize(ctx, barrierKey, c.secureRandomReader); err != nil { if err := c.barrier.Initialize(ctx, barrierKey, sealKey, c.secureRandomReader); err != nil {
c.logger.Error("failed to initialize barrier", "error", err) c.logger.Error("failed to initialize barrier", "error", err)
return nil, errwrap.Wrapf("failed to initialize barrier: {{err}}", err) return nil, errwrap.Wrapf("failed to initialize barrier: {{err}}", err)
} }
if c.logger.IsInfo() { if c.logger.IsInfo() {
c.logger.Info("security barrier initialized", "shares", barrierConfig.SecretShares, "threshold", barrierConfig.SecretThreshold) c.logger.Info("security barrier initialized", "stored", barrierConfig.StoredShares, "shares", barrierConfig.SecretShares, "threshold", barrierConfig.SecretThreshold)
} }
// Unseal the barrier // Unseal the barrier
@@ -243,22 +293,34 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
return nil, errwrap.Wrapf("barrier configuration saving failed: {{err}}", err) return nil, errwrap.Wrapf("barrier configuration saving failed: {{err}}", err)
} }
results := &InitResult{
SecretShares: [][]byte{},
}
// If we are storing shares, pop them out of the returned results and push // If we are storing shares, pop them out of the returned results and push
// them through the seal // them through the seal
if barrierConfig.StoredShares > 0 { switch c.seal.StoredKeysSupported() {
var keysToStore [][]byte case StoredKeysSupportedShamirMaster:
for i := 0; i < barrierConfig.StoredShares; i++ { keysToStore := [][]byte{barrierKey}
keysToStore = append(keysToStore, barrierUnsealKeys[0]) if err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(sealKey); err != nil {
barrierUnsealKeys = barrierUnsealKeys[1:] c.logger.Error("failed to set seal key", "error", err)
return nil, errwrap.Wrapf("failed to set seal key: {{err}}", err)
} }
if err := c.seal.SetStoredKeys(ctx, keysToStore); err != nil { if err := c.seal.SetStoredKeys(ctx, keysToStore); err != nil {
c.logger.Error("failed to store keys", "error", err) c.logger.Error("failed to store keys", "error", err)
return nil, errwrap.Wrapf("failed to store keys: {{err}}", err) return nil, errwrap.Wrapf("failed to store keys: {{err}}", err)
} }
} results.SecretShares = sealKeyShares
case StoredKeysSupportedGeneric:
results := &InitResult{ keysToStore := [][]byte{barrierKey}
SecretShares: barrierUnsealKeys, if err := c.seal.SetStoredKeys(ctx, keysToStore); err != nil {
c.logger.Error("failed to store keys", "error", err)
return nil, errwrap.Wrapf("failed to store keys: {{err}}", err)
}
default:
// We don't support initializing an old-style Shamir seal anymore, so
// this case is only reachable by tests.
results.SecretShares = barrierKeyShares
} }
// Perform initial setup // Perform initial setup
@@ -345,7 +407,7 @@ func (c *Core) UnsealWithStoredKeys(ctx context.Context) error {
c.unsealWithStoredKeysLock.Lock() c.unsealWithStoredKeysLock.Lock()
defer c.unsealWithStoredKeysLock.Unlock() defer c.unsealWithStoredKeysLock.Unlock()
if !c.seal.StoredKeysSupported() { if c.seal.BarrierType() == seal.Shamir {
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package vault
import ( import (
"context" "context"
"github.com/hashicorp/vault/vault/seal"
"reflect" "reflect"
"testing" "testing"
@@ -80,7 +81,7 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if len(res.SecretShares) != (barrierConf.SecretShares - barrierConf.StoredShares) { if c.seal.BarrierType() == seal.Shamir && len(res.SecretShares) != barrierConf.SecretShares {
t.Fatalf("Bad: got\n%#v\nexpected conf matching\n%#v\n", *res, *barrierConf) t.Fatalf("Bad: got\n%#v\nexpected conf matching\n%#v\n", *res, *barrierConf)
} }
if recoveryConf != nil { if recoveryConf != nil {

View File

@@ -4,14 +4,13 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/subtle" "crypto/subtle"
"encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"
@@ -169,30 +168,37 @@ func (c *Core) RekeyInit(config *SealConfig, recovery bool) logical.HTTPCodedErr
// BarrierRekeyInit is used to initialize the rekey settings for the barrier key // BarrierRekeyInit is used to initialize the rekey settings for the barrier key
func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError { func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
if c.seal.StoredKeysSupported() { switch c.seal.BarrierType() {
c.logger.Warn("stored keys supported, forcing rekey shares/threshold to 1") 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.SecretShares = 1
config.SecretThreshold = 1 config.SecretThreshold = 1
config.StoredShares = 1
}
if config.StoredShares > 0 {
if !c.seal.StoredKeysSupported() {
return logical.CodedError(http.StatusBadRequest, "storing keys not supported by barrier seal")
}
if len(config.PGPKeys) > 0 { if len(config.PGPKeys) > 0 {
return logical.CodedError(http.StatusBadRequest, "PGP key encryption not supported when using stored keys") return logical.CodedError(http.StatusBadRequest, "PGP key encryption not supported when using stored keys")
} }
if config.Backup { if config.Backup {
return logical.CodedError(http.StatusBadRequest, "key backup not supported when using stored keys") return logical.CodedError(http.StatusBadRequest, "key backup not supported when using stored keys")
} }
}
if c.seal.RecoveryKeySupported() { if c.seal.RecoveryKeySupported() {
if config.VerificationRequired { if config.VerificationRequired {
return logical.CodedError(http.StatusBadRequest, "requiring verification not supported when rekeying the barrier key with recovery keys") 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")
} }
c.logger.Debug("using recovery seal configuration to rekey barrier key")
} }
// Check if the seal configuration is valid // Check if the seal configuration is valid
@@ -326,7 +332,7 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
var existingConfig *SealConfig var existingConfig *SealConfig
var err error var err error
var useRecovery bool // Determines whether recovery key is being used to rekey the master key var useRecovery bool // Determines whether recovery key is being used to rekey the master key
if c.seal.StoredKeysSupported() && c.seal.RecoveryKeySupported() { if c.seal.StoredKeysSupported() == StoredKeysSupportedGeneric && c.seal.RecoveryKeySupported() {
existingConfig, err = c.seal.RecoveryConfig(ctx) existingConfig, err = c.seal.RecoveryConfig(ctx)
useRecovery = true useRecovery = true
} else { } else {
@@ -384,20 +390,41 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
} }
} }
if useRecovery { switch {
case useRecovery:
if err := c.seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil { if err := c.seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
c.logger.Error("rekey recovery key verification failed", "error", err) 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()) return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("recovery key verification failed: {{err}}", err).Error())
} }
} else { 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 { if err := c.barrier.VerifyMaster(recoveredKey); err != nil {
c.logger.Error("master key verification failed", "error", err) c.logger.Error("master key verification failed", "error", err)
return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("master key verification failed: {{err}}", err).Error()) return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("master key verification failed: {{err}}", err).Error())
} }
} }
// Generate a new master key // Generate a new key: for AutoUnseal, this is a new master key; for Shamir,
newMasterKey, err := c.barrier.GenerateKey(c.secureRandomReader) // 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 { if err != nil {
c.logger.Error("failed to generate master key", "error", err) 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()) return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("master key generation failed: {{err}}", err).Error())
@@ -406,27 +433,19 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
results := &RekeyResult{ results := &RekeyResult{
Backup: c.barrierRekeyConfig.Backup, Backup: c.barrierRekeyConfig.Backup,
} }
// Set result.SecretShares to the master key if only a single key if c.seal.StoredKeysSupported() != StoredKeysSupportedGeneric {
// part is used -- no Shamir split required. // Set result.SecretShares to the new key itself if only a single key
if c.barrierRekeyConfig.SecretShares == 1 { // part is used -- no Shamir split required.
results.SecretShares = append(results.SecretShares, newMasterKey) if c.barrierRekeyConfig.SecretShares == 1 {
} else { results.SecretShares = append(results.SecretShares, newKey)
// Split the master key using the Shamir algorithm } else {
shares, err := shamir.Split(newMasterKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold) // Split the new key using the Shamir algorithm
if err != nil { shares, err := shamir.Split(newKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
c.logger.Error("failed to generate shares", "error", err) if err != nil {
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate shares: {{err}}", err).Error()) 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 }
} results.SecretShares = shares
// If we are storing any shares, add them to the shares to store and remove
// from the returned keys
var keysToStore [][]byte
if c.seal.StoredKeysSupported() && c.barrierRekeyConfig.StoredShares > 0 {
for i := 0; i < c.barrierRekeyConfig.StoredShares; i++ {
keysToStore = append(keysToStore, results.SecretShares[0])
results.SecretShares = results.SecretShares[1:]
} }
} }
@@ -473,13 +492,6 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
} }
} }
if keysToStore != nil {
if err := c.seal.SetStoredKeys(ctx, keysToStore); err != nil {
c.logger.Error("failed to store keys", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to store keys: {{err}}", err).Error())
}
}
// If we are requiring validation, return now; otherwise rekey the barrier // If we are requiring validation, return now; otherwise rekey the barrier
if c.barrierRekeyConfig.VerificationRequired { if c.barrierRekeyConfig.VerificationRequired {
nonce, err := uuid.GenerateUUID() nonce, err := uuid.GenerateUUID()
@@ -488,14 +500,14 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate verification nonce: {{err}}", err).Error()) return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate verification nonce: {{err}}", err).Error())
} }
c.barrierRekeyConfig.VerificationNonce = nonce c.barrierRekeyConfig.VerificationNonce = nonce
c.barrierRekeyConfig.VerificationKey = newMasterKey c.barrierRekeyConfig.VerificationKey = newKey
results.VerificationRequired = true results.VerificationRequired = true
results.VerificationNonce = nonce results.VerificationNonce = nonce
return results, nil return results, nil
} }
if err := c.performBarrierRekey(ctx, newMasterKey); err != 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()) return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to perform barrier rekey: {{err}}", err).Error())
} }
@@ -503,14 +515,52 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
return results, nil return results, nil
} }
func (c *Core) performBarrierRekey(ctx context.Context, newMasterKey []byte) logical.HTTPCodedError { 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 // Rekey the barrier
if err := c.barrier.Rekey(ctx, newMasterKey); err != nil { if err := c.barrier.Rekey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to rekey barrier", "error", err) c.logger.Error("failed to rekey barrier", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to rekey barrier: {{err}}", err).Error()) return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to rekey barrier: {{err}}", err).Error())
} }
if c.logger.IsInfo() { if c.logger.IsInfo() {
c.logger.Info("security barrier rekeyed", "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold) 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 c.barrierRekeyConfig.VerificationKey = nil
@@ -531,14 +581,6 @@ func (c *Core) performBarrierRekey(ctx context.Context, newMasterKey []byte) log
} }
c.barrierRekeyConfig.RekeyProgress = nil c.barrierRekeyConfig.RekeyProgress = nil
if c.seal.BarrierType() == seal.Shamir {
_, err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
"key": base64.StdEncoding.EncodeToString(newMasterKey),
})
if err != nil {
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to update seal access: {{err}}", err).Error())
}
}
return nil return nil
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"testing" "testing"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
@@ -14,21 +15,28 @@ import (
) )
func TestCore_Rekey_Lifecycle(t *testing.T) { func TestCore_Rekey_Lifecycle(t *testing.T) {
bc, _ := TestSealDefConfigs() bc := &SealConfig{
bc.SecretShares = 1 SecretShares: 1,
bc.SecretThreshold = 1 SecretThreshold: 1,
bc.StoredShares = 0 StoredShares: 1,
}
c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil) c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
if len(masterKeys) != 1 { if len(masterKeys) != 1 {
t.Fatalf("expected %d keys, got %d", bc.SecretShares-bc.StoredShares, len(masterKeys)) t.Fatalf("expected %d keys, got %d", bc.SecretShares-bc.StoredShares, len(masterKeys))
} }
testCore_Rekey_Lifecycle_Common(t, c, masterKeys, false) testCore_Rekey_Lifecycle_Common(t, c, false)
} }
func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, masterKeys [][]byte, recovery bool) { func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
min, _ := c.barrier.KeyLength()
// Verify update not allowed // Verify update not allowed
if _, err := c.RekeyUpdate(context.Background(), masterKeys[0], "", recovery); err == nil { _, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery)
t.Fatalf("no rekey should be in progress") expected := "no barrier rekey in progress"
if recovery {
expected = "no recovery rekey in progress"
}
if err == nil || !strings.Contains(err.Error(), expected) {
t.Fatalf("no rekey should be in progress, err: %v", err)
} }
// Should be no progress // Should be no progress
@@ -130,10 +138,10 @@ func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
} }
func TestCore_Rekey_Update(t *testing.T) { func TestCore_Rekey_Update(t *testing.T) {
bc, _ := TestSealDefConfigs() bc := &SealConfig{
bc.SecretShares = 1 SecretShares: 1,
bc.SecretThreshold = 1 SecretThreshold: 1,
bc.StoredShares = 0 }
c, masterKeys, _, root := TestCoreUnsealedWithConfigs(t, bc, nil) c, masterKeys, _, root := TestCoreUnsealedWithConfigs(t, bc, nil)
testCore_Rekey_Update_Common(t, c, masterKeys, root, false) testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
} }
@@ -181,13 +189,6 @@ func testCore_Rekey_Update_Common(t *testing.T, c *Core, keys [][]byte, root str
if result == nil { if result == nil {
t.Fatal("nil result after update") t.Fatal("nil result after update")
} }
if newConf.StoredShares > 0 {
if len(result.SecretShares) > 0 {
t.Fatal("got secret shares when should have been storing")
}
} else if len(result.SecretShares) != newConf.SecretShares {
t.Fatalf("rekey update error: %#v", result)
}
// Should be no progress // Should be no progress
if _, _, err := c.RekeyProgress(recovery, false); err == nil { if _, _, err := c.RekeyProgress(recovery, false); err == nil {
@@ -321,8 +322,21 @@ func testCore_Rekey_Update_Common(t *testing.T, c *Core, keys [][]byte, root str
} }
} }
func TestCore_Rekey_Legacy(t *testing.T) {
bc := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
c, masterKeys, _, root := TestCoreUnsealedWithConfigSealOpts(t, bc, nil,
&TestSealOpts{StoredKeys: StoredKeysNotSupported})
testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
}
func TestCore_Rekey_Invalid(t *testing.T) { func TestCore_Rekey_Invalid(t *testing.T) {
bc, _ := TestSealDefConfigs() bc := &SealConfig{
SecretShares: 5,
SecretThreshold: 3,
}
bc.StoredShares = 0 bc.StoredShares = 0
bc.SecretShares = 1 bc.SecretShares = 1
bc.SecretThreshold = 1 bc.SecretThreshold = 1
@@ -496,3 +510,25 @@ func TestCore_Rekey_Standby(t *testing.T) {
t.Fatalf("rekey failed") t.Fatalf("rekey failed")
} }
} }
// verifies that if we are using recovery keys to force a
// rekey of a stored-shares barrier that verification is not allowed since
// the keys aren't returned
func TestSysRekey_Verification_Invalid(t *testing.T) {
core, _, _, _ := TestCoreUnsealedWithConfigSealOpts(t,
&SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
&SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
&TestSealOpts{StoredKeys: StoredKeysSupportedGeneric})
err := core.BarrierRekeyInit(&SealConfig{
VerificationRequired: true,
StoredShares: 1,
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "requiring verification not supported") {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@@ -3,17 +3,16 @@ package vault
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/subtle"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sync/atomic" "sync/atomic"
"github.com/golang/protobuf/proto"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/seal" "github.com/hashicorp/vault/vault/seal"
"github.com/keybase/go-crypto/openpgp" "github.com/keybase/go-crypto/openpgp"
"github.com/keybase/go-crypto/openpgp/packet" "github.com/keybase/go-crypto/openpgp/packet"
) )
@@ -50,12 +49,36 @@ const (
RecoveryTypeShamir = "shamir" RecoveryTypeShamir = "shamir"
) )
type StoredKeysSupport int
const (
// The 0 value of StoredKeysSupport is an invalid option
StoredKeysInvalid StoredKeysSupport = iota
StoredKeysNotSupported
StoredKeysSupportedGeneric
StoredKeysSupportedShamirMaster
)
func (s StoredKeysSupport) String() string {
switch s {
case StoredKeysNotSupported:
return "Old-style Shamir"
case StoredKeysSupportedGeneric:
return "AutoUnseal"
case StoredKeysSupportedShamirMaster:
return "New-style Shamir"
default:
return "Invalid StoredKeys type"
}
}
type Seal interface { type Seal interface {
SetCore(*Core) SetCore(*Core)
Init(context.Context) error Init(context.Context) error
Finalize(context.Context) error Finalize(context.Context) error
StoredKeysSupported() bool StoredKeysSupported() StoredKeysSupport
SealWrapable() bool
SetStoredKeys(context.Context, [][]byte) error SetStoredKeys(context.Context, [][]byte) error
GetStoredKeys(context.Context) ([][]byte, error) GetStoredKeys(context.Context) ([][]byte, error)
@@ -77,12 +100,9 @@ type Seal interface {
} }
type defaultSeal struct { type defaultSeal struct {
access seal.Access access seal.Access
config atomic.Value config atomic.Value
core *Core core *Core
PretendToAllowStoredShares bool
PretendToAllowRecoveryKeys bool
PretendRecoveryKey []byte
} }
func NewDefaultSeal(lowLevel seal.Access) Seal { func NewDefaultSeal(lowLevel seal.Access) Seal {
@@ -93,6 +113,10 @@ func NewDefaultSeal(lowLevel seal.Access) Seal {
return ret return ret
} }
func (d *defaultSeal) SealWrapable() bool {
return false
}
func (d *defaultSeal) checkCore() error { func (d *defaultSeal) checkCore() error {
if d.core == nil { if d.core == nil {
return fmt.Errorf("seal does not have a core set") return fmt.Errorf("seal does not have a core set")
@@ -124,25 +148,61 @@ func (d *defaultSeal) BarrierType() string {
return seal.Shamir return seal.Shamir
} }
func (d *defaultSeal) StoredKeysSupported() bool { func (d *defaultSeal) StoredKeysSupported() StoredKeysSupport {
return d.PretendToAllowStoredShares isLegacy, err := d.LegacySeal()
if err != nil {
if d.core != nil && d.core.logger != nil {
d.core.logger.Error("no seal config found, can't determine if legacy or new-style shamir")
}
return StoredKeysInvalid
}
switch {
case isLegacy:
return StoredKeysNotSupported
default:
return StoredKeysSupportedShamirMaster
}
} }
func (d *defaultSeal) RecoveryKeySupported() bool { func (d *defaultSeal) RecoveryKeySupported() bool {
return d.PretendToAllowRecoveryKeys return false
} }
func (d *defaultSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error { func (d *defaultSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error {
return fmt.Errorf("stored keys are not supported") isLegacy, err := d.LegacySeal()
if err != nil {
return err
}
if isLegacy {
return fmt.Errorf("stored keys are not supported")
}
return writeStoredKeys(ctx, d.core.physical, d.access, keys)
}
func (d *defaultSeal) LegacySeal() (bool, error) {
cfg := d.config.Load().(*SealConfig)
if cfg == nil {
return false, fmt.Errorf("no seal config found, can't determine if legacy or new-style shamir")
}
return cfg.StoredShares == 0, nil
} }
func (d *defaultSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) { func (d *defaultSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
return nil, fmt.Errorf("stored keys are not supported") isLegacy, err := d.LegacySeal()
if err != nil {
return nil, err
}
if isLegacy {
return nil, fmt.Errorf("stored keys are not supported")
}
keys, err := readStoredKeys(ctx, d.core.physical, d.access)
return keys, err
} }
func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) { func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
if d.config.Load().(*SealConfig) != nil { cfg := d.config.Load().(*SealConfig)
return d.config.Load().(*SealConfig).Clone(), nil if cfg != nil {
return cfg.Clone(), nil
} }
if err := d.checkCore(); err != nil { if err := d.checkCore(); err != nil {
@@ -204,7 +264,7 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
config.Type = d.BarrierType() config.Type = d.BarrierType()
// If we are doing a raft unseal we do not want to persit the barrier config // If we are doing a raft unseal we do not want to persist the barrier config
// because storage isn't setup yet. // because storage isn't setup yet.
if d.core.isRaftUnseal() { if d.core.isRaftUnseal() {
d.config.Store(config.Clone()) d.config.Store(config.Clone())
@@ -238,34 +298,18 @@ func (d *defaultSeal) SetCachedBarrierConfig(config *SealConfig) {
} }
func (d *defaultSeal) RecoveryType() string { func (d *defaultSeal) RecoveryType() string {
if d.PretendToAllowRecoveryKeys {
return RecoveryTypeShamir
}
return RecoveryTypeUnsupported return RecoveryTypeUnsupported
} }
func (d *defaultSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) { func (d *defaultSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
if d.PretendToAllowRecoveryKeys {
return &SealConfig{
SecretShares: 5,
SecretThreshold: 3,
}, nil
}
return nil, fmt.Errorf("recovery not supported") return nil, fmt.Errorf("recovery not supported")
} }
func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) { func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
if d.PretendToAllowRecoveryKeys {
return d.PretendRecoveryKey, nil
}
return nil, fmt.Errorf("recovery not supported") return nil, fmt.Errorf("recovery not supported")
} }
func (d *defaultSeal) SetRecoveryConfig(ctx context.Context, config *SealConfig) error { func (d *defaultSeal) SetRecoveryConfig(ctx context.Context, config *SealConfig) error {
if d.PretendToAllowRecoveryKeys {
return nil
}
return fmt.Errorf("recovery not supported") return fmt.Errorf("recovery not supported")
} }
@@ -273,20 +317,10 @@ func (d *defaultSeal) SetCachedRecoveryConfig(config *SealConfig) {
} }
func (d *defaultSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error { func (d *defaultSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
if d.PretendToAllowRecoveryKeys {
if subtle.ConstantTimeCompare(key, d.PretendRecoveryKey) == 1 {
return nil
}
return fmt.Errorf("mismatch")
}
return fmt.Errorf("recovery not supported") return fmt.Errorf("recovery not supported")
} }
func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error { func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
if d.PretendToAllowRecoveryKeys {
d.PretendRecoveryKey = key
return nil
}
return fmt.Errorf("recovery not supported") return fmt.Errorf("recovery not supported")
} }
@@ -318,7 +352,7 @@ type SealConfig struct {
// should be stored at coreUnsealKeysBackupPath after successful rekeying. // should be stored at coreUnsealKeysBackupPath after successful rekeying.
Backup bool `json:"backup" mapstructure:"backup"` Backup bool `json:"backup" mapstructure:"backup"`
// How many keys to store, for seals that support storage. // How many keys to store, for seals that support storage. Always 0 or 1.
StoredShares int `json:"stored_shares" mapstructure:"stored_shares"` StoredShares int `json:"stored_shares" mapstructure:"stored_shares"`
// Stores the progress of the rekey operation (key shares) // Stores the progress of the rekey operation (key shares)
@@ -361,10 +395,10 @@ func (s *SealConfig) Validate() error {
if s.SecretThreshold > s.SecretShares { if s.SecretThreshold > s.SecretShares {
return fmt.Errorf("threshold cannot be larger than shares") return fmt.Errorf("threshold cannot be larger than shares")
} }
if s.StoredShares > s.SecretShares { if s.StoredShares > 1 {
return fmt.Errorf("stored keys cannot be larger than shares") return fmt.Errorf("stored keys cannot be larger than 1")
} }
if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares-s.StoredShares { if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares {
return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares") return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares")
} }
if len(s.PGPKeys) > 0 { if len(s.PGPKeys) > 0 {
@@ -403,3 +437,71 @@ func (s *SealConfig) Clone() *SealConfig {
} }
return ret return ret
} }
func writeStoredKeys(ctx context.Context, storage physical.Backend, encryptor seal.Encryptor, keys [][]byte) error {
if keys == nil {
return fmt.Errorf("keys were nil")
}
if len(keys) == 0 {
return fmt.Errorf("no keys provided")
}
buf, err := json.Marshal(keys)
if err != nil {
return errwrap.Wrapf("failed to encode keys for storage: {{err}}", err)
}
// Encrypt and marshal the keys
blobInfo, err := encryptor.Encrypt(ctx, buf)
if err != nil {
return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
}
value, err := proto.Marshal(blobInfo)
if err != nil {
return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
}
// Store the seal configuration.
pe := &physical.Entry{
Key: StoredBarrierKeysPath,
Value: value,
}
if err := storage.Put(ctx, pe); err != nil {
return errwrap.Wrapf("failed to write keys to storage: {{err}}", err)
}
return nil
}
func readStoredKeys(ctx context.Context, storage physical.Backend, encryptor seal.Encryptor) ([][]byte, error) {
pe, err := storage.Get(ctx, StoredBarrierKeysPath)
if err != nil {
return nil, errwrap.Wrapf("failed to fetch stored keys: {{err}}", err)
}
// This is not strictly an error; we may not have any stored keys, for
// instance, if we're not initialized
if pe == nil {
return nil, nil
}
blobInfo := &physical.EncryptedBlobInfo{}
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
return nil, errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
}
pt, err := encryptor.Decrypt(ctx, blobInfo)
if err != nil {
return nil, errwrap.Wrapf("failed to decrypt encrypted stored keys: {{err}}", err)
}
// Decode the barrier entry
var keys [][]byte
if err := json.Unmarshal(pt, &keys); err != nil {
return nil, fmt.Errorf("failed to decode stored keys: %v", err)
}
return keys, nil
}

View File

@@ -22,6 +22,11 @@ const (
HSMAutoDeprecated = "hsm-auto" HSMAutoDeprecated = "hsm-auto"
) )
type Encryptor interface {
Encrypt(context.Context, []byte) (*physical.EncryptedBlobInfo, error)
Decrypt(context.Context, *physical.EncryptedBlobInfo) ([]byte, error)
}
// Access is the embedded implementation of autoSeal that contains logic // Access is the embedded implementation of autoSeal that contains logic
// specific to encrypting and decrypting data, or in this case keys. // specific to encrypting and decrypting data, or in this case keys.
type Access interface { type Access interface {
@@ -31,6 +36,5 @@ type Access interface {
Init(context.Context) error Init(context.Context) error
Finalize(context.Context) error Finalize(context.Context) error
Encrypt(context.Context, []byte) (*physical.EncryptedBlobInfo, error) Encryptor
Decrypt(context.Context, *physical.EncryptedBlobInfo) ([]byte, error)
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
@@ -17,6 +16,7 @@ import (
// ShamirSeal implements the seal.Access interface for Shamir unseal // ShamirSeal implements the seal.Access interface for Shamir unseal
type ShamirSeal struct { type ShamirSeal struct {
logger log.Logger logger log.Logger
key []byte
aead cipher.AEAD aead cipher.AEAD
} }
@@ -31,35 +31,24 @@ func NewSeal(logger log.Logger) *ShamirSeal {
return seal return seal
} }
// SetConfig sets the fields on the ShamirSeal object based on func (s *ShamirSeal) GetKey() []byte {
// values from the config parameter. return s.key
func (s *ShamirSeal) SetConfig(config map[string]string) (map[string]string, error) { }
// Map that holds non-sensitive configuration info
sealInfo := make(map[string]string)
if config == nil || config["key"] == "" {
return sealInfo, nil
}
keyB64 := config["key"]
key, err := base64.StdEncoding.DecodeString(keyB64)
if err != nil {
return sealInfo, err
}
func (s *ShamirSeal) SetKey(key []byte) error {
aesCipher, err := aes.NewCipher(key) aesCipher, err := aes.NewCipher(key)
if err != nil { if err != nil {
return sealInfo, err return err
} }
aead, err := cipher.NewGCM(aesCipher) aead, err := cipher.NewGCM(aesCipher)
if err != nil { if err != nil {
return sealInfo, err return err
} }
s.key = key
s.aead = aead s.aead = aead
return nil
return sealInfo, nil
} }
// Init is called during core.Initialize. No-op at the moment. // Init is called during core.Initialize. No-op at the moment.

View File

@@ -2,7 +2,6 @@ package vault
import ( import (
"context" "context"
"fmt"
) )
// SealAccess is a wrapper around Seal that exposes accessor methods // SealAccess is a wrapper around Seal that exposes accessor methods
@@ -16,7 +15,7 @@ func NewSealAccess(seal Seal) *SealAccess {
return &SealAccess{seal: seal} return &SealAccess{seal: seal}
} }
func (s *SealAccess) StoredKeysSupported() bool { func (s *SealAccess) StoredKeysSupported() StoredKeysSupport {
return s.seal.StoredKeysSupported() return s.seal.StoredKeysSupported()
} }
@@ -46,22 +45,3 @@ func (s *SealAccess) ClearCaches(ctx context.Context) {
s.seal.SetRecoveryConfig(ctx, nil) s.seal.SetRecoveryConfig(ctx, nil)
} }
} }
type SealAccessTestingParams struct {
PretendToAllowStoredShares bool
PretendToAllowRecoveryKeys bool
PretendRecoveryKey []byte
}
func (s *SealAccess) SetTestingParams(params *SealAccessTestingParams) error {
d, ok := s.seal.(*defaultSeal)
if !ok {
return fmt.Errorf("not a defaultseal")
}
d.PretendToAllowRecoveryKeys = params.PretendToAllowRecoveryKeys
d.PretendToAllowStoredShares = params.PretendToAllowStoredShares
if params.PretendRecoveryKey != nil {
d.PretendRecoveryKey = params.PretendRecoveryKey
}
return nil
}

View File

@@ -42,6 +42,10 @@ func NewAutoSeal(lowLevel seal.Access) *autoSeal {
return ret return ret
} }
func (d *autoSeal) SealWrapable() bool {
return true
}
func (d *autoSeal) GetAccess() seal.Access { func (d *autoSeal) GetAccess() seal.Access {
return d.Access return d.Access
} }
@@ -73,8 +77,8 @@ func (d *autoSeal) BarrierType() string {
return d.SealType() return d.SealType()
} }
func (d *autoSeal) StoredKeysSupported() bool { func (d *autoSeal) StoredKeysSupported() StoredKeysSupport {
return true return StoredKeysSupportedGeneric
} }
func (d *autoSeal) RecoveryKeySupported() bool { func (d *autoSeal) RecoveryKeySupported() bool {
@@ -84,73 +88,13 @@ func (d *autoSeal) RecoveryKeySupported() bool {
// SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry // SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry
// does not need to be seal wrapped in this case. // does not need to be seal wrapped in this case.
func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error { func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error {
if keys == nil { return writeStoredKeys(ctx, d.core.physical, d, keys)
return fmt.Errorf("keys were nil")
}
if len(keys) == 0 {
return fmt.Errorf("no keys provided")
}
buf, err := json.Marshal(keys)
if err != nil {
return errwrap.Wrapf("failed to encode keys for storage: {{err}}", err)
}
// Encrypt and marshal the keys
blobInfo, err := d.Encrypt(ctx, buf)
if err != nil {
return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
}
value, err := proto.Marshal(blobInfo)
if err != nil {
return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
}
// Store the seal configuration.
pe := &physical.Entry{
Key: StoredBarrierKeysPath,
Value: value,
}
if err := d.core.physical.Put(ctx, pe); err != nil {
return errwrap.Wrapf("failed to write keys to storage: {{err}}", err)
}
return nil
} }
// GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the // GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the
// autoseal. // autoseal.
func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) { func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
pe, err := d.core.physical.Get(ctx, StoredBarrierKeysPath) return readStoredKeys(ctx, d.core.physical, d)
if err != nil {
return nil, errwrap.Wrapf("failed to fetch stored keys: {{err}}", err)
}
// This is not strictly an error; we may not have any stored keys, for
// instance, if we're not initialized
if pe == nil {
return nil, nil
}
blobInfo := &physical.EncryptedBlobInfo{}
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
return nil, errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
}
pt, err := d.Decrypt(ctx, blobInfo)
if err != nil {
return nil, errwrap.Wrapf("failed to decrypt encrypted stored keys: {{err}}", err)
}
// Decode the barrier entry
var keys [][]byte
if err := json.Unmarshal(pt, &keys); err != nil {
return nil, fmt.Errorf("failed to decode stored keys: %v", err)
}
return keys, nil
} }
func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error { func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error {

View File

@@ -6,13 +6,16 @@ import (
"testing" "testing"
) )
// TestDefaultSeal_Config exercises Shamir SetBarrierConfig and BarrierConfig.
// Note that this is a little questionable, because we're doing an init and
// unseal, then changing the barrier config using an internal function instead
// of an API. In other words if your change break this test, it might be more
// the test's fault than your changes.
func TestDefaultSeal_Config(t *testing.T) { func TestDefaultSeal_Config(t *testing.T) {
bc, _ := TestSealDefConfigs() bc := &SealConfig{
// Change these to non-default values to ensure we are seeing the real SecretShares: 4,
// config we set SecretThreshold: 2,
bc.SecretShares = 4 }
bc.SecretThreshold = 2
core, _, _ := TestCoreUnsealed(t) core, _, _ := TestCoreUnsealed(t)
defSeal := NewDefaultSeal(nil) defSeal := NewDefaultSeal(nil)

View File

@@ -7,32 +7,29 @@ import (
testing "github.com/mitchellh/go-testing-interface" testing "github.com/mitchellh/go-testing-interface"
) )
var (
TestCoreUnsealedWithConfigs = testCoreUnsealedWithConfigs
TestSealDefConfigs = testSealDefConfigs
)
type TestSealOpts struct { type TestSealOpts struct {
StoredKeysDisabled bool Logger log.Logger
RecoveryKeysDisabled bool StoredKeys StoredKeysSupport
Secret []byte Secret []byte
Logger log.Logger
} }
func testCoreUnsealedWithConfigs(t testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) { func TestCoreUnsealedWithConfigs(t testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) {
t.Helper() t.Helper()
var opts *TestSealOpts opts := &TestSealOpts{}
if recoveryConf == nil { if recoveryConf == nil {
opts = &TestSealOpts{ opts.StoredKeys = StoredKeysSupportedShamirMaster
StoredKeysDisabled: true,
RecoveryKeysDisabled: true,
}
} }
seal := NewTestSeal(t, opts) return TestCoreUnsealedWithConfigSealOpts(t, barrierConf, recoveryConf, opts)
}
func TestCoreUnsealedWithConfigSealOpts(t testing.T, barrierConf, recoveryConf *SealConfig, sealOpts *TestSealOpts) (*Core, [][]byte, [][]byte, string) {
t.Helper()
seal := NewTestSeal(t, sealOpts)
core := TestCoreWithSeal(t, seal, false) core := TestCoreWithSeal(t, seal, false)
result, err := core.Initialize(context.Background(), &InitParams{ result, err := core.Initialize(context.Background(), &InitParams{
BarrierConfig: barrierConf, BarrierConfig: barrierConf,
RecoveryConfig: recoveryConf, RecoveryConfig: recoveryConf,
LegacyShamirSeal: sealOpts.StoredKeys == StoredKeysNotSupported,
}) })
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@@ -55,39 +52,3 @@ func testCoreUnsealedWithConfigs(t testing.T, barrierConf, recoveryConf *SealCon
return core, result.SecretShares, result.RecoveryShares, result.RootToken return core, result.SecretShares, result.RecoveryShares, result.RootToken
} }
func testSealDefConfigs() (*SealConfig, *SealConfig) {
return &SealConfig{
SecretShares: 5,
SecretThreshold: 3,
}, nil
}
func TestCoreUnsealedWithConfigSealOpts(t testing.T, barrierConf, recoveryConf *SealConfig, sealOpts *TestSealOpts) (*Core, [][]byte, [][]byte, string) {
seal := NewTestSeal(t, sealOpts)
core := TestCoreWithSeal(t, seal, false)
result, err := core.Initialize(context.Background(), &InitParams{
BarrierConfig: barrierConf,
RecoveryConfig: recoveryConf,
})
if err != nil {
t.Fatalf("err: %s", err)
}
err = core.UnsealWithStoredKeys(context.Background())
if err != nil {
t.Fatalf("err: %s", err)
}
if core.Sealed() {
for _, key := range result.SecretShares {
if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
}
if core.Sealed() {
t.Fatal("should not be sealed")
}
}
return core, result.SecretShares, result.RecoveryShares, result.RootToken
}

View File

@@ -1,17 +1,41 @@
// +build !enterprise
package vault package vault
import ( import (
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir" shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
testing "github.com/mitchellh/go-testing-interface" testing "github.com/mitchellh/go-testing-interface"
) )
func NewTestSeal(t testing.T, opts *TestSealOpts) Seal { func NewTestSeal(t testing.T, opts *TestSealOpts) Seal {
var logger hclog.Logger t.Helper()
if opts != nil { if opts == nil {
logger = opts.Logger opts = &TestSealOpts{}
}
if opts.Logger == nil {
opts.Logger = logging.NewVaultLogger(hclog.Debug)
}
switch opts.StoredKeys {
case StoredKeysSupportedShamirMaster:
newSeal := NewDefaultSeal(shamirseal.NewSeal(opts.Logger))
// Need StoredShares set or this will look like a legacy shamir seal.
newSeal.SetCachedBarrierConfig(&SealConfig{
StoredShares: 1,
SecretThreshold: 1,
SecretShares: 1,
})
return newSeal
case StoredKeysNotSupported:
newSeal := NewDefaultSeal(shamirseal.NewSeal(opts.Logger))
newSeal.SetCachedBarrierConfig(&SealConfig{
StoredShares: 0,
SecretThreshold: 1,
SecretShares: 1,
})
return newSeal
default:
return NewAutoSeal(seal.NewTestSeal(opts.Secret))
} }
return NewDefaultSeal(shamirseal.NewSeal(logger))
} }

View File

@@ -266,9 +266,11 @@ func TestCoreInitClusterWrapperSetup(t testing.T, core *Core, handler http.Handl
SecretThreshold: 3, SecretThreshold: 3,
} }
// If we support storing barrier keys, then set that to equal the min threshold to unseal switch core.seal.StoredKeysSupported() {
if core.seal.StoredKeysSupported() { case StoredKeysNotSupported:
barrierConfig.StoredShares = barrierConfig.SecretThreshold barrierConfig.StoredShares = 0
default:
barrierConfig.StoredShares = 1
} }
recoveryConfig := &SealConfig{ recoveryConfig := &SealConfig{
@@ -276,10 +278,14 @@ func TestCoreInitClusterWrapperSetup(t testing.T, core *Core, handler http.Handl
SecretThreshold: 3, SecretThreshold: 3,
} }
result, err := core.Initialize(context.Background(), &InitParams{ initParams := &InitParams{
BarrierConfig: barrierConfig, BarrierConfig: barrierConfig,
RecoveryConfig: recoveryConfig, RecoveryConfig: recoveryConfig,
}) }
if core.seal.StoredKeysSupported() == StoredKeysNotSupported {
initParams.LegacyShamirSeal = true
}
result, err := core.Initialize(context.Background(), initParams)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@@ -822,6 +828,7 @@ func (c *TestCluster) Start() {
// UnsealCores uses the cluster barrier keys to unseal the test cluster cores // UnsealCores uses the cluster barrier keys to unseal the test cluster cores
func (c *TestCluster) UnsealCores(t testing.T) { func (c *TestCluster) UnsealCores(t testing.T) {
t.Helper()
if err := c.UnsealCoresWithError(); err != nil { if err := c.UnsealCoresWithError(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -833,7 +840,7 @@ func (c *TestCluster) UnsealCoresWithError() error {
// Unseal first core // Unseal first core
for _, key := range c.BarrierKeys { for _, key := range c.BarrierKeys {
if _, err := c.Cores[0].Unseal(TestKeyCopy(key)); err != nil { if _, err := c.Cores[0].Unseal(TestKeyCopy(key)); err != nil {
return fmt.Errorf("unseal err: %s", err) return fmt.Errorf("unseal core %d err: %s", 0, err)
} }
} }
@@ -850,7 +857,7 @@ func (c *TestCluster) UnsealCoresWithError() error {
for i := 1; i < numCores; i++ { for i := 1; i < numCores; i++ {
for _, key := range c.BarrierKeys { for _, key := range c.BarrierKeys {
if _, err := c.Cores[i].Core.Unseal(TestKeyCopy(key)); err != nil { if _, err := c.Cores[i].Core.Unseal(TestKeyCopy(key)); err != nil {
return fmt.Errorf("unseal err: %s", err) return fmt.Errorf("unseal core %d err: %s", i, err)
} }
} }
} }
@@ -1630,9 +1637,15 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
t.Fatal(err) t.Fatal(err)
} }
cfg, err := cores[0].seal.BarrierConfig(ctx)
if err != nil {
t.Fatal(err)
}
// Unseal other cores unless otherwise specified // Unseal other cores unless otherwise specified
if (opts == nil || !opts.KeepStandbysSealed) && numCores > 1 { if (opts == nil || !opts.KeepStandbysSealed) && numCores > 1 {
for i := 1; i < numCores; i++ { for i := 1; i < numCores; i++ {
cores[i].seal.SetCachedBarrierConfig(cfg)
for _, key := range bKeys { for _, key := range bKeys {
if _, err := cores[i].Unseal(TestKeyCopy(key)); err != nil { if _, err := cores[i].Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err) t.Fatalf("unseal err: %s", err)