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 474a2a26f3
commit 1c98152fa0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
})
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+(.+)`)

View File

@ -303,7 +303,7 @@ func TestOperatorInitCommand_Run(t *testing.T) {
"-root-token-pgp-key", pubFiles[0],
})
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+: (.+)`)

View File

@ -20,7 +20,7 @@ import (
)
func TestSealMigration(t *testing.T) {
logger := logging.NewVaultLogger(hclog.Trace)
logger := logging.NewVaultLogger(hclog.Trace).Named(t.Name())
phys, err := physInmem.NewInmem(nil, logger)
if err != nil {
t.Fatal(err)
@ -47,8 +47,8 @@ func TestSealMigration(t *testing.T) {
var keys []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.Start()
defer cluster.Cleanup()
@ -73,9 +73,9 @@ func TestSealMigration(t *testing.T) {
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.Start()
defer cluster.Cleanup()
@ -103,8 +103,9 @@ func TestSealMigration(t *testing.T) {
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.Start()
defer cluster.Cleanup()
@ -147,8 +148,9 @@ func TestSealMigration(t *testing.T) {
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
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
@ -202,8 +204,9 @@ func TestSealMigration(t *testing.T) {
altTestSeal.Type = "test-alternate"
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
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
@ -239,9 +242,9 @@ func TestSealMigration(t *testing.T) {
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
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
@ -282,12 +285,9 @@ func TestSealMigration(t *testing.T) {
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
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
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
}
@ -1836,7 +1836,7 @@ func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig
}
// Handle unseal with stored keys
if core.SealAccess().StoredKeysSupported() {
if core.SealAccess().StoredKeysSupported() == vault.StoredKeysSupportedGeneric {
err := core.UnsealWithStoredKeys(ctx)
if err != nil {
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")
}
// 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.
existSeal = unwrapSeal
newSeal = barrierSeal

View File

@ -131,18 +131,32 @@ func EnsureCoreSealed(t testing.T, core *vault.TestClusterCore) {
func EnsureCoresUnsealed(t testing.T, c *vault.TestCluster) {
t.Helper()
for _, core := range c.Cores {
EnsureCoreUnsealed(t, c, core)
for i, core := range c.Cores {
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() {
return
return nil
}
core.SealAccess().ClearCaches(context.Background())
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
t.Fatal(err)
return err
}
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
// and then this fails for DR secondaries so check again
if core.Sealed() {
t.Fatal(err)
return err
} else {
return nil
}
break
}
if statusResp == nil {
t.Fatal("nil status response during unseal")
return fmt.Errorf("nil status response during unseal")
}
if !statusResp.Sealed {
break
}
}
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) {
@ -270,10 +286,16 @@ func WaitForActiveNode(t testing.T, cluster *vault.TestCluster) *vault.TestClust
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
init, err := client.Sys().RekeyInit(&api.RekeyInitRequest{
initFunc := client.Sys().RekeyInit
if recovery {
initFunc = client.Sys().RekeyRecoveryKeyInit
}
init, err := initFunc(&api.RekeyInitRequest{
SecretShares: 5,
SecretThreshold: 3,
})
@ -282,8 +304,17 @@ func RekeyCluster(t testing.T, cluster *vault.TestCluster) {
}
var statusResp *api.RekeyUpdateResponse
for j := 0; j < len(cluster.BarrierKeys); j++ {
statusResp, err = client.Sys().RekeyUpdate(base64.StdEncoding.EncodeToString(cluster.BarrierKeys[j]), init.Nonce)
var keys = cluster.BarrierKeys
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 {
t.Fatal(err)
}
@ -294,20 +325,23 @@ func RekeyCluster(t testing.T, cluster *vault.TestCluster) {
break
}
}
cluster.Logger.Info("cluster rekeyed", "recovery", recovery)
if cluster.Cores[0].Core.SealAccess().RecoveryKeySupported() && !recovery {
return nil
}
if len(statusResp.KeysB64) != 5 {
t.Fatal("wrong number of keys")
}
newBarrierKeys := make([][]byte, 5)
newKeys := make([][]byte, 5)
for i, key := range statusResp.KeysB64 {
newBarrierKeys[i], err = base64.StdEncoding.DecodeString(key)
newKeys[i], err = base64.StdEncoding.DecodeString(key)
if err != nil {
t.Fatal(err)
}
}
cluster.BarrierKeys = newBarrierKeys
return newKeys
}
type TestRaftServerAddressProvider struct {

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/base64"
"encoding/hex"
"fmt"
"net/http"
"github.com/hashicorp/vault/vault"
@ -59,42 +58,6 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
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{
BarrierConfig: barrierConfig,
RecoveryConfig: recoveryConfig,

View File

@ -55,6 +55,12 @@ const (
// keyring to discover the new master key. The new master key is then
// used to reload the keyring itself.
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
@ -69,8 +75,10 @@ type SecurityBarrier interface {
Initialized(ctx context.Context) (bool, error)
// Initialize works only if the barrier has not been initialized
// and makes use of the given master key.
Initialize(context.Context, []byte, io.Reader) error
// and makes use of the given master key. When sealKey is provided
// 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(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
// 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
min, max := b.KeyLength()
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 {
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
@ -720,6 +741,10 @@ func (b *AESGCMBarrier) Put(ctx context.Context, entry *logical.StorageEntry) er
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)
if err != nil {
return err

View File

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

View File

@ -70,12 +70,12 @@ func testBarrier(t *testing.T, b SecurityBarrier) {
}
// 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)
}
// 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)
}
@ -247,7 +247,7 @@ func testBarrier(t *testing.T, b SecurityBarrier) {
func testBarrier_Rotate(t *testing.T, b SecurityBarrier) {
// Initialize the barrier
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)
if err != nil {
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) {
// Initialize the barrier
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)
if err != nil {
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) {
// Initialize the barrier
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)
if err != nil {
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) {
// Initialize the barrier
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)
if err != nil {
t.Fatalf("err: %v", err)

View File

@ -6,7 +6,6 @@ import (
"crypto/rand"
"crypto/subtle"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io"
@ -932,9 +931,12 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
sealToUse := c.seal
if c.migrationSeal != nil {
c.logger.Info("unsealing using migration seal")
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)
if err != nil {
return false, err
@ -942,15 +944,39 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
if masterKey != nil {
if c.seal.BarrierType() == seal.Shamir {
_, err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
"key": base64.StdEncoding.EncodeToString(masterKey),
})
// If this is a legacy shamir seal this serves no purpose but it
// doesn't hurt.
err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(masterKey)
if err != nil {
return false, err
}
}
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)
}
@ -964,8 +990,9 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
go func() {
keyringFound := false
haveMasterKey := c.seal.StoredKeysSupported() != StoredKeysSupportedShamirMaster
defer func() {
if keyringFound {
if keyringFound && haveMasterKey {
_, err := c.unsealInternal(ctx, masterKey)
if err != nil {
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
// unseal the node.
for {
keys, err := c.underlyingPhysical.List(ctx, keyringPrefix)
if err != nil {
c.logger.Error("failed to list physical keys", "error", err)
if !keyringFound {
keys, err := c.underlyingPhysical.List(ctx, keyringPrefix)
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
}
if strutil.StrListContains(keys, "keyring") {
keyringFound = true
return
}
select {
case <-time.After(1 * time.Second):
}
time.Sleep(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) {
// Verify recovery key
// Verify recovery key.
if err := seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
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.
// If insufficient shares are provided, shamir.Combine will error, and if
// no stored keys are found it will return masterKey as nil.
if seal.StoredKeysSupported() {
if seal.StoredKeysSupported() == StoredKeysSupportedGeneric {
masterKeyShares, err := seal.GetStoredKeys(ctx)
if err != nil {
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 {
masterKey = recoveredKey
}
newRecoveryKey := masterKey
// If we have a migration seal, now's the time!
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
if err := c.barrier.Unseal(ctx, masterKey); err != nil {
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
// barrier key.
if err := c.barrier.Rekey(ctx, recoveryKey); err != nil {
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err)
// shamir KeK.
err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(recoveryKey)
if err != nil {
return nil, errwrap.Wrapf("failed to set master key in seal: {{err}}", err)
}
if err := c.barrier.Delete(ctx, StoredBarrierKeysPath); err != nil {
// 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)
if err := c.seal.SetStoredKeys(ctx, [][]byte{masterKey}); err != nil {
return nil, errwrap.Wrapf("error setting new barrier key information during migrate: {{err}}", err)
}
masterKey = recoveryKey
@ -1160,7 +1211,7 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
case c.seal.RecoveryKeySupported():
// The new seal will have recovery keys; we set it to the existing
// 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)
}

View File

@ -2,25 +2,66 @@ package api
import (
"encoding/base64"
"strings"
"fmt"
"testing"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/testhelpers"
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"
)
func TestSysRekey_Verification(t *testing.T) {
testSysRekey_Verification(t, false)
testSysRekey_Verification(t, true)
testcases := []struct {
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) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
func testSysRekey_Verification(t *testing.T, recovery bool, legacyShamir bool) {
opts := &vault.TestClusterOptions{
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()
defer cluster.Cleanup()
@ -41,48 +82,6 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
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 newKeys []string
doRekeyInitialSteps := func() {
@ -101,9 +100,13 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
t.Fatal("expected verification required")
}
keys := cluster.BarrierKeys
if recovery {
keys = cluster.RecoveryKeys
}
var resp *api.RekeyUpdateResponse
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 {
t.Fatal(err)
}
@ -124,7 +127,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
doRekeyInitialSteps()
// We are still going, so should not be able to init again
_, err := initFunc(&api.RekeyInitRequest{
_, err = initFunc(&api.RekeyInitRequest{
SecretShares: 5,
SecretThreshold: 3,
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
// the above again
cluster.EnsureCoresSealed(t)
cluster.UnsealCores(t)
if recovery {
cluster.UnsealWithStoredKeys(t)
} else {
cluster.UnsealCores(t)
}
vault.TestWaitActive(t, cluster.Cores[0].Core)
doRekeyInitialSteps()
doStartVerify := func() {
@ -211,6 +219,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
// still be the old keys (which are still currently set)
cluster.EnsureCoresSealed(t)
cluster.UnsealCores(t)
vault.TestWaitActive(t, cluster.Cores[0].Core)
// Should be able to init again and get back to where we were
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
// rotated
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 {
t.Fatal("expected error")
}
@ -252,7 +273,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
}
cluster.BarrierKeys = newKeyBytes
if err := cluster.UnsealCoresWithError(); err != nil {
t.Fatal("expected error")
t.Fatal(err)
}
} else {
// The old keys should no longer work
@ -261,7 +282,7 @@ func testSysRekey_Verification(t *testing.T, recovery bool) {
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
for _, key := range newKeys {
dec, err := base64.StdEncoding.DecodeString(key)

View File

@ -373,7 +373,7 @@ func TestRaft_SnapshotAPI_RekeyRotate_Backward(t *testing.T) {
if tCaseLocal.Rekey {
// Rekey
testhelpers.RekeyCluster(t, cluster)
cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
}
if tCaseLocal.Rekey {
@ -520,7 +520,7 @@ func TestRaft_SnapshotAPI_RekeyRotate_Forward(t *testing.T) {
if tCaseLocal.Rekey {
// Rekey
testhelpers.RekeyCluster(t, cluster)
cluster.BarrierKeys = testhelpers.RekeyCluster(t, cluster, false)
}
if tCaseLocal.Rotate {
// 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
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") {
t.Fatal(apiResp.Error())
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.Fatalf("expected error verifying hash file, got %v", apiResp.Error())
}
}

View File

@ -6,12 +6,14 @@ import (
"encoding/base64"
"errors"
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/shamir"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
)
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
// the stored keys and unseal the barrier
if c.recoveryMode {
if !c.seal.StoredKeysSupported() {
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)
storedKeys, err := c.seal.GetStoredKeys(ctx)
if err != nil {
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
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)
return nil, err
}
}
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 {
case c.recoveryMode:
// If we are in recovery mode, being able to unseal
// 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)
return nil, err
}
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)
return nil, err
}

View File

@ -82,7 +82,8 @@ func TestCore_GenerateRoot_Init(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
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)
testCore_GenerateRoot_Init_Common(t, c)
}

View File

@ -4,17 +4,17 @@ import (
"context"
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"github.com/hashicorp/vault/vault/seal/shamir"
"strings"
"sync/atomic"
"time"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics"
"github.com/hashicorp/errwrap"
multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/physical/raft"
"github.com/hashicorp/vault/sdk/helper/certutil"
@ -22,8 +22,6 @@ import (
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
"github.com/oklog/run"
)
@ -772,22 +770,35 @@ func (c *Core) reloadMasterKey(ctx context.Context) error {
if err := c.barrier.ReloadMasterKey(ctx); err != nil {
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()
if err != nil {
return errwrap.Wrapf("failed to update seal access: {{err}}", err)
}
_, 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)
}
shamirKey = keyring.masterKey
}
return nil
return c.seal.GetAccess().(*shamir.ShamirSeal).SetKey(shamirKey)
}
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)
}
if err := c.reloadShamirKey(ctx); err != nil {
return errwrap.Wrapf("error reloading shamir kek key: {{err}}", err)
}
if err := c.scheduleUpgradeCleanup(ctx); err != nil {
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/pgpkeys"
"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
@ -23,6 +25,9 @@ type InitParams struct {
BarrierConfig *SealConfig
RecoveryConfig *SealConfig
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
@ -132,6 +137,41 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
barrierConfig := initParams.BarrierConfig
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 recoveryConfig == nil {
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)
}
barrierKey, barrierUnsealKeys, err := c.generateShares(barrierConfig)
if err != nil {
c.logger.Error("error generating shares", "error", err)
return nil, err
}
initPTCleanup := initPTFunc(c)
if initPTCleanup != nil {
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
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)
return nil, errwrap.Wrapf("failed to initialize barrier: {{err}}", err)
}
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
@ -243,22 +293,34 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
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
// them through the seal
if barrierConfig.StoredShares > 0 {
var keysToStore [][]byte
for i := 0; i < barrierConfig.StoredShares; i++ {
keysToStore = append(keysToStore, barrierUnsealKeys[0])
barrierUnsealKeys = barrierUnsealKeys[1:]
switch c.seal.StoredKeysSupported() {
case StoredKeysSupportedShamirMaster:
keysToStore := [][]byte{barrierKey}
if err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetKey(sealKey); err != nil {
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 {
c.logger.Error("failed to store keys", "error", err)
return nil, errwrap.Wrapf("failed to store keys: {{err}}", err)
}
}
results := &InitResult{
SecretShares: barrierUnsealKeys,
results.SecretShares = sealKeyShares
case StoredKeysSupportedGeneric:
keysToStore := [][]byte{barrierKey}
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
@ -345,7 +407,7 @@ func (c *Core) UnsealWithStoredKeys(ctx context.Context) error {
c.unsealWithStoredKeysLock.Lock()
defer c.unsealWithStoredKeysLock.Unlock()
if !c.seal.StoredKeysSupported() {
if c.seal.BarrierType() == seal.Shamir {
return nil
}

View File

@ -2,6 +2,7 @@ package vault
import (
"context"
"github.com/hashicorp/vault/vault/seal"
"reflect"
"testing"
@ -80,7 +81,7 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
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)
}
if recoveryConf != nil {

View File

@ -4,14 +4,13 @@ import (
"bytes"
"context"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"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/sdk/helper/consts"
"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
func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
if c.seal.StoredKeysSupported() {
c.logger.Warn("stored keys supported, forcing rekey shares/threshold to 1")
switch c.seal.BarrierType() {
case seal.Shamir:
// As of Vault 1.3 all seals use StoredShares==1. The one exception is
// legacy shamir seals, which we can read but not write (by design).
// So if someone does a rekey, regardless of their intention, we're going
// to migrate them to a non-legacy Shamir seal.
if config.StoredShares != 1 {
c.logger.Warn("shamir stored keys supported, forcing rekey shares/threshold to 1")
config.StoredShares = 1
}
default:
if config.StoredShares != 1 {
c.logger.Warn("stored keys supported, forcing rekey shares/threshold to 1")
config.StoredShares = 1
}
config.SecretShares = 1
config.SecretThreshold = 1
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 {
return logical.CodedError(http.StatusBadRequest, "PGP key encryption not supported when using stored keys")
}
if config.Backup {
return logical.CodedError(http.StatusBadRequest, "key backup not supported when using stored keys")
}
}
if c.seal.RecoveryKeySupported() {
if config.VerificationRequired {
return logical.CodedError(http.StatusBadRequest, "requiring verification not supported when rekeying the barrier key with recovery keys")
}
c.logger.Debug("using recovery seal configuration to rekey barrier key")
if c.seal.RecoveryKeySupported() {
if config.VerificationRequired {
return logical.CodedError(http.StatusBadRequest, "requiring verification not supported when rekeying the barrier key with recovery keys")
}
c.logger.Debug("using recovery seal configuration to rekey barrier key")
}
// Check if the seal configuration is valid
@ -326,7 +332,7 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
var existingConfig *SealConfig
var err error
var useRecovery bool // Determines whether recovery key is being used to rekey the master key
if c.seal.StoredKeysSupported() && c.seal.RecoveryKeySupported() {
if c.seal.StoredKeysSupported() == StoredKeysSupportedGeneric && c.seal.RecoveryKeySupported() {
existingConfig, err = c.seal.RecoveryConfig(ctx)
useRecovery = true
} 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 {
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())
}
} 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 {
c.logger.Error("master key verification failed", "error", err)
return nil, logical.CodedError(http.StatusBadRequest, errwrap.Wrapf("master key verification failed: {{err}}", err).Error())
}
}
// Generate a new master key
newMasterKey, err := c.barrier.GenerateKey(c.secureRandomReader)
// Generate a new key: for AutoUnseal, this is a new master key; for Shamir,
// this is a new unseal key, and performBarrierRekey will also generate a
// new master key.
newKey, err := c.barrier.GenerateKey(c.secureRandomReader)
if err != nil {
c.logger.Error("failed to generate master key", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("master key generation failed: {{err}}", err).Error())
@ -406,27 +433,19 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
results := &RekeyResult{
Backup: c.barrierRekeyConfig.Backup,
}
// Set result.SecretShares to the master key if only a single key
// part is used -- no Shamir split required.
if c.barrierRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newMasterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(newMasterKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Error("failed to generate shares", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate shares: {{err}}", err).Error())
}
results.SecretShares = shares
}
// If 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:]
if c.seal.StoredKeysSupported() != StoredKeysSupportedGeneric {
// Set result.SecretShares to the new key itself if only a single key
// part is used -- no Shamir split required.
if c.barrierRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newKey)
} else {
// Split the new key using the Shamir algorithm
shares, err := shamir.Split(newKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Error("failed to generate shares", "error", err)
return nil, logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to generate shares: {{err}}", err).Error())
}
results.SecretShares = shares
}
}
@ -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 c.barrierRekeyConfig.VerificationRequired {
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())
}
c.barrierRekeyConfig.VerificationNonce = nonce
c.barrierRekeyConfig.VerificationKey = newMasterKey
c.barrierRekeyConfig.VerificationKey = newKey
results.VerificationRequired = true
results.VerificationNonce = nonce
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())
}
@ -503,14 +515,52 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
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
if err := c.barrier.Rekey(ctx, newMasterKey); err != nil {
c.logger.Error("failed to rekey barrier", "error", err)
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to rekey barrier: {{err}}", err).Error())
}
if c.logger.IsInfo() {
c.logger.Info("security barrier rekeyed", "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
@ -531,14 +581,6 @@ func (c *Core) performBarrierRekey(ctx context.Context, newMasterKey []byte) log
}
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
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"strings"
"testing"
log "github.com/hashicorp/go-hclog"
@ -14,21 +15,28 @@ import (
)
func TestCore_Rekey_Lifecycle(t *testing.T) {
bc, _ := TestSealDefConfigs()
bc.SecretShares = 1
bc.SecretThreshold = 1
bc.StoredShares = 0
bc := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
StoredShares: 1,
}
c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
if len(masterKeys) != 1 {
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
if _, err := c.RekeyUpdate(context.Background(), masterKeys[0], "", recovery); err == nil {
t.Fatalf("no rekey should be in progress")
_, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery)
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
@ -130,10 +138,10 @@ func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
}
func TestCore_Rekey_Update(t *testing.T) {
bc, _ := TestSealDefConfigs()
bc.SecretShares = 1
bc.SecretThreshold = 1
bc.StoredShares = 0
bc := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
c, masterKeys, _, root := TestCoreUnsealedWithConfigs(t, bc, nil)
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 {
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
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) {
bc, _ := TestSealDefConfigs()
bc := &SealConfig{
SecretShares: 5,
SecretThreshold: 3,
}
bc.StoredShares = 0
bc.SecretShares = 1
bc.SecretThreshold = 1
@ -496,3 +510,25 @@ func TestCore_Rekey_Standby(t *testing.T) {
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 (
"bytes"
"context"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"fmt"
"sync/atomic"
"github.com/golang/protobuf/proto"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/seal"
"github.com/keybase/go-crypto/openpgp"
"github.com/keybase/go-crypto/openpgp/packet"
)
@ -50,12 +49,36 @@ const (
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 {
SetCore(*Core)
Init(context.Context) error
Finalize(context.Context) error
StoredKeysSupported() bool
StoredKeysSupported() StoredKeysSupport
SealWrapable() bool
SetStoredKeys(context.Context, [][]byte) error
GetStoredKeys(context.Context) ([][]byte, error)
@ -77,12 +100,9 @@ type Seal interface {
}
type defaultSeal struct {
access seal.Access
config atomic.Value
core *Core
PretendToAllowStoredShares bool
PretendToAllowRecoveryKeys bool
PretendRecoveryKey []byte
access seal.Access
config atomic.Value
core *Core
}
func NewDefaultSeal(lowLevel seal.Access) Seal {
@ -93,6 +113,10 @@ func NewDefaultSeal(lowLevel seal.Access) Seal {
return ret
}
func (d *defaultSeal) SealWrapable() bool {
return false
}
func (d *defaultSeal) checkCore() error {
if d.core == nil {
return fmt.Errorf("seal does not have a core set")
@ -124,25 +148,61 @@ func (d *defaultSeal) BarrierType() string {
return seal.Shamir
}
func (d *defaultSeal) StoredKeysSupported() bool {
return d.PretendToAllowStoredShares
func (d *defaultSeal) StoredKeysSupported() StoredKeysSupport {
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 {
return d.PretendToAllowRecoveryKeys
return false
}
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) {
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) {
if d.config.Load().(*SealConfig) != nil {
return d.config.Load().(*SealConfig).Clone(), nil
cfg := d.config.Load().(*SealConfig)
if cfg != nil {
return cfg.Clone(), nil
}
if err := d.checkCore(); err != nil {
@ -204,7 +264,7 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
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.
if d.core.isRaftUnseal() {
d.config.Store(config.Clone())
@ -238,34 +298,18 @@ func (d *defaultSeal) SetCachedBarrierConfig(config *SealConfig) {
}
func (d *defaultSeal) RecoveryType() string {
if d.PretendToAllowRecoveryKeys {
return RecoveryTypeShamir
}
return RecoveryTypeUnsupported
}
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")
}
func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
if d.PretendToAllowRecoveryKeys {
return d.PretendRecoveryKey, nil
}
return nil, fmt.Errorf("recovery not supported")
}
func (d *defaultSeal) SetRecoveryConfig(ctx context.Context, config *SealConfig) error {
if d.PretendToAllowRecoveryKeys {
return nil
}
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 {
if d.PretendToAllowRecoveryKeys {
if subtle.ConstantTimeCompare(key, d.PretendRecoveryKey) == 1 {
return nil
}
return fmt.Errorf("mismatch")
}
return fmt.Errorf("recovery not supported")
}
func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
if d.PretendToAllowRecoveryKeys {
d.PretendRecoveryKey = key
return nil
}
return fmt.Errorf("recovery not supported")
}
@ -318,7 +352,7 @@ type SealConfig struct {
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
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"`
// Stores the progress of the rekey operation (key shares)
@ -361,10 +395,10 @@ func (s *SealConfig) Validate() error {
if s.SecretThreshold > s.SecretShares {
return fmt.Errorf("threshold cannot be larger than shares")
}
if s.StoredShares > s.SecretShares {
return fmt.Errorf("stored keys cannot be larger than shares")
if s.StoredShares > 1 {
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")
}
if len(s.PGPKeys) > 0 {
@ -403,3 +437,71 @@ func (s *SealConfig) Clone() *SealConfig {
}
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"
)
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
// specific to encrypting and decrypting data, or in this case keys.
type Access interface {
@ -31,6 +36,5 @@ type Access interface {
Init(context.Context) error
Finalize(context.Context) error
Encrypt(context.Context, []byte) (*physical.EncryptedBlobInfo, error)
Decrypt(context.Context, *physical.EncryptedBlobInfo) ([]byte, error)
Encryptor
}

View File

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

View File

@ -2,7 +2,6 @@ package vault
import (
"context"
"fmt"
)
// SealAccess is a wrapper around Seal that exposes accessor methods
@ -16,7 +15,7 @@ func NewSealAccess(seal Seal) *SealAccess {
return &SealAccess{seal: seal}
}
func (s *SealAccess) StoredKeysSupported() bool {
func (s *SealAccess) StoredKeysSupported() StoredKeysSupport {
return s.seal.StoredKeysSupported()
}
@ -46,22 +45,3 @@ func (s *SealAccess) ClearCaches(ctx context.Context) {
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
}
func (d *autoSeal) SealWrapable() bool {
return true
}
func (d *autoSeal) GetAccess() seal.Access {
return d.Access
}
@ -73,8 +77,8 @@ func (d *autoSeal) BarrierType() string {
return d.SealType()
}
func (d *autoSeal) StoredKeysSupported() bool {
return true
func (d *autoSeal) StoredKeysSupported() StoredKeysSupport {
return StoredKeysSupportedGeneric
}
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
// does not need to be seal wrapped in this case.
func (d *autoSeal) SetStoredKeys(ctx context.Context, 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 := 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
return writeStoredKeys(ctx, d.core.physical, d, keys)
}
// GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the
// autoseal.
func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
pe, err := d.core.physical.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 := 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
return readStoredKeys(ctx, d.core.physical, d)
}
func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error {

View File

@ -6,13 +6,16 @@ import (
"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) {
bc, _ := TestSealDefConfigs()
// Change these to non-default values to ensure we are seeing the real
// config we set
bc.SecretShares = 4
bc.SecretThreshold = 2
bc := &SealConfig{
SecretShares: 4,
SecretThreshold: 2,
}
core, _, _ := TestCoreUnsealed(t)
defSeal := NewDefaultSeal(nil)

View File

@ -7,32 +7,29 @@ import (
testing "github.com/mitchellh/go-testing-interface"
)
var (
TestCoreUnsealedWithConfigs = testCoreUnsealedWithConfigs
TestSealDefConfigs = testSealDefConfigs
)
type TestSealOpts struct {
StoredKeysDisabled bool
RecoveryKeysDisabled bool
Secret []byte
Logger log.Logger
Logger log.Logger
StoredKeys StoredKeysSupport
Secret []byte
}
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()
var opts *TestSealOpts
opts := &TestSealOpts{}
if recoveryConf == nil {
opts = &TestSealOpts{
StoredKeysDisabled: true,
RecoveryKeysDisabled: true,
}
opts.StoredKeys = StoredKeysSupportedShamirMaster
}
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)
result, err := core.Initialize(context.Background(), &InitParams{
BarrierConfig: barrierConf,
RecoveryConfig: recoveryConf,
BarrierConfig: barrierConf,
RecoveryConfig: recoveryConf,
LegacyShamirSeal: sealOpts.StoredKeys == StoredKeysNotSupported,
})
if err != nil {
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
}
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
import (
"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"
testing "github.com/mitchellh/go-testing-interface"
)
func NewTestSeal(t testing.T, opts *TestSealOpts) Seal {
var logger hclog.Logger
if opts != nil {
logger = opts.Logger
t.Helper()
if opts == nil {
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,
}
// If we support storing barrier keys, then set that to equal the min threshold to unseal
if core.seal.StoredKeysSupported() {
barrierConfig.StoredShares = barrierConfig.SecretThreshold
switch core.seal.StoredKeysSupported() {
case StoredKeysNotSupported:
barrierConfig.StoredShares = 0
default:
barrierConfig.StoredShares = 1
}
recoveryConfig := &SealConfig{
@ -276,10 +278,14 @@ func TestCoreInitClusterWrapperSetup(t testing.T, core *Core, handler http.Handl
SecretThreshold: 3,
}
result, err := core.Initialize(context.Background(), &InitParams{
initParams := &InitParams{
BarrierConfig: barrierConfig,
RecoveryConfig: recoveryConfig,
})
}
if core.seal.StoredKeysSupported() == StoredKeysNotSupported {
initParams.LegacyShamirSeal = true
}
result, err := core.Initialize(context.Background(), initParams)
if err != nil {
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
func (c *TestCluster) UnsealCores(t testing.T) {
t.Helper()
if err := c.UnsealCoresWithError(); err != nil {
t.Fatal(err)
}
@ -833,7 +840,7 @@ func (c *TestCluster) UnsealCoresWithError() error {
// Unseal first core
for _, key := range c.BarrierKeys {
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 _, key := range c.BarrierKeys {
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)
}
cfg, err := cores[0].seal.BarrierConfig(ctx)
if err != nil {
t.Fatal(err)
}
// Unseal other cores unless otherwise specified
if (opts == nil || !opts.KeepStandbysSealed) && numCores > 1 {
for i := 1; i < numCores; i++ {
cores[i].seal.SetCachedBarrierConfig(cfg)
for _, key := range bKeys {
if _, err := cores[i].Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)