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 }