diff --git a/docs/release-notes/release-notes-0.17.0.md b/docs/release-notes/release-notes-0.17.0.md index d2b3b3686..00132fdc0 100644 --- a/docs/release-notes/release-notes-0.17.0.md +++ b/docs/release-notes/release-notes-0.17.0.md @@ -49,6 +49,10 @@ unlock or create. * [Restore support](https://github.com/lightningnetwork/lnd/pull/7678) for `PKCS8`-encoded cert private keys. +* [Re-encrypt/regenerate](https://github.com/lightningnetwork/lnd/pull/7705) + all macaroon DB root keys on `ChangePassword`/`GenerateNewRootKey` + respectively. + ## Code Health * Updated [our fork for serializing protobuf as JSON to be based on the diff --git a/macaroons/store.go b/macaroons/store.go index 09da98885..cca548d98 100644 --- a/macaroons/store.go +++ b/macaroons/store.go @@ -54,6 +54,10 @@ var ( // ErrEncKeyNotFound specifies that there was no encryption key found // even if one was expected to be generated. 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. @@ -140,8 +144,8 @@ func (r *RootKeyStorage) CreateUnlock(password *[]byte) error { }, func() {}) } -// ChangePassword decrypts the macaroon root key with the old password and then -// encrypts it again with the new password. +// ChangePassword decrypts all the macaroon root keys with the old password and +// then encrypts them again with the new password. func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error { // We need the store to already be unlocked. With this we can make sure // that there already is a key in the DB. @@ -159,19 +163,18 @@ func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error { if bucket == nil { return ErrRootKeyBucketNotFound } - encKeyDb := bucket.Get(encryptionKeyID) - rootKeyDb := bucket.Get(DefaultRootKeyID) - // Both the encryption key and the root key must be present - // otherwise we are in the wrong state to change the password. - if len(encKeyDb) == 0 || len(rootKeyDb) == 0 { + // The encryption key must be present, otherwise we are in the + // wrong state to change the password. + encKeyDB := bucket.Get(encryptionKeyID) + if len(encKeyDB) == 0 { return ErrEncKeyNotFound } // Unmarshal parameters for old encryption key and derive the // old key with them. encKeyOld := &snacl.SecretKey{} - err := encKeyOld.Unmarshal(encKeyDb) + err := encKeyOld.Unmarshal(encKeyDB) if err != nil { return err } @@ -188,21 +191,42 @@ func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error { return err } - // 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(rootKeyDb) + // foundDefaultRootKey is used to keep track of if we have + // found and re-encrypted the default root key so that we can + // 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 { return err } - rootKey := make([]byte, len(decryptedKey)) - copy(rootKey, decryptedKey) - encryptedKey, err := encKeyNew.Encrypt(rootKey) - if err != nil { - return err - } - err = bucket.Put(DefaultRootKeyID, encryptedKey) - if err != nil { - return err + + if !foundDefaultRootKey { + return ErrDefaultRootKeyNotFound } // Finally, store the new encryption key parameters in the DB @@ -325,10 +349,34 @@ func (r *RootKeyStorage) GenerateNewRootKey() error { if bucket == nil { 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( 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() {}) } diff --git a/macaroons/store_test.go b/macaroons/store_test.go index a9a49edda..ace1e764a 100644 --- a/macaroons/store_test.go +++ b/macaroons/store_test.go @@ -16,6 +16,10 @@ var ( defaultRootKeyIDContext = macaroons.ContextWithRootKeyID( context.Background(), macaroons.DefaultRootKeyID, ) + + nonDefaultRootKeyIDContext = macaroons.ContextWithRootKeyID( + context.Background(), []byte{1}, + ) ) // 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) } -// TestStoreGenerateNewRootKey tests that a root key can be replaced with a new -// one in the store without changing the password. +// TestStoreGenerateNewRootKey tests that root keys can be replaced with new +// ones in the store without changing the password. func TestStoreGenerateNewRootKey(t *testing.T) { _, store := newTestStore(t) @@ -140,23 +144,33 @@ func TestStoreGenerateNewRootKey(t *testing.T) { err := store.GenerateNewRootKey() require.Equal(t, macaroons.ErrStoreLocked, err) - // Unlock the store and read the current key. + // Unlock the store. pw := []byte("weks") err = store.CreateUnlock(&pw) require.NoError(t, err) - oldRootKey, _, err := store.RootKey(defaultRootKeyIDContext) + + // Read the default root key. + oldRootKey1, _, err := store.RootKey(defaultRootKeyIDContext) 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() 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/ // 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.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. @@ -195,20 +209,25 @@ func TestStoreSetRootKey(t *testing.T) { } // 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) { 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) require.Equal(t, macaroons.ErrStoreLocked, err) - // Unlock the DB and read the current root key. This will need to stay - // the same after changing the password for the test to succeed. + // Unlock the DB and read the current default root key and one other + // non-default root key. Both of these should stay the same after + // changing the password for the test to succeed. pw := []byte("weks") err = store.CreateUnlock(&pw) 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) // Both passwords must be set. @@ -242,9 +261,13 @@ func TestStoreChangePassword(t *testing.T) { err = store.CreateUnlock(&newPw) require.NoError(t, err) - // Finally read the root key from the DB using the new password and - // make sure the root key stayed the same. - rootKeyDb, _, err := store.RootKey(defaultRootKeyIDContext) + // Finally, read the root keys from the DB using the new password and + // make sure that both root keys stayed the same. + rootKeyDB1, _, err := store.RootKey(defaultRootKeyIDContext) 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) }