parent
2c0902837a
commit
81171ad9e6
|
@ -12,6 +12,9 @@ import (
|
|||
|
||||
"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/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/keystore"
|
||||
|
@ -477,3 +480,116 @@ func (m *Manager) MigrateKeyStoreDir(oldDir, newDir string, addresses []string)
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"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/t/utils"
|
||||
|
||||
|
@ -17,6 +19,9 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const testPassword = "test-password"
|
||||
const newTestPassword = "new-test-password"
|
||||
|
||||
func TestVerifyAccountPassword(t *testing.T) {
|
||||
accManager := NewGethManager()
|
||||
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||
|
@ -143,8 +148,6 @@ func (s *ManagerTestSuite) SetupTest() {
|
|||
s.Require().NoError(s.accManager.InitKeystore(keyStoreDir))
|
||||
s.keydir = keyStoreDir
|
||||
|
||||
testPassword := "test-password"
|
||||
|
||||
// Initial test - create test account
|
||||
_, accountInfo, mnemonic, err := s.accManager.CreateAccount(testPassword)
|
||||
s.Require().NoError(err)
|
||||
|
@ -348,3 +351,72 @@ func (s *ManagerTestSuite) TestMigrateKeyStoreDir() {
|
|||
files, _ = ioutil.ReadDir(newKeyDir)
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -459,6 +459,25 @@ 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.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 {
|
||||
err := b.SaveAccount(acc)
|
||||
if err != nil {
|
||||
|
|
|
@ -31,3 +31,7 @@ func DecryptDatabase(oldPath, newPath, password string) error {
|
|||
func EncryptDatabase(oldPath, newPath, password string) error {
|
||||
return sqlite.EncryptDB(oldPath, newPath, password)
|
||||
}
|
||||
|
||||
func ChangeDatabasePassword(path, password, newPassword string) error {
|
||||
return sqlite.ChangeEncryptionKey(path, password, newPassword)
|
||||
}
|
||||
|
|
|
@ -328,3 +328,13 @@ func pkcs7Unpad(in []byte) []byte {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -687,3 +687,11 @@ func ImportUnencryptedDatabase(accountData, password, databasePath string) strin
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -137,3 +137,18 @@ func OpenUnecryptedDB(path string) (*sql.DB, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue