Bump AESGCM version; include path in the GCM tags.
This commit is contained in:
parent
68c268a6f0
commit
c5ddfbc391
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue