github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/identity/keystore_filesystem.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU 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   * This program 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 General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package identity
    19  
    20  import (
    21  	"crypto/aes"
    22  	"crypto/cipher"
    23  	"crypto/ecdsa"
    24  	"crypto/rand"
    25  	"crypto/sha512"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"os"
    30  	"sync"
    31  	"time"
    32  
    33  	"github.com/ethereum/go-ethereum/accounts"
    34  	ethKs "github.com/ethereum/go-ethereum/accounts/keystore"
    35  	"github.com/ethereum/go-ethereum/common"
    36  	"github.com/ethereum/go-ethereum/crypto"
    37  	"golang.org/x/crypto/hkdf"
    38  )
    39  
    40  type ethKeystore interface {
    41  	Delete(a accounts.Account, passphrase string) error
    42  	Accounts() []accounts.Account
    43  	NewAccount(passphrase string) (accounts.Account, error)
    44  	Find(a accounts.Account) (accounts.Account, error)
    45  	Export(a accounts.Account, passphrase, newPassphrase string) ([]byte, error)
    46  	Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error)
    47  }
    48  
    49  // NewKeystoreFilesystem create new keystore, which keeps keys in filesystem.
    50  func NewKeystoreFilesystem(directory string, ks ethKeystore) *Keystore {
    51  	return &Keystore{
    52  		ethKeystore: ks,
    53  		loadKey:     loadStoredKey,
    54  		unlocked:    make(map[common.Address]*unlocked),
    55  	}
    56  }
    57  
    58  // Keystore handles everything that's related to eth accounts.
    59  type Keystore struct {
    60  	ethKeystore
    61  	loadKey func(addr common.Address, filename, auth string) (*ethKs.Key, error)
    62  
    63  	unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
    64  	mu       sync.RWMutex
    65  }
    66  
    67  // Unlock unlocks the given account indefinitely.
    68  func (ks *Keystore) Unlock(a accounts.Account, passphrase string) error {
    69  	return ks.TimedUnlock(a, passphrase, 0)
    70  }
    71  
    72  // Lock removes the private key with the given address from memory.
    73  func (ks *Keystore) Lock(addr common.Address) error {
    74  	ks.mu.Lock()
    75  	if unl, found := ks.unlocked[addr]; found {
    76  		ks.mu.Unlock()
    77  		ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
    78  	} else {
    79  		ks.mu.Unlock()
    80  	}
    81  	return nil
    82  }
    83  
    84  // TimedUnlock unlocks the given account with the passphrase. The account
    85  // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
    86  // until the program exits. The account must match a unique key file.
    87  //
    88  // If the account address is already unlocked for a duration, TimedUnlock extends or
    89  // shortens the active unlock timeout. If the address was previously unlocked
    90  // indefinitely the timeout is not altered.
    91  func (ks *Keystore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
    92  	a, key, err := ks.getDecryptedKey(a, passphrase)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	ks.mu.Lock()
    98  	defer ks.mu.Unlock()
    99  	u, found := ks.unlocked[a.Address]
   100  	if found {
   101  		if u.abort == nil {
   102  			// The address was unlocked indefinitely, so unlocking
   103  			// it with a timeout would be confusing.
   104  			zeroKey(key.PrivateKey)
   105  			return nil
   106  		}
   107  		// Terminate the expire goroutine and replace it below.
   108  		close(u.abort)
   109  	}
   110  	if timeout > 0 {
   111  		u = &unlocked{Key: key, abort: make(chan struct{})}
   112  		go ks.expire(a.Address, u, timeout)
   113  	} else {
   114  		u = &unlocked{Key: key}
   115  	}
   116  	ks.unlocked[a.Address] = u
   117  	return nil
   118  }
   119  
   120  func (ks *Keystore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *ethKs.Key, error) {
   121  	a, err := ks.ethKeystore.Find(a)
   122  	if err != nil {
   123  		return a, nil, err
   124  	}
   125  	key, err := ks.loadKey(a.Address, a.URL.Path, auth)
   126  	return a, key, err
   127  }
   128  
   129  func (ks *Keystore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
   130  	t := time.NewTimer(timeout)
   131  	defer t.Stop()
   132  	select {
   133  	case <-u.abort:
   134  		// just quit
   135  	case <-t.C:
   136  		ks.mu.Lock()
   137  		// only drop if it's still the same key instance that dropLater
   138  		// was launched with. we can check that using pointer equality
   139  		// because the map stores a new pointer every time the key is
   140  		// unlocked.
   141  		if ks.unlocked[addr] == u {
   142  			zeroKey(u.PrivateKey)
   143  			delete(ks.unlocked, addr)
   144  		}
   145  		ks.mu.Unlock()
   146  	}
   147  }
   148  
   149  // Encrypt takes a derived key for the given address and encrypts the plaintext.
   150  func (ks *Keystore) Encrypt(addr common.Address, plaintext []byte) ([]byte, error) {
   151  	ks.mu.RLock()
   152  	defer ks.mu.RUnlock()
   153  
   154  	key, found := ks.unlocked[addr]
   155  	if !found {
   156  		return nil, ethKs.ErrLocked
   157  	}
   158  
   159  	keyDerived, err := key.deriveKey()
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	c, err := aes.NewCipher(keyDerived)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	gcm, err := cipher.NewGCM(c)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	nonce := make([]byte, gcm.NonceSize())
   175  	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	return gcm.Seal(nonce, nonce, plaintext, nil), nil
   180  }
   181  
   182  // Decrypt takes a derived key for the given address and decrypts the encrypted message.
   183  func (ks *Keystore) Decrypt(addr common.Address, encrypted []byte) ([]byte, error) {
   184  	ks.mu.RLock()
   185  	defer ks.mu.RUnlock()
   186  
   187  	key, found := ks.unlocked[addr]
   188  	if !found {
   189  		return nil, ethKs.ErrLocked
   190  	}
   191  
   192  	keyDerived, err := key.deriveKey()
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	c, err := aes.NewCipher(keyDerived)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	gcm, err := cipher.NewGCM(c)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	nonceSize := gcm.NonceSize()
   208  	if len(encrypted) < nonceSize {
   209  		return nil, errors.New("ciphertext too short")
   210  	}
   211  
   212  	nonce, encrypted := encrypted[:nonceSize], encrypted[nonceSize:]
   213  	return gcm.Open(nil, nonce, encrypted, nil)
   214  }
   215  
   216  // SignHash calculates a ECDSA signature for the given hash. The produced
   217  // signature is in the [R || S || V] format where V is 0 or 1.
   218  func (ks *Keystore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
   219  	// Look up the key to sign with and abort if it cannot be found
   220  	ks.mu.RLock()
   221  	defer ks.mu.RUnlock()
   222  
   223  	unlockedKey, found := ks.unlocked[a.Address]
   224  	if !found {
   225  		return nil, ethKs.ErrLocked
   226  	}
   227  	// Sign the hash using plain ECDSA operations
   228  	return crypto.Sign(hash, unlockedKey.PrivateKey)
   229  }
   230  
   231  // zeroKey zeroes a private key in memory.
   232  func zeroKey(k *ecdsa.PrivateKey) {
   233  	b := k.D.Bits()
   234  	for i := range b {
   235  		b[i] = 0
   236  	}
   237  }
   238  
   239  type unlocked struct {
   240  	*ethKs.Key
   241  	abort chan struct{}
   242  }
   243  
   244  func (u *unlocked) deriveKey() ([]byte, error) {
   245  	hashFunc := sha512.New
   246  	hkdfDerived := hkdf.New(hashFunc, u.Key.PrivateKey.D.Bytes(), nil, nil)
   247  	key := make([]byte, 32)
   248  	_, err := io.ReadFull(hkdfDerived, key)
   249  	return key, err
   250  }
   251  
   252  func loadStoredKey(addr common.Address, filename, auth string) (*ethKs.Key, error) {
   253  	// Load the key from the keystore and decrypt its contents
   254  	keyjson, err := os.ReadFile(filename)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	key, err := ethKs.DecryptKey(keyjson, auth)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	// Make sure we're really operating on the requested key (no swap attacks)
   263  	if key.Address != addr {
   264  		return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
   265  	}
   266  	return key, nil
   267  }