diff --git a/vault/barrier_aes_gcm.go b/vault/barrier_aes_gcm.go index c2a868f94b..45995ac81c 100644 --- a/vault/barrier_aes_gcm.go +++ b/vault/barrier_aes_gcm.go @@ -24,10 +24,12 @@ const ( // termSize the number of bytes used for the key term. termSize = 4 +) - // aesgcmVersionByte is prefixed to a message to allow for - // future versioning of barrier implementations. - aesgcmVersionByte = 0x1 +// Versions of the AESGCM storage methodology +const ( + AESGCMVersion1 = 0x1 + AESGCMVersion2 = 0x2 ) // barrierInit is the JSON encoded value stored @@ -55,6 +57,11 @@ type AESGCMBarrier struct { // cache is used to reduce the number of AEAD constructions we do cache map[uint32]cipher.AEAD cacheLock sync.RWMutex + + // currentAESGCMVersionByte is prefixed to a message to allow for + // future versioning of barrier implementations. It's var instead + // of const to allow for testing + currentAESGCMVersionByte byte } // NewAESGCMBarrier is used to construct a new barrier that uses @@ -64,6 +71,7 @@ func NewAESGCMBarrier(physical physical.Backend) (*AESGCMBarrier, error) { backend: physical, sealed: true, cache: make(map[uint32]cipher.AEAD), + currentAESGCMVersionByte: byte(AESGCMVersion2), } return b, nil } @@ -141,7 +149,7 @@ func (b *AESGCMBarrier) persistKeyring(keyring *Keyring) error { } // Encrypt the barrier init value - value := b.encrypt(initialKeyTerm, gcm, buf) + value := b.encrypt(keyringPath, initialKeyTerm, gcm, buf) // Create the keyring physical entry pe := &physical.Entry{ @@ -170,7 +178,7 @@ func (b *AESGCMBarrier) persistKeyring(keyring *Keyring) error { if err != nil { return err } - value = b.encrypt(activeKey.Term, aead, buf) + value = b.encrypt(masterKeyPath, activeKey.Term, aead, buf) // Update the masterKeyPath for standby instances pe = &physical.Entry{ @@ -243,7 +251,7 @@ func (b *AESGCMBarrier) ReloadKeyring() error { } // Decrypt the barrier init key - plain, err := b.decrypt(gcm, out.Value) + plain, err := b.decrypt(keyringPath, gcm, out.Value) if err != nil { if strings.Contains(err.Error(), "message authentication failed") { return ErrBarrierInvalidKey @@ -323,7 +331,7 @@ func (b *AESGCMBarrier) Unseal(key []byte) error { } if out != nil { // Decrypt the barrier init key - plain, err := b.decrypt(gcm, out.Value) + plain, err := b.decrypt(keyringPath, gcm, out.Value) if err != nil { if strings.Contains(err.Error(), "message authentication failed") { return ErrBarrierInvalidKey @@ -354,7 +362,7 @@ func (b *AESGCMBarrier) Unseal(key []byte) error { } // Decrypt the barrier init key - plain, err := b.decrypt(gcm, out.Value) + plain, err := b.decrypt(barrierInitPath, gcm, out.Value) if err != nil { if strings.Contains(err.Error(), "message authentication failed") { return ErrBarrierInvalidKey @@ -469,10 +477,12 @@ func (b *AESGCMBarrier) CreateUpgrade(term uint32) error { return err } + key := fmt.Sprintf("%s%d", keyringUpgradePrefix, prevTerm) + value := b.encrypt(key, prevTerm, primary, buf) // Create upgrade key pe := &physical.Entry{ - Key: fmt.Sprintf("%s%d", keyringUpgradePrefix, prevTerm), - Value: b.encrypt(prevTerm, primary, buf), + Key: key, + Value: value, } return b.backend.Put(pe) } @@ -593,7 +603,7 @@ func (b *AESGCMBarrier) Put(entry *Entry) error { pe := &physical.Entry{ Key: entry.Key, - Value: b.encrypt(term, primary, entry.Value), + Value: b.encrypt(entry.Key, term, primary, entry.Value), } return b.backend.Put(pe) } @@ -616,7 +626,7 @@ func (b *AESGCMBarrier) Get(key string) (*Entry, error) { } // Decrypt the ciphertext - plain, err := b.decryptKeyring(pe.Value) + plain, err := b.decryptKeyring(key, pe.Value) if err != nil { return nil, fmt.Errorf("decryption failed: %v", err) } @@ -706,7 +716,7 @@ func (b *AESGCMBarrier) aeadFromKey(key []byte) (cipher.AEAD, error) { } // encrypt is used to encrypt a value -func (b *AESGCMBarrier) encrypt(term uint32, gcm cipher.AEAD, plain []byte) []byte { +func (b *AESGCMBarrier) encrypt(path string, term uint32, gcm cipher.AEAD, plain []byte) []byte { // Allocate the output buffer with room for tern, version byte, // nonce, GCM tag and the plaintext capacity := termSize + 1 + gcm.NonceSize() + gcm.Overhead() + len(plain) @@ -717,50 +727,57 @@ func (b *AESGCMBarrier) encrypt(term uint32, gcm cipher.AEAD, plain []byte) []by binary.BigEndian.PutUint32(out[:4], term) // Set the version byte - out[4] = aesgcmVersionByte + out[4] = b.currentAESGCMVersionByte // Generate a random nonce nonce := out[5 : 5+gcm.NonceSize()] rand.Read(nonce) // Seal the output - out = gcm.Seal(out, nonce, plain, nil) + switch b.currentAESGCMVersionByte { + case AESGCMVersion1: + out = gcm.Seal(out, nonce, plain, nil) + case AESGCMVersion2: + out = gcm.Seal(out, nonce, plain, []byte(path)) + default: + panic("Unknown AESGCM version") + } + return out } // decrypt is used to decrypt a value -func (b *AESGCMBarrier) decrypt(gcm cipher.AEAD, cipher []byte) ([]byte, error) { +func (b *AESGCMBarrier) decrypt(path string, gcm cipher.AEAD, cipher []byte) ([]byte, error) { // Verify the term is always just one term := binary.BigEndian.Uint32(cipher[:4]) if term != initialKeyTerm { return nil, fmt.Errorf("term mis-match") } - // Verify the version byte - if cipher[4] != aesgcmVersionByte { - return nil, fmt.Errorf("version bytes mis-match") - } - // Capture the parts nonce := cipher[5 : 5+gcm.NonceSize()] raw := cipher[5+gcm.NonceSize():] out := make([]byte, 0, len(raw)-gcm.NonceSize()) - // Attempt to open - return gcm.Open(out, nonce, raw, nil) + // Verify the cipher byte and attempt to open + switch cipher[4] { + case AESGCMVersion1: + return gcm.Open(out, nonce, raw, nil) + case AESGCMVersion2: + return gcm.Open(out, nonce, raw, []byte(path)) + default: + return nil, fmt.Errorf("version bytes mis-match") + } } // decryptKeyring is used to decrypt a value using the keyring -func (b *AESGCMBarrier) decryptKeyring(cipher []byte) ([]byte, error) { +func (b *AESGCMBarrier) decryptKeyring(path string, cipher []byte) ([]byte, error) { // Verify the term term := binary.BigEndian.Uint32(cipher[:4]) - // Verify the version byte - if cipher[4] != aesgcmVersionByte { - return nil, fmt.Errorf("version bytes mis-match") - } - // Get the GCM by term + // It is expensive to do this first but it is not a + // normal case that this won't match gcm, err := b.aeadForTerm(term) if err != nil { return nil, err @@ -769,11 +786,17 @@ func (b *AESGCMBarrier) decryptKeyring(cipher []byte) ([]byte, error) { return nil, fmt.Errorf("no decryption key available for term %d", term) } - // Capture the parts nonce := cipher[5 : 5+gcm.NonceSize()] raw := cipher[5+gcm.NonceSize():] out := make([]byte, 0, len(raw)-gcm.NonceSize()) // Attempt to open - return gcm.Open(out, nonce, raw, nil) + switch cipher[4] { + case AESGCMVersion1: + return gcm.Open(out, nonce, raw, nil) + case AESGCMVersion2: + return gcm.Open(out, nonce, raw, []byte(path)) + default: + return nil, fmt.Errorf("version bytes mis-match") + } } diff --git a/vault/barrier_aes_gcm_test.go b/vault/barrier_aes_gcm_test.go index 0f23666d72..a78c335cff 100644 --- a/vault/barrier_aes_gcm_test.go +++ b/vault/barrier_aes_gcm_test.go @@ -96,7 +96,7 @@ func TestAESGCMBarrier_BackwardsCompatible(t *testing.T) { // Protect with master key master, _ := b.GenerateKey() gcm, _ := b.aeadFromKey(master) - value := b.encrypt(initialKeyTerm, gcm, buf) + value := b.encrypt(barrierInitPath, initialKeyTerm, gcm, buf) // Write to the physical backend pe := &physical.Entry{ @@ -109,7 +109,7 @@ func TestAESGCMBarrier_BackwardsCompatible(t *testing.T) { gcm, _ = b.aeadFromKey(encrypt) pe = &physical.Entry{ Key: "test/foo", - Value: b.encrypt(initialKeyTerm, gcm, []byte("test")), + Value: b.encrypt("test/foo", initialKeyTerm, gcm, []byte("test")), } inm.Put(pe) @@ -193,7 +193,7 @@ func TestAESGCMBarrier_Confidential(t *testing.T) { } } -// Verify data sent through is cannot be tampered +// Verify data sent through cannot be tampered with func TestAESGCMBarrier_Integrity(t *testing.T) { inm := physical.NewInmem() b, err := NewAESGCMBarrier(inm) @@ -228,6 +228,135 @@ func TestAESGCMBarrier_Integrity(t *testing.T) { } } +// Verify data sent through cannot be moved +func TestAESGCMBarrier_MoveIntegrityV1(t *testing.T) { + inm := physical.NewInmem() + b, err := NewAESGCMBarrier(inm) + if err != nil { + t.Fatalf("err: %v", err) + } + b.currentAESGCMVersionByte = AESGCMVersion1 + + // Initialize and unseal + key, _ := b.GenerateKey() + err = b.Initialize(key) + if err != nil { + t.Fatalf("err: %v", err) + } + err = b.Unseal(key) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Put a logical entry + entry := &Entry{Key: "test", Value: []byte("test")} + err = b.Put(entry) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Change the location of the underlying physical entry + pe, _ := inm.Get("test") + pe.Key = "moved" + err = inm.Put(pe) + + // Read from the barrier + _, err = b.Get("moved") + if err != nil { + t.Fatalf("should succeed with version 1!") + } +} + +func TestAESGCMBarrier_MoveIntegrityV2(t *testing.T) { + inm := physical.NewInmem() + b, err := NewAESGCMBarrier(inm) + if err != nil { + t.Fatalf("err: %v", err) + } + b.currentAESGCMVersionByte = AESGCMVersion2 + + // Initialize and unseal + key, _ := b.GenerateKey() + err = b.Initialize(key) + if err != nil { + t.Fatalf("err: %v", err) + } + err = b.Unseal(key) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Put a logical entry + entry := &Entry{Key: "test", Value: []byte("test")} + err = b.Put(entry) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Change the location of the underlying physical entry + pe, _ := inm.Get("test") + pe.Key = "moved" + err = inm.Put(pe) + + // Read from the barrier + _, err = b.Get("moved") + if err == nil { + t.Fatalf("should fail with version 2!") + } +} + +func TestAESGCMBarrier_UpgradeV1toV2(t *testing.T) { + inm := physical.NewInmem() + b, err := NewAESGCMBarrier(inm) + if err != nil { + t.Fatalf("err: %v", err) + } + b.currentAESGCMVersionByte = AESGCMVersion1 + + // Initialize and unseal + key, _ := b.GenerateKey() + err = b.Initialize(key) + if err != nil { + t.Fatalf("err: %v", err) + } + err = b.Unseal(key) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Put a logical entry + entry := &Entry{Key: "test", Value: []byte("test")} + err = b.Put(entry) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Seal + err = b.Seal() + if err != nil { + t.Fatalf("err: %v", err) + } + + // Open again as version 2 + b, err = NewAESGCMBarrier(inm) + if err != nil { + t.Fatalf("err: %v", err) + } + b.currentAESGCMVersionByte = AESGCMVersion2 + + // Unseal + err = b.Unseal(key) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Check successful decryption + _, err = b.Get("test") + if err != nil { + t.Fatalf("Upgrade unsuccessful") + } +} + func TestEncrypt_Unique(t *testing.T) { inm := physical.NewInmem() b, err := NewAESGCMBarrier(inm) @@ -247,8 +376,8 @@ func TestEncrypt_Unique(t *testing.T) { term := b.keyring.ActiveTerm() primary, _ := b.aeadForTerm(term) - first := b.encrypt(term, primary, entry.Value) - second := b.encrypt(term, primary, entry.Value) + first := b.encrypt("test", term, primary, entry.Value) + second := b.encrypt("test", term, primary, entry.Value) if bytes.Equal(first, second) == true { t.Fatalf("improper random seeding detected")