From 4ab7a290cdc6495a7ebcea9384cdc6622f2fd000 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 20 Feb 2015 11:36:50 +0100 Subject: accounts: use crypto/randentropy in test --- accounts/accounts_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'accounts') diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index da9406ebe..30e8c6285 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -1,8 +1,10 @@ package accounts import ( - "github.com/ethereum/go-ethereum/crypto" "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/randentropy" ) func TestAccountManager(t *testing.T) { @@ -10,7 +12,7 @@ func TestAccountManager(t *testing.T) { am := NewAccountManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) - toSign := crypto.GetEntropyCSPRNG(32) + toSign := randentropy.GetEntropyCSPRNG(32) _, err = am.Sign(a1, pass, toSign) if err != nil { t.Fatal(err) -- cgit v1.2.3 From 40adb7feb657cd1cb2e4c7a02c8a9db95b18e67c Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 23 Feb 2015 11:28:20 +0100 Subject: Implement OS sensitive dataDirs --- accounts/accounts_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'accounts') diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 30e8c6285..127334404 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -5,10 +5,11 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" + "github.com/ethereum/go-ethereum/ethutil" ) func TestAccountManager(t *testing.T) { - ks := crypto.NewKeyStorePlain(crypto.DefaultDataDir()) + ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir()) am := NewAccountManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) -- cgit v1.2.3 From 923950ccaaa4f9c1c0cebfbdd99fb0f16c47fd37 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Tue, 24 Feb 2015 18:03:10 +0100 Subject: Fix key store address hex decoding and accounts test Thanks to https://github.com/jaekwon for original fix! --- accounts/account_manager.go | 4 ++++ accounts/accounts_test.go | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'accounts') diff --git a/accounts/account_manager.go b/accounts/account_manager.go index da0bd8900..f7a7506ba 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -56,6 +56,10 @@ func NewAccountManager(keyStore crypto.KeyStore2) AccountManager { return *am } +func (am AccountManager) DeleteAccount(address []byte, auth string) error { + return am.keyStore.DeleteKey(address, auth) +} + func (am *AccountManager) Sign(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth) if err != nil { diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 127334404..4e97de545 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -9,7 +9,7 @@ import ( ) func TestAccountManager(t *testing.T) { - ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir()) + ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") am := NewAccountManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) @@ -18,4 +18,16 @@ func TestAccountManager(t *testing.T) { if err != nil { t.Fatal(err) } + + // Cleanup + accounts, err := am.Accounts() + if err != nil { + t.Fatal(err) + } + for _, account := range accounts { + err := am.DeleteAccount(account.Address, pass) + if err != nil { + t.Fatal(err) + } + } } -- cgit v1.2.3 From b296b36d2b2aaa2f81d26b3c133ace2714c58a7d Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 25 Feb 2015 17:29:23 +0100 Subject: Add automatic locking / unlocking of accounts * Change account signing API to two sign functions; Sign without passphrase - works if account is unlocked Sign with passphrase - always works and unlocks the account * Account stays unlocked for X ms and is then automatically locked --- accounts/account_manager.go | 48 ++++++++++++++++++++++++++++++++-------- accounts/accounts_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 11 deletions(-) (limited to 'accounts') diff --git a/accounts/account_manager.go b/accounts/account_manager.go index f7a7506ba..4d63bd0f2 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -34,24 +34,33 @@ package accounts import ( crand "crypto/rand" + "errors" "github.com/ethereum/go-ethereum/crypto" + "sync" + "time" ) +var ErrLocked = errors.New("account is locked; please request passphrase") + // TODO: better name for this struct? type Account struct { Address []byte } type AccountManager struct { - keyStore crypto.KeyStore2 + keyStore crypto.KeyStore2 + unlockedKeys map[string]crypto.Key + unlockedMilliSeconds int + mutex sync.Mutex } -// TODO: get key by addr - modify KeyStore2 GetKey to work with addr - -// TODO: pass through passphrase for APIs which require access to private key? -func NewAccountManager(keyStore crypto.KeyStore2) AccountManager { +func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliSeconds int) AccountManager { + keysMap := make(map[string]crypto.Key) am := &AccountManager{ - keyStore: keyStore, + keyStore: keyStore, + unlockedKeys: keysMap, + unlockedMilliSeconds: unlockMilliSeconds, + mutex: sync.Mutex{}, // for accessing unlockedKeys map } return *am } @@ -60,11 +69,26 @@ func (am AccountManager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } -func (am *AccountManager) Sign(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { +func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature []byte, err error) { + am.mutex.Lock() + unlockedKey := am.unlockedKeys[string(fromAccount.Address)] + am.mutex.Unlock() + if unlockedKey.Address == nil { + return nil, ErrLocked + } + signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey) + return signature, err +} + +func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth) if err != nil { return nil, err } + am.mutex.Lock() + am.unlockedKeys[string(fromAccount.Address)] = *key + am.mutex.Unlock() + go unlockLater(am, fromAccount.Address) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err } @@ -80,8 +104,6 @@ func (am AccountManager) NewAccount(auth string) (*Account, error) { return ua, err } -// set of accounts == set of keys in given key store -// TODO: do we need persistence of accounts as well? func (am *AccountManager) Accounts() ([]Account, error) { addresses, err := am.keyStore.GetKeyAddresses() if err != nil { @@ -97,3 +119,11 @@ func (am *AccountManager) Accounts() ([]Account, error) { } return accounts, err } + +func unlockLater(am *AccountManager, addr []byte) { + time.Sleep(time.Millisecond * time.Duration(am.unlockedMilliSeconds)) + am.mutex.Lock() + // TODO: how do we know the key is actually gone from memory? + delete(am.unlockedKeys, string(addr)) + am.mutex.Unlock() +} diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 4e97de545..8f036fd1f 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -6,19 +6,68 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/ethutil" + "time" ) func TestAccountManager(t *testing.T) { ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks) + am := NewAccountManager(ks, 100) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) - _, err = am.Sign(a1, pass, toSign) + _, err = am.SignLocked(a1, pass, toSign) if err != nil { t.Fatal(err) } + // Cleanup + time.Sleep(time.Millisecond * time.Duration(150)) // wait for locking + + accounts, err := am.Accounts() + if err != nil { + t.Fatal(err) + } + for _, account := range accounts { + err := am.DeleteAccount(account.Address, pass) + if err != nil { + t.Fatal(err) + } + } +} + +func TestAccountManagerLocking(t *testing.T) { + ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") + am := NewAccountManager(ks, 200) + pass := "foo" + a1, err := am.NewAccount(pass) + toSign := randentropy.GetEntropyCSPRNG(32) + + // Signing without passphrase fails because account is locked + _, err = am.Sign(a1, toSign) + if err != ErrLocked { + t.Fatal(err) + } + + // Signing with passphrase works + _, err = am.SignLocked(a1, pass, toSign) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = am.Sign(a1, toSign) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase fails after automatic locking + time.Sleep(time.Millisecond * time.Duration(250)) + + _, err = am.Sign(a1, toSign) + if err != ErrLocked { + t.Fatal(err) + } + // Cleanup accounts, err := am.Accounts() if err != nil { -- cgit v1.2.3 From d1311c53eee916ae8472a3b7eaf40b5e94f7675f Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 25 Feb 2015 18:40:59 +0100 Subject: Address pull request comments * Use RWMutex instead of Mutex * Use time.Duration instead of int for unlock time * Use time.After with select instead of time.Sleep --- accounts/account_manager.go | 34 ++++++++++++++++++---------------- accounts/accounts_test.go | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) (limited to 'accounts') diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 4d63bd0f2..38dd6f736 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -48,19 +48,19 @@ type Account struct { } type AccountManager struct { - keyStore crypto.KeyStore2 - unlockedKeys map[string]crypto.Key - unlockedMilliSeconds int - mutex sync.Mutex + keyStore crypto.KeyStore2 + unlockedKeys map[string]crypto.Key + unlockMilliseconds time.Duration + mutex sync.RWMutex } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliSeconds int) AccountManager { +func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { keysMap := make(map[string]crypto.Key) am := &AccountManager{ - keyStore: keyStore, - unlockedKeys: keysMap, - unlockedMilliSeconds: unlockMilliSeconds, - mutex: sync.Mutex{}, // for accessing unlockedKeys map + keyStore: keyStore, + unlockedKeys: keysMap, + unlockMilliseconds: unlockMilliseconds, + mutex: sync.RWMutex{}, // for accessing unlockedKeys map } return *am } @@ -70,9 +70,9 @@ func (am AccountManager) DeleteAccount(address []byte, auth string) error { } func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature []byte, err error) { - am.mutex.Lock() + am.mutex.RLock() unlockedKey := am.unlockedKeys[string(fromAccount.Address)] - am.mutex.Unlock() + am.mutex.RUnlock() if unlockedKey.Address == nil { return nil, ErrLocked } @@ -85,9 +85,9 @@ func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSig if err != nil { return nil, err } - am.mutex.Lock() + am.mutex.RLock() am.unlockedKeys[string(fromAccount.Address)] = *key - am.mutex.Unlock() + am.mutex.RUnlock() go unlockLater(am, fromAccount.Address) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err @@ -121,9 +121,11 @@ func (am *AccountManager) Accounts() ([]Account, error) { } func unlockLater(am *AccountManager, addr []byte) { - time.Sleep(time.Millisecond * time.Duration(am.unlockedMilliSeconds)) - am.mutex.Lock() + select { + case <-time.After(time.Millisecond * am.unlockMilliseconds): + } + am.mutex.RLock() // TODO: how do we know the key is actually gone from memory? delete(am.unlockedKeys, string(addr)) - am.mutex.Unlock() + am.mutex.RUnlock() } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 8f036fd1f..44d1d72f1 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -21,7 +21,7 @@ func TestAccountManager(t *testing.T) { } // Cleanup - time.Sleep(time.Millisecond * time.Duration(150)) // wait for locking + time.Sleep(time.Millisecond * 150) // wait for locking accounts, err := am.Accounts() if err != nil { -- cgit v1.2.3 From 23f265809170fae044be12851f5591f55495003a Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Wed, 25 Feb 2015 19:30:57 +0100 Subject: Remove unneeded initialisation of mutex --- accounts/account_manager.go | 1 - 1 file changed, 1 deletion(-) (limited to 'accounts') diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 38dd6f736..90fed1343 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -60,7 +60,6 @@ func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Durati keyStore: keyStore, unlockedKeys: keysMap, unlockMilliseconds: unlockMilliseconds, - mutex: sync.RWMutex{}, // for accessing unlockedKeys map } return *am } -- cgit v1.2.3 From 5ab0eaa06d2f5879b9b22778988410bd0c73dcc0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Feb 2015 11:14:54 +0100 Subject: wip --- accounts/account_manager.go | 1 + 1 file changed, 1 insertion(+) (limited to 'accounts') diff --git a/accounts/account_manager.go b/accounts/account_manager.go index f7a7506ba..f1ad450e6 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -34,6 +34,7 @@ package accounts import ( crand "crypto/rand" + "github.com/ethereum/go-ethereum/crypto" ) -- cgit v1.2.3