Browse Source

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.
Felix Lange 10 years ago
parent
commit
3750ec7b7d
1 changed files with 50 additions and 19 deletions
  1. 50 19
      accounts/account_manager.go

+ 50 - 19
accounts/account_manager.go

@@ -53,17 +53,24 @@ type Account struct {
 }
 }
 
 
 type AccountManager struct {
 type AccountManager struct {
-	keyStore     crypto.KeyStore2
-	unlockedKeys map[string]crypto.Key
-	unlockTime   time.Duration
-	mutex        sync.RWMutex
+	keyStore   crypto.KeyStore2
+	unlocked   map[string]*unlocked
+	unlockTime time.Duration
+	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,
-		unlockedKeys: make(map[string]crypto.Key),
-		unlockTime:   unlockTime,
+		keyStore:   keyStore,
+		unlocked:   make(map[string]*unlocked),
+		unlockTime: unlockTime,
 	}
 	}
 }
 }
 
 
@@ -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) (
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	am.mutex.Lock()
-	am.unlockedKeys[string(a.Address)] = *key
-	am.mutex.Unlock()
-	go unlockLater(am, a.Address)
+	u := am.addUnlocked(a.Address, key)
+	go am.dropLater(u)
 	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) {
 	return accounts, err
 	return accounts, err
 }
 }
 
 
-func unlockLater(am *AccountManager, addr []byte) {
-	select {
-	case <-time.After(am.unlockTime):
-	}
+func (am *AccountManager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
+	u := &unlocked{addr: addr, abort: make(chan struct{}), Key: key}
 	am.mutex.Lock()
 	am.mutex.Lock()
-	// TODO: how do we know the key is actually gone from memory?
-	delete(am.unlockedKeys, string(addr))
+	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()
 	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()
+		if am.unlocked[string(u.addr)] == u {
+			zeroKey(u.PrivateKey)
+			delete(am.unlocked, string(u.addr))
+		}
+		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
+	}
 }
 }