411 lines
13 KiB
Go
411 lines
13 KiB
Go
package account
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"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/protocol/tt"
|
|
"github.com/status-im/status-go/t/utils"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
const testPassword = "test-password"
|
|
const newTestPassword = "new-test-password"
|
|
|
|
func TestVerifyAccountPassword(t *testing.T) {
|
|
accManager := NewGethManager(tt.MustCreateTestLogger())
|
|
keyStoreDir := t.TempDir()
|
|
emptyKeyStoreDir := t.TempDir()
|
|
|
|
// import account keys
|
|
utils.Init()
|
|
require.NoError(t, utils.ImportTestAccount(keyStoreDir, utils.GetAccount1PKFile()))
|
|
require.NoError(t, utils.ImportTestAccount(keyStoreDir, utils.GetAccount2PKFile()))
|
|
|
|
account1Address := types.BytesToAddress(types.FromHex(utils.TestConfig.Account1.WalletAddress))
|
|
|
|
testCases := []struct {
|
|
name string
|
|
keyPath string
|
|
address string
|
|
password string
|
|
expectedError error
|
|
}{
|
|
{
|
|
"correct address, correct password (decrypt should succeed)",
|
|
keyStoreDir,
|
|
utils.TestConfig.Account1.WalletAddress,
|
|
utils.TestConfig.Account1.Password,
|
|
nil,
|
|
},
|
|
{
|
|
"correct address, correct password, non-existent key store",
|
|
filepath.Join(keyStoreDir, "non-existent-folder"),
|
|
utils.TestConfig.Account1.WalletAddress,
|
|
utils.TestConfig.Account1.Password,
|
|
fmt.Errorf("cannot traverse key store folder: lstat %s/non-existent-folder: no such file or directory", keyStoreDir),
|
|
},
|
|
{
|
|
"correct address, correct password, empty key store (pk is not there)",
|
|
emptyKeyStoreDir,
|
|
utils.TestConfig.Account1.WalletAddress,
|
|
utils.TestConfig.Account1.Password,
|
|
ErrCannotLocateKeyFile{fmt.Sprintf("cannot locate account for address: %s", account1Address.Hex())},
|
|
},
|
|
{
|
|
"wrong address, correct password",
|
|
keyStoreDir,
|
|
"0x79791d3e8f2daa1f7fec29649d152c0ada3cc535",
|
|
utils.TestConfig.Account1.Password,
|
|
ErrCannotLocateKeyFile{fmt.Sprintf("cannot locate account for address: %s", "0x79791d3E8F2dAa1F7FeC29649d152c0aDA3cc535")},
|
|
},
|
|
{
|
|
"correct address, wrong password",
|
|
keyStoreDir,
|
|
utils.TestConfig.Account1.WalletAddress,
|
|
"wrong password", // wrong password
|
|
errors.New("could not decrypt key with given password"),
|
|
},
|
|
}
|
|
for _, testCase := range testCases {
|
|
accountKey, err := accManager.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
|
|
if testCase.expectedError != nil && err != nil && testCase.expectedError.Error() != err.Error() ||
|
|
((testCase.expectedError == nil || err == nil) && testCase.expectedError != err) {
|
|
require.FailNow(t, fmt.Sprintf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err))
|
|
}
|
|
if err == nil {
|
|
if accountKey == nil { // nolint: staticcheck
|
|
require.Fail(t, "no error reported, but account key is missing")
|
|
}
|
|
accountAddress := types.BytesToAddress(types.FromHex(testCase.address))
|
|
if accountKey.Address != accountAddress { // nolint: staticcheck
|
|
require.Fail(t, "account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestVerifyAccountPasswordWithAccountBeforeEIP55 verifies if VerifyAccountPassword
|
|
// can handle accounts before introduction of EIP55.
|
|
func TestVerifyAccountPasswordWithAccountBeforeEIP55(t *testing.T) {
|
|
keyStoreDir := t.TempDir()
|
|
|
|
// Import keys and make sure one was created before EIP55 introduction.
|
|
utils.Init()
|
|
err := utils.ImportTestAccount(keyStoreDir, "test-account3-before-eip55.pk")
|
|
require.NoError(t, err)
|
|
|
|
accManager := NewGethManager(tt.MustCreateTestLogger())
|
|
|
|
address := types.HexToAddress(utils.TestConfig.Account3.WalletAddress)
|
|
_, err = accManager.VerifyAccountPassword(keyStoreDir, address.Hex(), utils.TestConfig.Account3.Password)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestManagerTestSuite(t *testing.T) {
|
|
suite.Run(t, new(ManagerTestSuite))
|
|
}
|
|
|
|
type ManagerTestSuite struct {
|
|
suite.Suite
|
|
testAccount
|
|
accManager *GethManager
|
|
keydir string
|
|
}
|
|
|
|
type testAccount struct {
|
|
password string
|
|
walletAddress string
|
|
walletPubKey string
|
|
chatAddress string
|
|
chatPubKey string
|
|
mnemonic string
|
|
}
|
|
|
|
// SetupTest is used here for reinitializing the mock before every
|
|
// test function to avoid faulty execution.
|
|
func (s *ManagerTestSuite) SetupTest() {
|
|
s.accManager = NewGethManager(tt.MustCreateTestLogger())
|
|
|
|
keyStoreDir := s.T().TempDir()
|
|
s.Require().NoError(s.accManager.InitKeystore(keyStoreDir))
|
|
s.keydir = keyStoreDir
|
|
|
|
// Initial test - create test account
|
|
_, accountInfo, mnemonic, err := s.accManager.CreateAccount(testPassword)
|
|
s.Require().NoError(err)
|
|
s.Require().NotEmpty(accountInfo.WalletAddress)
|
|
s.Require().NotEmpty(accountInfo.WalletPubKey)
|
|
s.Require().NotEmpty(accountInfo.ChatAddress)
|
|
s.Require().NotEmpty(accountInfo.ChatPubKey)
|
|
s.Require().NotEmpty(mnemonic)
|
|
|
|
// Before the complete decoupling of the keys, wallet and chat keys are the same
|
|
s.Equal(accountInfo.WalletAddress, accountInfo.ChatAddress)
|
|
s.Equal(accountInfo.WalletPubKey, accountInfo.ChatPubKey)
|
|
|
|
s.testAccount = testAccount{
|
|
testPassword,
|
|
accountInfo.WalletAddress,
|
|
accountInfo.WalletPubKey,
|
|
accountInfo.ChatAddress,
|
|
accountInfo.ChatPubKey,
|
|
mnemonic,
|
|
}
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestRecoverAccount() {
|
|
accountInfo, err := s.accManager.RecoverAccount(s.password, s.mnemonic)
|
|
s.NoError(err)
|
|
s.Equal(s.walletAddress, accountInfo.WalletAddress)
|
|
s.Equal(s.walletPubKey, accountInfo.WalletPubKey)
|
|
s.Equal(s.chatAddress, accountInfo.ChatAddress)
|
|
s.Equal(s.chatPubKey, accountInfo.ChatPubKey)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestOnboarding() {
|
|
// try to choose an account before starting onboarding
|
|
_, _, err := s.accManager.ImportOnboardingAccount("test-id", "test-password")
|
|
s.Equal(ErrOnboardingNotStarted, err)
|
|
|
|
// generates 5 random accounts
|
|
count := 5
|
|
accounts, err := s.accManager.StartOnboarding(count, 24)
|
|
s.Require().NoError(err)
|
|
s.Equal(count, len(accounts))
|
|
|
|
// try to choose an account with an undefined id
|
|
_, _, err = s.accManager.ImportOnboardingAccount("test-id", "test-password")
|
|
s.Equal(ErrOnboardingAccountNotFound, err)
|
|
|
|
// choose one account and encrypt it with password
|
|
password := "test-onboarding-account"
|
|
account := accounts[0]
|
|
info, mnemonic, err := s.accManager.ImportOnboardingAccount(account.ID, password)
|
|
s.Require().NoError(err)
|
|
s.Equal(account.Info, info)
|
|
s.Equal(account.mnemonic, mnemonic)
|
|
s.Nil(s.accManager.onboarding)
|
|
|
|
// try to decrypt it with password to check if it's been imported correctly
|
|
decAccount, _, err := s.accManager.AddressToDecryptedAccount(info.WalletAddress, password)
|
|
s.Require().NoError(err)
|
|
s.Equal(info.WalletAddress, decAccount.Address.Hex())
|
|
|
|
// try resetting onboarding
|
|
_, err = s.accManager.StartOnboarding(count, 24)
|
|
s.Require().NoError(err)
|
|
s.NotNil(s.accManager.onboarding)
|
|
|
|
s.accManager.RemoveOnboarding()
|
|
s.Nil(s.accManager.onboarding)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestSelectAccountSuccess() {
|
|
s.testSelectAccount(types.HexToAddress(s.testAccount.chatAddress), types.HexToAddress(s.testAccount.walletAddress), s.testAccount.password, nil)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestSelectAccountWrongAddress() {
|
|
s.testSelectAccount(types.HexToAddress("0x0000000000000000000000000000000000000001"), types.HexToAddress(s.testAccount.walletAddress), s.testAccount.password, errors.New("cannot retrieve a valid key for a given account: no key for given address or file"))
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestSelectAccountWrongPassword() {
|
|
s.testSelectAccount(types.HexToAddress(s.testAccount.chatAddress), types.HexToAddress(s.testAccount.walletAddress), "wrong", errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given password"))
|
|
}
|
|
|
|
func (s *ManagerTestSuite) testSelectAccount(chat, wallet types.Address, password string, expErr error) {
|
|
loginParams := LoginParams{
|
|
ChatAddress: chat,
|
|
MainAccount: wallet,
|
|
Password: password,
|
|
}
|
|
err := s.accManager.SelectAccount(loginParams)
|
|
s.Require().Equal(expErr, err)
|
|
|
|
selectedMainAccountAddress, walletErr := s.accManager.MainAccountAddress()
|
|
selectedChatAccount, chatErr := s.accManager.SelectedChatAccount()
|
|
|
|
if expErr == nil {
|
|
s.Require().NoError(walletErr)
|
|
s.Require().NoError(chatErr)
|
|
s.Equal(wallet, selectedMainAccountAddress)
|
|
s.Equal(chat, crypto.PubkeyToAddress(selectedChatAccount.AccountKey.PrivateKey.PublicKey))
|
|
} else {
|
|
s.Equal(types.Address{}, selectedMainAccountAddress)
|
|
s.Nil(selectedChatAccount)
|
|
s.Equal(walletErr, ErrNoAccountSelected)
|
|
s.Equal(chatErr, ErrNoAccountSelected)
|
|
}
|
|
|
|
s.accManager.Logout()
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestSetChatAccount() {
|
|
s.accManager.Logout()
|
|
|
|
privKey, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
|
|
|
s.Require().NoError(s.accManager.SetChatAccount(privKey))
|
|
selectedChatAccount, err := s.accManager.SelectedChatAccount()
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(selectedChatAccount)
|
|
s.Equal(privKey, selectedChatAccount.AccountKey.PrivateKey)
|
|
s.Equal(address, selectedChatAccount.Address)
|
|
|
|
selectedMainAccountAddress, err := s.accManager.MainAccountAddress()
|
|
s.Error(err)
|
|
s.Equal(types.Address{}, selectedMainAccountAddress)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestLogout() {
|
|
s.accManager.Logout()
|
|
s.Equal(types.Address{}, s.accManager.mainAccountAddress)
|
|
s.Nil(s.accManager.selectedChatAccount)
|
|
s.Len(s.accManager.watchAddresses, 0)
|
|
}
|
|
|
|
// TestAccounts tests cases for (*Manager).Accounts.
|
|
func (s *ManagerTestSuite) TestAccounts() {
|
|
// Select the test account
|
|
loginParams := LoginParams{
|
|
MainAccount: types.HexToAddress(s.walletAddress),
|
|
ChatAddress: types.HexToAddress(s.chatAddress),
|
|
Password: s.password,
|
|
}
|
|
err := s.accManager.SelectAccount(loginParams)
|
|
s.NoError(err)
|
|
|
|
// Success
|
|
accs, err := s.accManager.Accounts()
|
|
s.NoError(err)
|
|
s.NotNil(accs)
|
|
// Selected main account address is zero address but doesn't fail
|
|
s.accManager.mainAccountAddress = types.Address{}
|
|
accs, err = s.accManager.Accounts()
|
|
s.NoError(err)
|
|
s.NotNil(accs)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestAddressToDecryptedAccountSuccess() {
|
|
s.testAddressToDecryptedAccount(s.walletAddress, s.password, nil)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestAddressToDecryptedAccountWrongAddress() {
|
|
s.testAddressToDecryptedAccount("0x0001", s.password, ErrAddressToAccountMappingFailure)
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestAddressToDecryptedAccountWrongPassword() {
|
|
s.testAddressToDecryptedAccount(s.walletAddress, "wrong", errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given password"))
|
|
}
|
|
|
|
func (s *ManagerTestSuite) testAddressToDecryptedAccount(wallet, password string, expErr error) {
|
|
acc, key, err := s.accManager.AddressToDecryptedAccount(wallet, password)
|
|
if expErr != nil {
|
|
s.Equal(expErr, err)
|
|
} else {
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(acc)
|
|
s.Require().NotNil(key)
|
|
s.Equal(acc.Address, key.Address)
|
|
}
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestMigrateKeyStoreDir() {
|
|
oldKeyDir := s.keydir
|
|
newKeyDir := filepath.Join(oldKeyDir, "new_dir")
|
|
err := os.Mkdir(newKeyDir, 0777)
|
|
s.Require().NoError(err)
|
|
|
|
files, _ := os.ReadDir(newKeyDir)
|
|
s.Equal(0, len(files))
|
|
|
|
address := types.HexToAddress(s.walletAddress).Hex()
|
|
addresses := []string{address}
|
|
err = s.accManager.MigrateKeyStoreDir(oldKeyDir, newKeyDir, addresses)
|
|
s.Require().NoError(err)
|
|
|
|
files, _ = os.ReadDir(newKeyDir)
|
|
s.Equal(1, len(files))
|
|
}
|
|
|
|
func (s *ManagerTestSuite) TestReEncryptKey() {
|
|
var firstKeyPath string
|
|
files, _ := os.ReadDir(s.keydir)
|
|
|
|
// there 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, _ := os.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 := os.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)
|
|
}
|