Commit 3750ec7b authored by Felix Lange's avatar Felix Lange

accounts: prevent early drops and zero keys in memory when dropping

Private keys would be locked early if SignLocked was called more than
once because the unlockLater was still running. Terminate it properly.
parent d6a73329
...@@ -54,15 +54,22 @@ type Account struct { ...@@ -54,15 +54,22 @@ type Account struct {
type AccountManager struct { type AccountManager struct {
keyStore crypto.KeyStore2 keyStore crypto.KeyStore2
unlockedKeys map[string]crypto.Key unlocked map[string]*unlocked
unlockTime time.Duration unlockTime time.Duration
mutex sync.RWMutex mutex sync.RWMutex
} }
type unlocked struct {
addr []byte
abort chan struct{}
*crypto.Key
}
func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *AccountManager { func NewAccountManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *AccountManager {
return &AccountManager{ return &AccountManager{
keyStore: keyStore, keyStore: keyStore,
unlockedKeys: make(map[string]crypto.Key), unlocked: make(map[string]*unlocked),
unlockTime: unlockTime, unlockTime: unlockTime,
} }
} }
...@@ -97,9 +104,9 @@ func (am *AccountManager) DeleteAccount(address []byte, auth string) error { ...@@ -97,9 +104,9 @@ func (am *AccountManager) DeleteAccount(address []byte, auth string) error {
func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err error) { func (am *AccountManager) Sign(a Account, toSign []byte) (signature []byte, err error) {
am.mutex.RLock() am.mutex.RLock()
unlockedKey := am.unlockedKeys[string(a.Address)] unlockedKey, found := am.unlocked[string(a.Address)]
am.mutex.RUnlock() am.mutex.RUnlock()
if unlockedKey.Address == nil { if !found {
return nil, ErrLocked return nil, ErrLocked
} }
signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey) signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
...@@ -111,10 +118,8 @@ func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) ( ...@@ -111,10 +118,8 @@ func (am *AccountManager) SignLocked(a Account, keyAuth string, toSign []byte) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
am.mutex.Lock() u := am.addUnlocked(a.Address, key)
am.unlockedKeys[string(a.Address)] = *key go am.dropLater(u)
am.mutex.Unlock()
go unlockLater(am, a.Address)
signature, err = crypto.Sign(toSign, key.PrivateKey) signature, err = crypto.Sign(toSign, key.PrivateKey)
return signature, err return signature, err
} }
...@@ -143,14 +148,40 @@ func (am *AccountManager) Accounts() ([]Account, error) { ...@@ -143,14 +148,40 @@ func (am *AccountManager) Accounts() ([]Account, error) {
return accounts, err return accounts, err
} }
func unlockLater(am *AccountManager, addr []byte) { func (am *AccountManager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
select { u := &unlocked{addr: addr, abort: make(chan struct{}), Key: key}
case <-time.After(am.unlockTime): am.mutex.Lock()
prev, found := am.unlocked[string(addr)]
if found {
// terminate dropLater for this key to avoid unexpected drops.
close(prev.abort)
zeroKey(prev.PrivateKey)
} }
am.unlocked[string(addr)] = u
am.mutex.Unlock()
return u
}
func (am *AccountManager) dropLater(u *unlocked) {
t := time.NewTimer(am.unlockTime)
defer t.Stop()
select {
case <-u.abort:
// just quit
case <-t.C:
am.mutex.Lock() am.mutex.Lock()
// TODO: how do we know the key is actually gone from memory? if am.unlocked[string(u.addr)] == u {
delete(am.unlockedKeys, string(addr)) zeroKey(u.PrivateKey)
delete(am.unlocked, string(u.addr))
}
am.mutex.Unlock() am.mutex.Unlock()
}
} }
// zeroKey zeroes a private key in memory.
func zeroKey(k *ecdsa.PrivateKey) {
b := k.D.Bits()
for i := range b {
b[i] = 0
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment