mirror of https://github.com/status-im/op-geth.git
accounts: add {Timed,}Unlock, remove SignLocked
This commit is contained in:
parent
9bf513e993
commit
487f68ec48
|
@ -54,10 +54,9 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
keyStore crypto.KeyStore2
|
keyStore crypto.KeyStore2
|
||||||
unlocked map[string]*unlocked
|
unlocked map[string]*unlocked
|
||||||
unlockTime time.Duration
|
mutex sync.RWMutex
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type unlocked struct {
|
type unlocked struct {
|
||||||
|
@ -65,11 +64,10 @@ type unlocked struct {
|
||||||
abort chan struct{}
|
abort chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager {
|
func NewManager(keyStore crypto.KeyStore2) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
keyStore: keyStore,
|
keyStore: keyStore,
|
||||||
unlocked: make(map[string]*unlocked),
|
unlocked: make(map[string]*unlocked),
|
||||||
unlockTime: unlockTime,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,15 +113,28 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error)
|
||||||
return signature, err
|
return signature, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) {
|
// TimedUnlock unlocks the account with the given address.
|
||||||
key, err := am.keyStore.GetKey(a.Address, keyAuth)
|
// When timeout has passed, the account will be locked again.
|
||||||
|
func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error {
|
||||||
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
u := am.addUnlocked(a.Address, key)
|
u := am.addUnlocked(addr, key)
|
||||||
go am.dropLater(a.Address, u)
|
go am.dropLater(addr, u, timeout)
|
||||||
signature, err = crypto.Sign(toSign, key.PrivateKey)
|
return nil
|
||||||
return signature, err
|
}
|
||||||
|
|
||||||
|
// Unlock unlocks the account with the given address. The account
|
||||||
|
// stays unlocked until the program exits or until a TimedUnlock
|
||||||
|
// timeout (started after the call to Unlock) expires.
|
||||||
|
func (am *Manager) Unlock(addr []byte, keyAuth string) error {
|
||||||
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
am.addUnlocked(addr, key)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) NewAccount(auth string) (Account, error) {
|
func (am *Manager) NewAccount(auth string) (Account, error) {
|
||||||
|
@ -155,6 +166,9 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
|
||||||
if found {
|
if found {
|
||||||
// terminate dropLater for this key to avoid unexpected drops.
|
// terminate dropLater for this key to avoid unexpected drops.
|
||||||
close(prev.abort)
|
close(prev.abort)
|
||||||
|
// the key is zeroed here instead of in dropLater because
|
||||||
|
// there might not actually be a dropLater running for this
|
||||||
|
// key, i.e. when Unlock was used.
|
||||||
zeroKey(prev.PrivateKey)
|
zeroKey(prev.PrivateKey)
|
||||||
}
|
}
|
||||||
am.unlocked[string(addr)] = u
|
am.unlocked[string(addr)] = u
|
||||||
|
@ -162,8 +176,8 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) dropLater(addr []byte, u *unlocked) {
|
func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
|
||||||
t := time.NewTimer(am.unlockTime)
|
t := time.NewTimer(timeout)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
select {
|
select {
|
||||||
case <-u.abort:
|
case <-u.abort:
|
||||||
|
|
|
@ -1,44 +1,36 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||||
"github.com/ethereum/go-ethereum/ethutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccountManager(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts")
|
dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain)
|
||||||
am := NewManager(ks, 100*time.Millisecond)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
am := NewManager(ks)
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
a1, err := am.NewAccount(pass)
|
a1, err := am.NewAccount(pass)
|
||||||
toSign := randentropy.GetEntropyCSPRNG(32)
|
toSign := randentropy.GetEntropyCSPRNG(32)
|
||||||
_, err = am.SignLocked(a1, pass, toSign)
|
am.Unlock(a1.Address, "")
|
||||||
|
|
||||||
|
_, err = am.Sign(a1, toSign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
time.Sleep(150 * time.Millisecond) // 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) {
|
func TestTimedUnlock(t *testing.T) {
|
||||||
ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts")
|
dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase)
|
||||||
am := NewManager(ks, 200*time.Millisecond)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
am := NewManager(ks)
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := am.NewAccount(pass)
|
a1, err := am.NewAccount(pass)
|
||||||
toSign := randentropy.GetEntropyCSPRNG(32)
|
toSign := randentropy.GetEntropyCSPRNG(32)
|
||||||
|
@ -46,38 +38,32 @@ func TestAccountManagerLocking(t *testing.T) {
|
||||||
// Signing without passphrase fails because account is locked
|
// Signing without passphrase fails because account is locked
|
||||||
_, err = am.Sign(a1, toSign)
|
_, err = am.Sign(a1, toSign)
|
||||||
if err != ErrLocked {
|
if err != ErrLocked {
|
||||||
t.Fatal(err)
|
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signing with passphrase works
|
// Signing with passphrase works
|
||||||
_, err = am.SignLocked(a1, pass, toSign)
|
if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signing without passphrase works because account is temp unlocked
|
// Signing without passphrase works because account is temp unlocked
|
||||||
_, err = am.Sign(a1, toSign)
|
_, err = am.Sign(a1, toSign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signing without passphrase fails after automatic locking
|
// Signing fails again after automatic locking
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
_, err = am.Sign(a1, toSign)
|
_, err = am.Sign(a1, toSign)
|
||||||
if err != ErrLocked {
|
if err != ErrLocked {
|
||||||
t.Fatal(err)
|
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup
|
func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore2) (string, crypto.KeyStore2) {
|
||||||
accounts, err := am.Accounts()
|
d, err := ioutil.TempDir("", "eth-keystore-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, account := range accounts {
|
return d, new(d)
|
||||||
err := am.DeleteAccount(account.Address, pass)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
@ -199,7 +198,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D
|
||||||
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
|
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
|
||||||
dataDir := ctx.GlobalString(DataDirFlag.Name)
|
dataDir := ctx.GlobalString(DataDirFlag.Name)
|
||||||
ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys"))
|
ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys"))
|
||||||
return accounts.NewManager(ks, 300*time.Second)
|
return accounts.NewManager(ks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartRPC(eth *eth.Ethereum, ctx *cli.Context) {
|
func StartRPC(eth *eth.Ethereum, ctx *cli.Context) {
|
||||||
|
|
Loading…
Reference in New Issue