Bump AESGCM version; include path in the GCM tags.

This commit is contained in:
Jeff Mitchell 2015-09-19 15:33:18 -04:00
parent 68c268a6f0
commit c5ddfbc391
2 changed files with 188 additions and 36 deletions

View File

@ -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")
}
}

View File

@ -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")