Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: re-encrypt barrier and recovery keys if the unseal key is updated #7493

Closed
wants to merge 12 commits into from
Closed
8 changes: 7 additions & 1 deletion vault/seal/seal_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type TestSeal struct {
Type string
secret []byte
keyId string
}

var _ Access = (*TestSeal)(nil)
Expand All @@ -18,6 +19,7 @@ func NewTestSeal(secret []byte) *TestSeal {
return &TestSeal{
Type: Test,
secret: secret,
keyId: "static-key",
}
}

Expand All @@ -34,7 +36,11 @@ func (t *TestSeal) SealType() string {
}

func (t *TestSeal) KeyID() string {
return "static-key"
return t.keyId
}

func (t *TestSeal) SetKeyID(k string) {
t.keyId = k
}

func (t *TestSeal) Encrypt(_ context.Context, plaintext []byte) (*physical.EncryptedBlobInfo, error) {
Expand Down
2 changes: 1 addition & 1 deletion vault/seal_autoseal.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type autoSeal struct {
// Ensure we are implementing the Seal interface
var _ Seal = (*autoSeal)(nil)

func NewAutoSeal(lowLevel seal.Access) Seal {
func NewAutoSeal(lowLevel seal.Access) *autoSeal {
ret := &autoSeal{
Access: lowLevel,
}
Expand Down
151 changes: 151 additions & 0 deletions vault/seal_autoseal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package vault

import (
"bytes"
"context"
"reflect"
"testing"

proto "github.com/golang/protobuf/proto"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/seal"
)

type phy struct {
mgaffney marked this conversation as resolved.
Show resolved Hide resolved
t *testing.T
entries map[string][]*physical.Entry
}

func newTestBackend(t *testing.T) *phy {
return &phy{
t: t,
entries: make(map[string][]*physical.Entry),
}
}

func (p *phy) Put(_ context.Context, entry *physical.Entry) error {
p.entries[entry.Key] = append(p.entries[entry.Key], entry)
return nil
}

func (p *phy) Get(_ context.Context, key string) (*physical.Entry, error) {
entries := p.entries[key]
if entries == nil {
return nil, nil
}
return entries[len(entries)-1], nil
}

func (p *phy) Delete(_ context.Context, key string) error {
p.t.Errorf("Delete called on phy: key: %v", key)
return nil
}

func (p *phy) List(_ context.Context, prefix string) ([]string, error) {
p.t.Errorf("List called on phy: prefix: %v", prefix)
return []string{}, nil
}

func (p *phy) Len() int {
return len(p.entries)
}

func TestAutoSeal_UpgradeKeys(t *testing.T) {
core, _, _ := TestCoreUnsealed(t)
testSeal := seal.NewTestSeal(nil)

var encKeys []string
changeKey := func(key string) {
encKeys = append(encKeys, key)
testSeal.SetKeyID(key)
}

// Set initial encryption key.
changeKey("kaz")

autoSeal := NewAutoSeal(testSeal)
autoSeal.SetCore(core)
pBackend := newTestBackend(t)
core.physical = pBackend

ctx := context.Background()

inkeys := [][]byte{[]byte("grist"), []byte("house")}
briankassouf marked this conversation as resolved.
Show resolved Hide resolved
if err := autoSeal.SetStoredKeys(ctx, inkeys); err != nil {
t.Fatalf("SetStoredKeys: want no error, got %v", err)
}

inRecoveryKey := []byte("falernum")
if err := autoSeal.SetRecoveryKey(ctx, inRecoveryKey); err != nil {
t.Fatalf("SetRecoveryKey: want no error, got %v", err)
}

check := func() {
// The values of the stored keys should never change.
outkeys, err := autoSeal.GetStoredKeys(ctx)
if err != nil {
t.Fatalf("GetStoredKeys: want no error, got %v", err)
}
if !reflect.DeepEqual(inkeys, outkeys) {
t.Errorf("incorrect stored keys: want %v, got %v", inkeys, outkeys)
}

// The value of the recovery key should also never change.
outRecoveryKey, err := autoSeal.RecoveryKey(ctx)
if err != nil {
t.Fatalf("RecoveryKey: want no error, got %v", err)
}
if !bytes.Equal(inRecoveryKey, outRecoveryKey) {
t.Errorf("incorrect recovery key: want %q, got %q", inRecoveryKey, outRecoveryKey)
}

// There should only be 2 entries in the physical backend. One for
// the stored keys and one for the recovery key.
if want, got := 2, pBackend.Len(); want != got {
t.Errorf("backend unexpected Len: want %d, got %d", want, got)
}

for phyKey, phyEntries := range pBackend.entries {
// Calling UpgradeKeys should only add an entry if the key has
// changed.
if keyCount, entryCount := len(encKeys), len(phyEntries); keyCount != entryCount {
t.Errorf("phyKey = %s: encryption key count not equal to entry count: keys=%d, entries=%d", phyKey, keyCount, entryCount)
}

// Each phyEntry should correspond to a key at the same index
// in encKeys. Iterate over each phyEntry and verify it was
// encrypted with its corresponding key in encKeys.
for i, phyEntry := range phyEntries {
blobInfo := &physical.EncryptedBlobInfo{}
if err := proto.Unmarshal(phyEntry.Value, blobInfo); err != nil {
t.Errorf("phyKey = %s: failed to proto decode stored keys: %s", phyKey, err)
}
if blobInfo.KeyInfo == nil {
t.Errorf("phyKey = %s: KeyInfo missing: %+v", phyKey, blobInfo)
}
if want, got := encKeys[i], blobInfo.KeyInfo.KeyID; want != got {
t.Errorf("phyKey = %s: Incorrect encryption key: want %s, got %s", phyKey, want, got)
}
}
}
}

// Verify the current state is correct before calling UpgradeKeys.
check()

// Call UpgradeKeys before changing the encryption key and verify
// nothing has changed.
if err := autoSeal.UpgradeKeys(ctx); err != nil {
t.Fatalf("UpgradeKeys: want no error, got %v", err)
}
check()

// Change the encryption key, call UpgradeKeys, then verify the stored
// keys and recovery key has been re-encrypted with the new encryption
// key.
changeKey("primanti")
if err := autoSeal.UpgradeKeys(ctx); err != nil {
t.Fatalf("UpgradeKeys: want no error, got %v", err)
}
check()
}