Merge branch 'v0-16-3-branch-7705' into v0-16-3-branch

This commit is contained in:
Olaoluwa Osuntokun 2023-05-22 18:13:39 -07:00
commit fcbe6844c0
3 changed files with 116 additions and 38 deletions

View file

@ -6,5 +6,12 @@ Optimized [mempool
management](https://github.com/lightningnetwork/lnd/pull/7681) to lower the CPU management](https://github.com/lightningnetwork/lnd/pull/7681) to lower the CPU
usage. usage.
## Bug Fixes
* [Re-encrypt/regenerate](https://github.com/lightningnetwork/lnd/pull/7705)
all macaroon DB root keys on `ChangePassword`/`GenerateNewRootKey`
respectively.
# Contributors (Alphabetical Order) # Contributors (Alphabetical Order)
* Elle Mouton
* Yong Yu * Yong Yu

View file

@ -54,6 +54,10 @@ var (
// ErrEncKeyNotFound specifies that there was no encryption key found // ErrEncKeyNotFound specifies that there was no encryption key found
// even if one was expected to be generated. // even if one was expected to be generated.
ErrEncKeyNotFound = fmt.Errorf("macaroon encryption key not found") ErrEncKeyNotFound = fmt.Errorf("macaroon encryption key not found")
// ErrDefaultRootKeyNotFound is returned when the default root key is
// not found in the DB when it is expected to be.
ErrDefaultRootKeyNotFound = fmt.Errorf("default root key not found")
) )
// RootKeyStorage implements the bakery.RootKeyStorage interface. // RootKeyStorage implements the bakery.RootKeyStorage interface.
@ -140,8 +144,8 @@ func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
}, func() {}) }, func() {})
} }
// ChangePassword decrypts the macaroon root key with the old password and then // ChangePassword decrypts all the macaroon root keys with the old password and
// encrypts it again with the new password. // then encrypts them again with the new password.
func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error { func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
// We need the store to already be unlocked. With this we can make sure // We need the store to already be unlocked. With this we can make sure
// that there already is a key in the DB. // that there already is a key in the DB.
@ -159,19 +163,18 @@ func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
} }
encKeyDb := bucket.Get(encryptionKeyID)
rootKeyDb := bucket.Get(DefaultRootKeyID)
// Both the encryption key and the root key must be present // The encryption key must be present, otherwise we are in the
// otherwise we are in the wrong state to change the password. // wrong state to change the password.
if len(encKeyDb) == 0 || len(rootKeyDb) == 0 { encKeyDB := bucket.Get(encryptionKeyID)
if len(encKeyDB) == 0 {
return ErrEncKeyNotFound return ErrEncKeyNotFound
} }
// Unmarshal parameters for old encryption key and derive the // Unmarshal parameters for old encryption key and derive the
// old key with them. // old key with them.
encKeyOld := &snacl.SecretKey{} encKeyOld := &snacl.SecretKey{}
err := encKeyOld.Unmarshal(encKeyDb) err := encKeyOld.Unmarshal(encKeyDB)
if err != nil { if err != nil {
return err return err
} }
@ -188,21 +191,42 @@ func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
return err return err
} }
// Now try to decrypt the root key with the old encryption key, // foundDefaultRootKey is used to keep track of if we have
// encrypt it with the new one and then store it in the DB. // found and re-encrypted the default root key so that we can
decryptedKey, err := encKeyOld.Decrypt(rootKeyDb) // return an error if it is not found.
var foundDefaultRootKey bool
err = bucket.ForEach(func(k, v []byte) error {
// Skip the key if it is the encryption key ID since
// we do not want to re-encrypt this.
if bytes.Equal(k, encryptionKeyID) {
return nil
}
if bytes.Equal(k, DefaultRootKeyID) {
foundDefaultRootKey = true
}
// Now try to decrypt the root key with the old
// encryption key, encrypt it with the new one and then
// store it in the DB.
decryptedKey, err := encKeyOld.Decrypt(v)
if err != nil {
return err
}
encryptedKey, err := encKeyNew.Encrypt(decryptedKey)
if err != nil {
return err
}
return bucket.Put(k, encryptedKey)
})
if err != nil { if err != nil {
return err return err
} }
rootKey := make([]byte, len(decryptedKey))
copy(rootKey, decryptedKey) if !foundDefaultRootKey {
encryptedKey, err := encKeyNew.Encrypt(rootKey) return ErrDefaultRootKeyNotFound
if err != nil {
return err
}
err = bucket.Put(DefaultRootKeyID, encryptedKey)
if err != nil {
return err
} }
// Finally, store the new encryption key parameters in the DB // Finally, store the new encryption key parameters in the DB
@ -325,10 +349,34 @@ func (r *RootKeyStorage) GenerateNewRootKey() error {
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
} }
// The default root key should be created even if it does not
// yet exist, so we do this separately from the rest of the
// root keys.
_, err := generateAndStoreNewRootKey( _, err := generateAndStoreNewRootKey(
bucket, DefaultRootKeyID, r.encKey, bucket, DefaultRootKeyID, r.encKey,
) )
return err if err != nil {
return err
}
// Now iterate over all the other root keys that may exist
// and re-generate each of them.
return bucket.ForEach(func(k, v []byte) error {
if bytes.Equal(k, encryptionKeyID) {
return nil
}
if bytes.Equal(k, DefaultRootKeyID) {
return nil
}
_, err := generateAndStoreNewRootKey(
bucket, k, r.encKey,
)
return err
})
}, func() {}) }, func() {})
} }

View file

@ -16,6 +16,10 @@ var (
defaultRootKeyIDContext = macaroons.ContextWithRootKeyID( defaultRootKeyIDContext = macaroons.ContextWithRootKeyID(
context.Background(), macaroons.DefaultRootKeyID, context.Background(), macaroons.DefaultRootKeyID,
) )
nonDefaultRootKeyIDContext = macaroons.ContextWithRootKeyID(
context.Background(), []byte{1},
)
) )
// newTestStore creates a new bolt DB in a temporary directory and then // newTestStore creates a new bolt DB in a temporary directory and then
@ -131,8 +135,8 @@ func TestStore(t *testing.T) {
require.Equal(t, rootID, id) require.Equal(t, rootID, id)
} }
// TestStoreGenerateNewRootKey tests that a root key can be replaced with a new // TestStoreGenerateNewRootKey tests that root keys can be replaced with new
// one in the store without changing the password. // ones in the store without changing the password.
func TestStoreGenerateNewRootKey(t *testing.T) { func TestStoreGenerateNewRootKey(t *testing.T) {
_, store := newTestStore(t) _, store := newTestStore(t)
@ -140,23 +144,33 @@ func TestStoreGenerateNewRootKey(t *testing.T) {
err := store.GenerateNewRootKey() err := store.GenerateNewRootKey()
require.Equal(t, macaroons.ErrStoreLocked, err) require.Equal(t, macaroons.ErrStoreLocked, err)
// Unlock the store and read the current key. // Unlock the store.
pw := []byte("weks") pw := []byte("weks")
err = store.CreateUnlock(&pw) err = store.CreateUnlock(&pw)
require.NoError(t, err) require.NoError(t, err)
oldRootKey, _, err := store.RootKey(defaultRootKeyIDContext)
// Read the default root key.
oldRootKey1, _, err := store.RootKey(defaultRootKeyIDContext)
require.NoError(t, err) require.NoError(t, err)
// Replace the root key with a new random key. // Read the non-default root-key.
oldRootKey2, _, err := store.RootKey(nonDefaultRootKeyIDContext)
require.NoError(t, err)
// Replace the root keys with new random keys.
err = store.GenerateNewRootKey() err = store.GenerateNewRootKey()
require.NoError(t, err) require.NoError(t, err)
// Finally, read the root key from the DB and compare it to the one // Finally, read both root keys from the DB and compare them to the ones
// we got returned earlier. This makes sure that the encryption/ // we got returned earlier. This makes sure that the encryption/
// decryption of the key in the DB worked as expected too. // decryption of the key in the DB worked as expected too.
newRootKey, _, err := store.RootKey(defaultRootKeyIDContext) newRootKey1, _, err := store.RootKey(defaultRootKeyIDContext)
require.NoError(t, err) require.NoError(t, err)
require.NotEqual(t, oldRootKey, newRootKey) require.NotEqual(t, oldRootKey1, newRootKey1)
newRootKey2, _, err := store.RootKey(nonDefaultRootKeyIDContext)
require.NoError(t, err)
require.NotEqual(t, oldRootKey2, newRootKey2)
} }
// TestStoreSetRootKey tests that a root key can be set to a specified value. // TestStoreSetRootKey tests that a root key can be set to a specified value.
@ -195,20 +209,25 @@ func TestStoreSetRootKey(t *testing.T) {
} }
// TestStoreChangePassword tests that the password for the store can be changed // TestStoreChangePassword tests that the password for the store can be changed
// without changing the root key. // without changing the root keys.
func TestStoreChangePassword(t *testing.T) { func TestStoreChangePassword(t *testing.T) {
tempDir, store := newTestStore(t) tempDir, store := newTestStore(t)
// The store must be unlocked to replace the root key. // The store must be unlocked to replace the root keys.
err := store.ChangePassword(nil, nil) err := store.ChangePassword(nil, nil)
require.Equal(t, macaroons.ErrStoreLocked, err) require.Equal(t, macaroons.ErrStoreLocked, err)
// Unlock the DB and read the current root key. This will need to stay // Unlock the DB and read the current default root key and one other
// the same after changing the password for the test to succeed. // non-default root key. Both of these should stay the same after
// changing the password for the test to succeed.
pw := []byte("weks") pw := []byte("weks")
err = store.CreateUnlock(&pw) err = store.CreateUnlock(&pw)
require.NoError(t, err) require.NoError(t, err)
rootKey, _, err := store.RootKey(defaultRootKeyIDContext)
rootKey1, _, err := store.RootKey(defaultRootKeyIDContext)
require.NoError(t, err)
rootKey2, _, err := store.RootKey(nonDefaultRootKeyIDContext)
require.NoError(t, err) require.NoError(t, err)
// Both passwords must be set. // Both passwords must be set.
@ -242,9 +261,13 @@ func TestStoreChangePassword(t *testing.T) {
err = store.CreateUnlock(&newPw) err = store.CreateUnlock(&newPw)
require.NoError(t, err) require.NoError(t, err)
// Finally read the root key from the DB using the new password and // Finally, read the root keys from the DB using the new password and
// make sure the root key stayed the same. // make sure that both root keys stayed the same.
rootKeyDb, _, err := store.RootKey(defaultRootKeyIDContext) rootKeyDB1, _, err := store.RootKey(defaultRootKeyIDContext)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, rootKey, rootKeyDb) require.Equal(t, rootKey1, rootKeyDB1)
rootKeyDB2, _, err := store.RootKey(nonDefaultRootKeyIDContext)
require.NoError(t, err)
require.Equal(t, rootKey2, rootKeyDB2)
} }