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 }