Expose password reset function (#2223)

Handled edge-cases
This commit is contained in:
Shivek Khurana 2021-06-23 14:51:21 +05:30 committed by GitHub
parent 2c0902837a
commit 81171ad9e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 3 deletions

View File

@ -1 +1 @@
0.79.15 0.80.00

View File

@ -12,6 +12,9 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account/generator" "github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/keystore" "github.com/status-im/status-go/eth-node/keystore"
@ -477,3 +480,116 @@ func (m *Manager) MigrateKeyStoreDir(oldDir, newDir string, addresses []string)
return nil return nil
} }
func (m *Manager) ReEncryptKey(rawKey []byte, pass string, newPass string) (reEncryptedKey []byte, e error) {
cryptoJSON, e := keystore.RawKeyToCryptoJSON(rawKey)
if e != nil {
return reEncryptedKey, fmt.Errorf("convert to crypto json error: %v", e)
}
decryptedKey, e := keystore.DecryptKey(rawKey, pass)
if e != nil {
return reEncryptedKey, fmt.Errorf("decryption error: %v", e)
}
if cryptoJSON.KDFParams["n"] == nil || cryptoJSON.KDFParams["p"] == nil {
return reEncryptedKey, fmt.Errorf("Unable to determine `n` or `p`: %v", e)
}
n := int(cryptoJSON.KDFParams["n"].(float64))
p := int(cryptoJSON.KDFParams["p"].(float64))
gethKey := gethkeystore.Key{
Id: decryptedKey.ID,
Address: gethcommon.Address(decryptedKey.Address),
PrivateKey: decryptedKey.PrivateKey,
ExtendedKey: decryptedKey.ExtendedKey,
SubAccountIndex: decryptedKey.SubAccountIndex,
}
return gethkeystore.EncryptKey(&gethKey, newPass, n, p)
}
func (m *Manager) ReEncryptKeyStoreDir(keyDirPath, oldPass, newPass string) error {
rencryptFileAtPath := func(tempKeyDirPath, path string, fileInfo os.FileInfo) error {
if fileInfo.IsDir() {
return nil
}
rawKeyFile, e := ioutil.ReadFile(path)
if e != nil {
return fmt.Errorf("invalid account key file: %v", e)
}
reEncryptedKey, e := m.ReEncryptKey(rawKeyFile, oldPass, newPass)
if e != nil {
return fmt.Errorf("unable to re-encrypt key file: %v, path: %s, name: %s", e, path, fileInfo.Name())
}
tempWritePath := filepath.Join(tempKeyDirPath, fileInfo.Name())
e = ioutil.WriteFile(tempWritePath, reEncryptedKey, fileInfo.Mode().Perm())
if e != nil {
return fmt.Errorf("unable write key file: %v", e)
}
return nil
}
keyParent, keyDirName := filepath.Split(keyDirPath)
// backupKeyDirName used to store existing keys before final write
backupKeyDirName := keyDirName + "-backup"
// tempKeyDirName used to put re-encrypted keys
tempKeyDirName := keyDirName + "-re-encrypted"
backupKeyDirPath := filepath.Join(keyParent, backupKeyDirName)
tempKeyDirPath := filepath.Join(keyParent, tempKeyDirName)
// create temp key dir
err := os.MkdirAll(tempKeyDirPath, os.ModePerm)
if err != nil {
return fmt.Errorf("mkdirall error: %v, tempKeyDirPath: %s", err, tempKeyDirPath)
}
err = filepath.Walk(keyDirPath, func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
os.RemoveAll(tempKeyDirPath)
return fmt.Errorf("walk callback error: %v", err)
}
return rencryptFileAtPath(tempKeyDirPath, path, fileInfo)
})
if err != nil {
os.RemoveAll(tempKeyDirPath)
return fmt.Errorf("walk error: %v", err)
}
// move existing keys
err = os.Rename(keyDirPath, backupKeyDirPath)
if err != nil {
os.RemoveAll(tempKeyDirPath)
return fmt.Errorf("unable to rename keyDirPath to backupKeyDirPath: %v", err)
}
// move tempKeyDirPath to keyDirPath
err = os.Rename(tempKeyDirPath, keyDirPath)
if err != nil {
// if this happens, then the app is probably bricked, because the keystore won't exist anymore
// try to restore from backup
_ = os.Rename(backupKeyDirPath, keyDirPath)
return fmt.Errorf("unable to rename tempKeyDirPath to keyDirPath: %v", err)
}
// remove temp and backup folders and their contents
err = os.RemoveAll(tempKeyDirPath)
if err != nil {
// the re-encryption is complete so we don't throw
log.Error("unable to delete tempKeyDirPath, manual cleanup required")
}
err = os.RemoveAll(backupKeyDirPath)
if err != nil {
// the re-encryption is complete so we don't throw
log.Error("unable to delete backupKeyDirPath, manual cleanup required")
}
return nil
}

View File

@ -1,6 +1,7 @@
package account package account
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -10,6 +11,7 @@ import (
"testing" "testing"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/keystore"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/t/utils" "github.com/status-im/status-go/t/utils"
@ -17,6 +19,9 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
const testPassword = "test-password"
const newTestPassword = "new-test-password"
func TestVerifyAccountPassword(t *testing.T) { func TestVerifyAccountPassword(t *testing.T) {
accManager := NewGethManager() accManager := NewGethManager()
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts") keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
@ -143,8 +148,6 @@ func (s *ManagerTestSuite) SetupTest() {
s.Require().NoError(s.accManager.InitKeystore(keyStoreDir)) s.Require().NoError(s.accManager.InitKeystore(keyStoreDir))
s.keydir = keyStoreDir s.keydir = keyStoreDir
testPassword := "test-password"
// Initial test - create test account // Initial test - create test account
_, accountInfo, mnemonic, err := s.accManager.CreateAccount(testPassword) _, accountInfo, mnemonic, err := s.accManager.CreateAccount(testPassword)
s.Require().NoError(err) s.Require().NoError(err)
@ -348,3 +351,72 @@ func (s *ManagerTestSuite) TestMigrateKeyStoreDir() {
files, _ = ioutil.ReadDir(newKeyDir) files, _ = ioutil.ReadDir(newKeyDir)
s.Equal(1, len(files)) s.Equal(1, len(files))
} }
func (s *ManagerTestSuite) TestReEncryptKey() {
var firstKeyPath string
files, _ := ioutil.ReadDir(s.keydir)
// thiere is only one file in this dir,
// is there a better way to reference it?
for _, f := range files {
firstKeyPath = filepath.Join(s.keydir, f.Name())
}
rawKey, _ := ioutil.ReadFile(firstKeyPath)
reEncryptedKey, _ := s.accManager.ReEncryptKey(rawKey, testPassword, newTestPassword)
type Key struct {
Address string `json:"address"`
}
var unmarshaledRaw, unmarshaledReEncrypted Key
_ = json.Unmarshal(rawKey, &unmarshaledRaw)
_ = json.Unmarshal(reEncryptedKey, &unmarshaledReEncrypted)
oldCrypto, _ := keystore.RawKeyToCryptoJSON(rawKey)
newCrypto, _ := keystore.RawKeyToCryptoJSON(reEncryptedKey)
// Test address is same post re-encryption
s.Equal(unmarshaledRaw.Address, unmarshaledReEncrypted.Address)
// Test cipher changes after re-encryption
s.NotEqual(oldCrypto.CipherText, newCrypto.CipherText)
// Test re-encrypted key cannot be decrypted using old testPasswordword
_, decryptOldError := keystore.DecryptKey(reEncryptedKey, testPassword)
s.Require().Error(decryptOldError)
// Test re-encrypted key can be decrypted using new testPassword
_, decryptNewError := keystore.DecryptKey(reEncryptedKey, newTestPassword)
s.Require().NoError(decryptNewError)
}
func (s *ManagerTestSuite) TestReEncryptKeyStoreDir() {
err := s.accManager.ReEncryptKeyStoreDir(s.keydir, testPassword, newTestPassword)
s.Require().NoError(err)
err = filepath.Walk(s.keydir, func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo.IsDir() {
return nil
}
// walk should not throw callback errors
s.Require().NoError(err)
rawKeyFile, err := ioutil.ReadFile(path)
s.Require().NoError(err)
// should not decrypt with old password
_, decryptError := keystore.DecryptKey(rawKeyFile, testPassword)
s.Require().Error(decryptError)
// should decrypt with new password
_, decryptError = keystore.DecryptKey(rawKeyFile, newTestPassword)
s.Require().NoError(decryptError)
return nil
})
s.Require().NoError(err)
}

View File

@ -459,6 +459,25 @@ func (b *GethStatusBackend) ImportUnencryptedDatabase(acc multiaccounts.Account,
return nil return nil
} }
func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password string, newPassword string) error {
dbPath := filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", keyUID))
config := b.StatusNode().Config()
keyDir := config.KeyStoreDir
err := b.accountManager.ReEncryptKeyStoreDir(keyDir, password, newPassword)
if err != nil {
return fmt.Errorf("ReEncryptKeyStoreDir error: %v", err)
}
err = appdatabase.ChangeDatabasePassword(dbPath, password, newPassword)
if err != nil {
// couldn't change db password so undo keystore changes to mainitain consistency
_ = b.accountManager.ReEncryptKeyStoreDir(keyDir, newPassword, password)
return err
}
return nil
}
func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(acc multiaccounts.Account, password string, settings accounts.Settings, nodecfg *params.NodeConfig, subaccs []accounts.Account, keyHex string) error { func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(acc multiaccounts.Account, password string, settings accounts.Settings, nodecfg *params.NodeConfig, subaccs []accounts.Account, keyHex string) error {
err := b.SaveAccount(acc) err := b.SaveAccount(acc)
if err != nil { if err != nil {

View File

@ -31,3 +31,7 @@ func DecryptDatabase(oldPath, newPath, password string) error {
func EncryptDatabase(oldPath, newPath, password string) error { func EncryptDatabase(oldPath, newPath, password string) error {
return sqlite.EncryptDB(oldPath, newPath, password) return sqlite.EncryptDB(oldPath, newPath, password)
} }
func ChangeDatabasePassword(path, password, newPassword string) error {
return sqlite.ChangeEncryptionKey(path, password, newPassword)
}

View File

@ -328,3 +328,13 @@ func pkcs7Unpad(in []byte) []byte {
} }
return in[:len(in)-int(padding)] return in[:len(in)-int(padding)]
} }
func RawKeyToCryptoJSON(rawKeyFile []byte) (cj CryptoJSON, e error){
var keyJSON encryptedKeyJSONV3
if e := json.Unmarshal(rawKeyFile, &keyJSON); e != nil {
return cj, fmt.Errorf("failed to read key file: %s", e)
}
return keyJSON.Crypto, e
}

View File

@ -687,3 +687,11 @@ func ImportUnencryptedDatabase(accountData, password, databasePath string) strin
} }
return makeJSONResponse(nil) return makeJSONResponse(nil)
} }
func ChangeDatabasePassword(keyUID, password, newPassword string) string {
err := statusBackend.ChangeDatabasePassword(keyUID, password, newPassword)
if err != nil {
return makeJSONResponse(err)
}
return makeJSONResponse(nil)
}

View File

@ -137,3 +137,18 @@ func OpenUnecryptedDB(path string) (*sql.DB, error) {
return db, nil return db, nil
} }
func ChangeEncryptionKey(path, key, newKey string) error {
db, err := openDB(path, key)
if err != nil {
return err
}
resetKeyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey)
if _, err = db.Exec(resetKeyString); err != nil {
return errors.New("failed to set rekey pragma")
}
return nil
}