feat: Make DB re-encryption fault proof (#3607)
Improve the error management in order to avoid DB corruption in case the process is killed while encrypting the DB. Changes: Use sqlcipher_export instead of rekey to change the DB password. The advantage is that sqlcipher_export will operate on a new DB file and we don't need to modify the current account unless the export is successful. Keeping the rekey requires to create a DB copy before trying to re-encrypt the DB, but the DB copy is risky in case the DB file changes wile the copy is in progress. It could also lead to DB corruption.
This commit is contained in:
parent
43b2c3b7ce
commit
3978048afa
|
@ -749,8 +749,7 @@ func (b *GethStatusBackend) ImportUnencryptedDatabase(acc multiaccounts.Account,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password string, newPassword string) error {
|
||||
dbPath := filepath.Join(b.rootDataDir, fmt.Sprintf("%s-v4.db", keyUID))
|
||||
func (b *GethStatusBackend) reEncryptKeyStoreDir(currentPassword string, newPassword string) error {
|
||||
config := b.StatusNode().Config()
|
||||
keyDir := ""
|
||||
if config == nil {
|
||||
|
@ -760,25 +759,74 @@ func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password strin
|
|||
}
|
||||
|
||||
if keyDir != "" {
|
||||
err := b.accountManager.ReEncryptKeyStoreDir(keyDir, password, newPassword)
|
||||
err := b.accountManager.ReEncryptKeyStoreDir(keyDir, currentPassword, newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReEncryptKeyStoreDir error: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(keyUID)
|
||||
func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password string, newPassword string) error {
|
||||
dbPath := filepath.Join(b.rootDataDir, fmt.Sprintf("%s-v4.db", keyUID))
|
||||
|
||||
account, err := b.multiaccountsDB.GetAccount(keyUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = appdatabase.ChangeDatabasePassword(dbPath, password, kdfIterations, newPassword)
|
||||
|
||||
file, err := os.CreateTemp("", "*-v4.db")
|
||||
if err != nil {
|
||||
if config != nil {
|
||||
keyDir := config.KeyStoreDir
|
||||
// couldn't change db password so undo keystore changes to mainitain consistency
|
||||
_ = b.accountManager.ReEncryptKeyStoreDir(keyDir, newPassword, password)
|
||||
return err
|
||||
}
|
||||
|
||||
newDBPath := file.Name()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
_ = os.Remove(newDBPath)
|
||||
_ = os.Remove(newDBPath + "-wal")
|
||||
_ = os.Remove(newDBPath + "-shm")
|
||||
_ = os.Remove(newDBPath + "-journal")
|
||||
}()
|
||||
|
||||
// Exporting database to a temporary file with a new password
|
||||
err = appdatabase.ExportDB(dbPath, password, account.KDFIterations, newDBPath, newPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.reEncryptKeyStoreDir(password, newPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replacing the old database with the new one requires closing all connections to the database
|
||||
// This is done by stopping the node and restarting it with the new DB
|
||||
appDBPath, _ := appdatabase.GetDBFilename(b.appDB)
|
||||
changeCurrentAccountPassword := appDBPath == dbPath
|
||||
if changeCurrentAccountPassword {
|
||||
_ = b.Logout()
|
||||
}
|
||||
|
||||
// Replacing the old database files with the new ones, ignoring the wal and shm errors
|
||||
err = os.Rename(newDBPath, dbPath)
|
||||
if err != nil {
|
||||
// Restore the old account
|
||||
_ = b.reEncryptKeyStoreDir(newPassword, password)
|
||||
if changeCurrentAccountPassword {
|
||||
_ = b.startNodeWithAccount(*account, password, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
_ = os.Remove(dbPath + "-wal")
|
||||
_ = os.Remove(dbPath + "-shm")
|
||||
_ = os.Rename(newDBPath+"-wal", dbPath+"-wal")
|
||||
_ = os.Rename(newDBPath+"-shm", dbPath+"-shm")
|
||||
|
||||
if changeCurrentAccountPassword {
|
||||
return b.startNodeWithAccount(*account, newPassword, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,10 @@ func EncryptDatabase(oldPath, newPath, password string, kdfIterationsNumber int)
|
|||
return sqlite.EncryptDB(oldPath, newPath, password, kdfIterationsNumber)
|
||||
}
|
||||
|
||||
func ExportDB(path string, password string, kdfIterationsNumber int, newDbPAth string, newPassword string) error {
|
||||
return sqlite.ExportDB(path, password, kdfIterationsNumber, newDbPAth, newPassword)
|
||||
}
|
||||
|
||||
func ChangeDatabasePassword(path string, password string, kdfIterationsNumber int, newPassword string) error {
|
||||
return sqlite.ChangeEncryptionKey(path, password, kdfIterationsNumber, newPassword)
|
||||
}
|
||||
|
|
|
@ -102,6 +102,16 @@ func EncryptDB(unencryptedPath string, encryptedPath string, key string, kdfIter
|
|||
return encryptDB(db, encryptedPath, key, kdfIterationsNumber)
|
||||
}
|
||||
|
||||
// Export takes an encrypted database and re-encrypts it in a new file, with a new key
|
||||
func ExportDB(encryptedPath string, key string, kdfIterationsNumber int, newPath string, newKey string) error {
|
||||
db, err := openDB(encryptedPath, key, kdfIterationsNumber, V4CipherPageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return encryptDB(db, newPath, newKey, kdfIterationsNumber)
|
||||
}
|
||||
|
||||
func buildSqlcipherDSN(path string) (string, error) {
|
||||
if path == InMemoryPath {
|
||||
return InMemoryPath, nil
|
||||
|
|
Loading…
Reference in New Issue