parent
2c0902837a
commit
81171ad9e6
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue