github.com/r8d8/go-ethereum@v5.5.2+incompatible/accounts/manager.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package accounts implements encrypted storage of secp256k1 private keys.
    18  //
    19  // Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
    20  // See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
    21  package accounts
    22  
    23  import (
    24  	"crypto/ecdsa"
    25  	"errors"
    26  	"fmt"
    27  	"os"
    28  	"runtime"
    29  	"sync"
    30  	"time"
    31  
    32  	"encoding/json"
    33  	"path/filepath"
    34  
    35  	"github.com/ethereumproject/go-ethereum/common"
    36  	"github.com/ethereumproject/go-ethereum/crypto"
    37  )
    38  
    39  var (
    40  	ErrLocked  = errors.New("account is locked")
    41  	ErrNoMatch = errors.New("no key for given address or file")
    42  	ErrDecrypt = errors.New("could not decrypt key with given passphrase")
    43  
    44  	errAddrMismatch = errors.New("security violation: address of file didn't match request")
    45  )
    46  
    47  // Account represents a stored key.
    48  // When used as an argument, it selects a unique key file to act on.
    49  type Account struct {
    50  	Address      common.Address // Ethereum account address derived from the key
    51  	EncryptedKey string         // web3JSON format
    52  
    53  	// File contains the key file name.
    54  	// When Acccount is used as an argument to select a key, File can be left blank to
    55  	// select just by address or set to the basename or absolute path of a file in the key
    56  	// directory. Accounts returned by Manager will always contain an absolute path.
    57  	File string
    58  }
    59  
    60  // AccountJSON is an auxiliary between Account and EasyMarshal'd structs.
    61  //easyjson:json
    62  type AccountJSON struct {
    63  	Address      string `json:"address"`
    64  	EncryptedKey string `json:"key"`
    65  	File         string `json:"file"`
    66  }
    67  
    68  func (acc *Account) MarshalJSON() ([]byte, error) {
    69  	return []byte(`"` + acc.Address.Hex() + `"`), nil
    70  }
    71  
    72  func (acc *Account) UnmarshalJSON(raw []byte) error {
    73  	return json.Unmarshal(raw, &acc.Address)
    74  }
    75  
    76  // Manager manages a key storage directory on disk.
    77  type Manager struct {
    78  	ac       caching
    79  	keyStore keyStore
    80  	mu       sync.RWMutex
    81  	unlocked map[common.Address]*unlocked
    82  }
    83  
    84  type unlocked struct {
    85  	*key
    86  	abort chan struct{}
    87  }
    88  
    89  const (
    90  	// n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
    91  	StandardScryptN = 1 << 18
    92  	StandardScryptP = 1
    93  
    94  	// n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
    95  	LightScryptN = 1 << 12
    96  	LightScryptP = 6
    97  
    98  	scryptR     = 8
    99  	scryptDKLen = 32
   100  )
   101  
   102  // NewManager creates a manager for the given directory.
   103  // keydir is by default /Users/ia/Library/EthereumClassic/mainnet/keystore
   104  func NewManager(keydir string, scryptN, scryptP int, wantCacheDB bool) (*Manager, error) {
   105  	store, err := newKeyStore(keydir, scryptN, scryptP)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	am := &Manager{
   111  		keyStore: *store,
   112  		unlocked: make(map[common.Address]*unlocked),
   113  	}
   114  	if wantCacheDB {
   115  		am.ac = newCacheDB(keydir)
   116  	} else {
   117  		am.ac = newAddrCache(keydir)
   118  	}
   119  
   120  	// TODO: In order for this finalizer to work, there must be no references
   121  	// to am. addrCache doesn't keep a reference but unlocked keys do,
   122  	// so the finalizer will not trigger until all timed unlocks have expired.
   123  	runtime.SetFinalizer(am, func(m *Manager) {
   124  		// bug(whilei): I was getting panic: close of closed channel when running tests as package;
   125  		// individually each test would pass but not when run in a bunch.
   126  		// either manager reference was stuck somewhere or the tests were outpacing themselves
   127  		// checking for nil seems to fix the issue.
   128  		if am.ac != nil {
   129  			m.ac.close()
   130  		}
   131  
   132  	})
   133  
   134  	return am, nil
   135  }
   136  
   137  func (am *Manager) BuildIndexDB() []error {
   138  	return am.ac.Syncfs2db(time.Now().Add(-60 * 24 * 7 * 30 * 120 * time.Minute)) // arbitrarily long "last updated"
   139  }
   140  
   141  // HasAddress reports whether a key with the given address is present.
   142  func (am *Manager) HasAddress(addr common.Address) bool {
   143  	return am.ac.hasAddress(addr)
   144  }
   145  
   146  // Accounts returns all key files present in the directory.
   147  func (am *Manager) Accounts() []Account {
   148  	return am.ac.accounts()
   149  }
   150  
   151  // DeleteAccount deletes the key matched by account if the passphrase is correct.
   152  // If a contains no filename, the address must match a unique key.
   153  func (am *Manager) DeleteAccount(a Account, passphrase string) error {
   154  	// Decrypting the key isn't really necessary, but we do
   155  	// it anyway to check the password and zero out the key
   156  	// immediately afterwards.
   157  	a, key, err := am.getDecryptedKey(a, passphrase)
   158  	if key != nil {
   159  		zeroKey(key.PrivateKey)
   160  	}
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	if !filepath.IsAbs(a.File) {
   166  		p := filepath.Join(am.ac.getKeydir(), a.File)
   167  		a.File = p
   168  	}
   169  
   170  	// The order is crucial here. The key is dropped from the
   171  	// cache after the file is gone so that a reload happening in
   172  	// between won't insert it into the cache again.
   173  	err = os.Remove(a.File)
   174  	if err == nil {
   175  		am.ac.delete(a)
   176  	}
   177  	return err
   178  }
   179  
   180  // Sign signs hash with an unlocked private key matching the given address.
   181  func (am *Manager) Sign(addr common.Address, hash []byte) (signature []byte, err error) {
   182  	am.mu.RLock()
   183  	defer am.mu.RUnlock()
   184  
   185  	unlockedKey, found := am.unlocked[addr]
   186  	if !found {
   187  		return nil, ErrLocked
   188  	}
   189  	return crypto.Sign(hash, unlockedKey.PrivateKey)
   190  }
   191  
   192  // SignWithPassphrase signs hash if the private key matching the given address can be
   193  // decrypted with the given passphrase.
   194  func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) {
   195  	_, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	defer zeroKey(key.PrivateKey)
   201  	return crypto.Sign(hash, key.PrivateKey)
   202  }
   203  
   204  // Unlock unlocks the given account indefinitely.
   205  func (am *Manager) Unlock(a Account, passphrase string) error {
   206  	return am.TimedUnlock(a, passphrase, 0)
   207  }
   208  
   209  // Lock removes the private key with the given address from memory.
   210  func (am *Manager) Lock(addr common.Address) error {
   211  	am.mu.Lock()
   212  	if unl, found := am.unlocked[addr]; found {
   213  		am.mu.Unlock()
   214  		am.expire(addr, unl, time.Duration(0)*time.Nanosecond)
   215  	} else {
   216  		am.mu.Unlock()
   217  	}
   218  	return nil
   219  }
   220  
   221  // TimedUnlock unlocks the given account with the passphrase. The account
   222  // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
   223  // until the program exits. The account must match a unique key file.
   224  //
   225  // If the account address is already unlocked for a duration, TimedUnlock extends or
   226  // shortens the active unlock timeout. If the address was previously unlocked
   227  // indefinitely the timeout is not altered.
   228  func (am *Manager) TimedUnlock(a Account, passphrase string, timeout time.Duration) error {
   229  	a, key, err := am.getDecryptedKey(a, passphrase)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	am.mu.Lock()
   235  	defer am.mu.Unlock()
   236  	u, found := am.unlocked[a.Address]
   237  	if found {
   238  		if u.abort == nil {
   239  			// The address was unlocked indefinitely, so unlocking
   240  			// it with a timeout would be confusing.
   241  			zeroKey(key.PrivateKey)
   242  			return nil
   243  		} else {
   244  			// Terminate the expire goroutine and replace it below.
   245  			close(u.abort)
   246  		}
   247  	}
   248  	if timeout > 0 {
   249  		u = &unlocked{key: key, abort: make(chan struct{})}
   250  		go am.expire(a.Address, u, timeout)
   251  	} else {
   252  		u = &unlocked{key: key}
   253  	}
   254  	am.unlocked[a.Address] = u
   255  	return nil
   256  }
   257  
   258  func (am *Manager) getDecryptedKey(a Account, auth string) (Account, *key, error) {
   259  	am.ac.maybeReload()
   260  	am.ac.muLock()
   261  	a, err := am.ac.find(a)
   262  	am.ac.muUnlock()
   263  	if err != nil {
   264  		return Account{}, nil, err
   265  	}
   266  
   267  	key := &key{}
   268  	if a.EncryptedKey != "" {
   269  		key, err = am.keyStore.DecryptKey([]byte(a.EncryptedKey), auth)
   270  	} else {
   271  		key, err = am.keyStore.Lookup(a.File, auth)
   272  	}
   273  
   274  	if err != nil {
   275  		return Account{}, nil, err
   276  	}
   277  	if key.Address != a.Address {
   278  		return Account{}, nil, errAddrMismatch
   279  	}
   280  
   281  	return a, key, err
   282  }
   283  
   284  func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duration) {
   285  	t := time.NewTimer(timeout)
   286  	defer t.Stop()
   287  	select {
   288  	case <-u.abort:
   289  		// just quit
   290  	case <-t.C:
   291  		am.mu.Lock()
   292  		// only drop if it's still the same key instance that dropLater
   293  		// was launched with. we can check that using pointer equality
   294  		// because the map stores a new pointer every time the key is
   295  		// unlocked.
   296  		if am.unlocked[addr] == u {
   297  			zeroKey(u.PrivateKey)
   298  			delete(am.unlocked, addr)
   299  		}
   300  		am.mu.Unlock()
   301  	}
   302  }
   303  
   304  // NewAccount generates a new key and stores it into the key directory,
   305  // encrypting it with the passphrase.
   306  func (am *Manager) NewAccount(passphrase string) (Account, error) {
   307  	_, account, err := storeNewKey(&am.keyStore, passphrase)
   308  	if err != nil {
   309  		return Account{}, err
   310  	}
   311  	// Add the account to the cache immediately rather
   312  	// than waiting for file system notifications to pick it up.
   313  	am.ac.add(account)
   314  	return account, nil
   315  }
   316  
   317  // AccountByIndex returns the ith account.
   318  func (am *Manager) AccountByIndex(i int) (Account, error) {
   319  	accounts := am.Accounts()
   320  	if i < 0 || i >= len(accounts) {
   321  		return Account{}, fmt.Errorf("account index %d out of range [0, %d]", i, len(accounts)-1)
   322  	}
   323  	return accounts[i], nil
   324  }
   325  
   326  // Export exports as a JSON key, encrypted with newPassphrase.
   327  func (am *Manager) Export(a Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
   328  	_, key, err := am.getDecryptedKey(a, passphrase)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  	return encryptKey(key, newPassphrase, am.keyStore.scryptN, am.keyStore.scryptP)
   333  }
   334  
   335  // Import stores the given encrypted JSON key into the key directory.
   336  func (am *Manager) Import(keyJSON []byte, passphrase, newPassphrase string) (Account, error) {
   337  	key, err := decryptKey(keyJSON, passphrase)
   338  	if key != nil && key.PrivateKey != nil {
   339  		defer zeroKey(key.PrivateKey)
   340  	}
   341  	if err != nil {
   342  		return Account{}, err
   343  	}
   344  	return am.importKey(key, newPassphrase)
   345  }
   346  
   347  // ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
   348  func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) {
   349  	key, err := newKeyFromECDSA(priv)
   350  	if err != nil {
   351  		return Account{}, err
   352  	}
   353  
   354  	if am.ac.hasAddress(key.Address) {
   355  		return Account{}, fmt.Errorf("account already exists")
   356  	}
   357  
   358  	return am.importKey(key, passphrase)
   359  }
   360  
   361  func (am *Manager) importKey(key *key, passphrase string) (Account, error) {
   362  	file, err := am.keyStore.Insert(key, passphrase)
   363  	if err != nil {
   364  		return Account{}, err
   365  	}
   366  
   367  	a := Account{File: file, Address: key.Address}
   368  	am.ac.add(a)
   369  	return a, nil
   370  }
   371  
   372  // Update changes the passphrase of an existing account.
   373  func (am *Manager) Update(a Account, passphrase, newPassphrase string) error {
   374  	a, key, err := am.getDecryptedKey(a, passphrase)
   375  	if err != nil {
   376  		return err
   377  	}
   378  	return am.keyStore.Update(a.File, key, newPassphrase)
   379  }
   380  
   381  // ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
   382  // a key file in the key directory. The key file is encrypted with the same passphrase.
   383  func (am *Manager) ImportPreSaleKey(keyJSON []byte, passphrase string) (Account, error) {
   384  	a, _, err := importPreSaleKey(&am.keyStore, keyJSON, passphrase)
   385  	if err != nil {
   386  		return a, err
   387  	}
   388  	am.ac.add(a)
   389  	return a, nil
   390  }
   391  
   392  // zeroKey zeroes a private key in memory.
   393  func zeroKey(k *ecdsa.PrivateKey) {
   394  	b := k.D.Bits()
   395  	for i := range b {
   396  		b[i] = 0
   397  	}
   398  }