github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/keys/keyring.go (about) 1 package keys 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "reflect" 11 "sort" 12 "strings" 13 14 "github.com/99designs/keyring" 15 "github.com/pkg/errors" 16 17 tmcrypto "github.com/fibonacci-chain/fbc/libs/tendermint/crypto" 18 cryptoAmino "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/encoding/amino" 19 "github.com/tendermint/crypto/bcrypt" 20 21 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/input" 22 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/keyerror" 23 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/mintkey" 24 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 25 ) 26 27 const ( 28 BackendFile = "file" 29 BackendFileForRPC = "file4rpc" 30 BackendOS = "os" 31 BackendKWallet = "kwallet" 32 BackendPass = "pass" 33 BackendTest = "test" 34 BackendMemory = "memory" 35 ) 36 37 const ( 38 keyringDirNameFmt = "keyring-%s" 39 testKeyringDirNameFmt = "keyring-test-%s" 40 ) 41 42 var _ Keybase = keyringKeybase{} 43 44 // keyringKeybase implements the Keybase interface by using the Keyring library 45 // for account key persistence. 46 type keyringKeybase struct { 47 base baseKeybase 48 db keyring.Keyring 49 fileDir string 50 } 51 52 var maxPassphraseEntryAttempts = 3 53 54 func newKeyringKeybase(db keyring.Keyring, path string, opts ...KeybaseOption) Keybase { 55 return keyringKeybase{ 56 db: db, 57 fileDir: path, 58 base: newBaseKeybase(opts...), 59 } 60 } 61 62 // NewKeyring creates a new instance of a keyring. Keybase 63 // options can be applied when generating this new Keybase. 64 // Available backends are "os", "file", "test". 65 func NewKeyring( 66 appName, backend, rootDir string, userInput io.Reader, opts ...KeybaseOption, 67 ) (Keybase, error) { 68 69 var db keyring.Keyring 70 var err error 71 var config keyring.Config 72 73 switch backend { 74 case BackendTest: 75 config = lkbToKeyringConfig(appName, rootDir, nil, true) 76 case BackendFile: 77 config = newFileBackendKeyringConfig(appName, rootDir, userInput) 78 case BackendFileForRPC: 79 config = newFileBackendKeyringConfigForRPC(appName, rootDir, userInput) 80 case BackendOS: 81 config = lkbToKeyringConfig(appName, rootDir, userInput, false) 82 case BackendKWallet: 83 config = newKWalletBackendKeyringConfig(appName, rootDir, userInput) 84 case BackendPass: 85 config = newPassBackendKeyringConfig(appName, rootDir, userInput) 86 case BackendMemory: 87 return NewInMemory(opts...), err 88 default: 89 return nil, fmt.Errorf("unknown keyring backend %v", backend) 90 } 91 db, err = keyring.Open(config) 92 if err != nil { 93 return nil, err 94 } 95 96 return newKeyringKeybase(db, config.FileDir, opts...), nil 97 } 98 99 // CreateMnemonic generates a new key and persists it to storage, encrypted 100 // using the provided password. It returns the generated mnemonic and the key Info. 101 // An error is returned if it fails to generate a key for the given algo type, 102 // or if another key is already stored under the same name. 103 func (kb keyringKeybase) CreateMnemonic( 104 name string, language Language, passwd string, algo SigningAlgo, mnemonicInput string, 105 ) (info Info, mnemonic string, err error) { 106 107 return kb.base.CreateMnemonic(kb, name, language, passwd, algo, mnemonicInput) 108 } 109 110 // CreateMnemonicWithHDPath generates a new key and persists it to storage, encrypted 111 // using the provided password. It returns the generated mnemonic and the key Info. 112 // An error is returned if it fails to generate a key for the given algo type, 113 // or if another key is already stored under the same name. 114 func (kb keyringKeybase) CreateMnemonicWithHDPath( 115 name string, language Language, passwd string, algo SigningAlgo, mnemonicInput string, hdPath string, 116 ) (info Info, mnemonic string, err error) { 117 118 return kb.base.CreateMnemonicWithHDPath(kb, name, language, passwd, algo, mnemonicInput, hdPath) 119 } 120 121 // CreateAccount converts a mnemonic to a private key and persists it, encrypted 122 // with the given password. 123 func (kb keyringKeybase) CreateAccount( 124 name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo, 125 ) (Info, error) { 126 127 return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo) 128 } 129 130 // CreateLedger creates a new locally-stored reference to a Ledger keypair. 131 // It returns the created key info and an error if the Ledger could not be queried. 132 func (kb keyringKeybase) CreateLedger( 133 name string, algo SigningAlgo, hrp string, account, index uint32, 134 ) (Info, error) { 135 136 return kb.base.CreateLedger(kb, name, algo, hrp, account, index) 137 } 138 139 // CreateOffline creates a new reference to an offline keypair. It returns the 140 // created key info. 141 func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) { 142 return kb.base.writeOfflineKey(kb, name, pub, algo), nil 143 } 144 145 // CreateMulti creates a new reference to a multisig (offline) keypair. It 146 // returns the created key Info object. 147 func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) { 148 return kb.base.writeMultisigKey(kb, name, pub), nil 149 } 150 151 // List returns the keys from storage in alphabetical order. 152 func (kb keyringKeybase) List() ([]Info, error) { 153 var res []Info 154 keys, err := kb.db.Keys() 155 if err != nil { 156 return nil, err 157 } 158 159 sort.Strings(keys) 160 161 for _, key := range keys { 162 if strings.HasSuffix(key, infoSuffix) { 163 rawInfo, err := kb.db.Get(key) 164 if err != nil { 165 return nil, err 166 } 167 168 if len(rawInfo.Data) == 0 { 169 return nil, keyerror.NewErrKeyNotFound(key) 170 } 171 172 info, err := unmarshalInfo(rawInfo.Data) 173 if err != nil { 174 return nil, err 175 } 176 177 res = append(res, info) 178 } 179 } 180 181 return res, nil 182 } 183 184 // Get returns the public information about one key. 185 func (kb keyringKeybase) Get(name string) (Info, error) { 186 key := infoKey(name) 187 188 bs, err := kb.db.Get(string(key)) 189 if err != nil { 190 return nil, err 191 } 192 193 if len(bs.Data) == 0 { 194 return nil, keyerror.NewErrKeyNotFound(name) 195 } 196 197 return unmarshalInfo(bs.Data) 198 } 199 200 // GetByAddress fetches a key by address and returns its public information. 201 func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) { 202 ik, err := kb.db.Get(string(addrKey(address))) 203 if err != nil { 204 return nil, err 205 } 206 207 if len(ik.Data) == 0 { 208 return nil, fmt.Errorf("key with address %s not found", address) 209 } 210 211 bs, err := kb.db.Get(string(ik.Data)) 212 if err != nil { 213 return nil, err 214 } 215 216 return unmarshalInfo(bs.Data) 217 } 218 219 // Sign signs an arbitrary set of bytes with the named key. It returns an error 220 // if the key doesn't exist or the decryption fails. 221 func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { 222 info, err := kb.Get(name) 223 if err != nil { 224 return 225 } 226 227 var priv tmcrypto.PrivKey 228 229 switch i := info.(type) { 230 case localInfo: 231 if i.PrivKeyArmor == "" { 232 return nil, nil, fmt.Errorf("private key not available") 233 } 234 235 priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor)) 236 if err != nil { 237 return nil, nil, err 238 } 239 240 case ledgerInfo: 241 return kb.base.SignWithLedger(info, msg) 242 243 case offlineInfo, multiInfo: 244 return kb.base.DecodeSignature(info, msg) 245 } 246 247 sig, err = priv.Sign(msg) 248 if err != nil { 249 return nil, nil, err 250 } 251 252 return sig, priv.PubKey(), nil 253 } 254 255 // ExportPrivateKeyObject exports an armored private key object. 256 func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) { 257 info, err := kb.Get(name) 258 if err != nil { 259 return nil, err 260 } 261 262 var priv tmcrypto.PrivKey 263 264 switch linfo := info.(type) { 265 case localInfo: 266 if linfo.PrivKeyArmor == "" { 267 err = fmt.Errorf("private key not available") 268 return nil, err 269 } 270 271 priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor)) 272 if err != nil { 273 return nil, err 274 } 275 276 case ledgerInfo, offlineInfo, multiInfo: 277 return nil, errors.New("only works on local private keys") 278 } 279 280 return priv, nil 281 } 282 283 // Export exports armored private key to the caller. 284 func (kb keyringKeybase) Export(name string) (armor string, err error) { 285 bz, err := kb.db.Get(string(infoKey(name))) 286 if err != nil { 287 return "", err 288 } 289 290 if bz.Data == nil { 291 return "", fmt.Errorf("no key to export with name: %s", name) 292 } 293 294 return mintkey.ArmorInfoBytes(bz.Data), nil 295 } 296 297 // ExportPubKey returns public keys in ASCII armored format. It retrieves an Info 298 // object by its name and return the public key in a portable format. 299 func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) { 300 bz, err := kb.Get(name) 301 if err != nil { 302 return "", err 303 } 304 305 if bz == nil { 306 return "", fmt.Errorf("no key to export with name: %s", name) 307 } 308 309 return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil 310 } 311 312 // Import imports armored private key. 313 func (kb keyringKeybase) Import(name string, armor string) error { 314 bz, _ := kb.Get(name) 315 316 if bz != nil { 317 pubkey := bz.GetPubKey() 318 319 if len(pubkey.Bytes()) > 0 { 320 return fmt.Errorf("cannot overwrite data for name: %s", name) 321 } 322 } 323 324 infoBytes, err := mintkey.UnarmorInfoBytes(armor) 325 if err != nil { 326 return err 327 } 328 329 info, err := unmarshalInfo(infoBytes) 330 if err != nil { 331 return err 332 } 333 334 kb.writeInfo(name, info) 335 336 err = kb.db.Set(keyring.Item{ 337 Key: string(addrKey(info.GetAddress())), 338 Data: infoKey(name), 339 }) 340 if err != nil { 341 return err 342 } 343 344 return nil 345 } 346 347 // ExportPrivKey returns a private key in ASCII armored format. An error is returned 348 // if the key does not exist or a wrong encryption passphrase is supplied. 349 func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) { 350 priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase) 351 if err != nil { 352 return "", err 353 } 354 355 info, err := kb.Get(name) 356 if err != nil { 357 return "", err 358 } 359 360 return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil 361 } 362 363 // ImportPrivKey imports a private key in ASCII armor format. An error is returned 364 // if a key with the same name exists or a wrong encryption passphrase is 365 // supplied. 366 func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error { 367 if kb.HasKey(name) { 368 return fmt.Errorf("cannot overwrite key: %s", name) 369 } 370 371 privKey, algo, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase) 372 if err != nil { 373 return errors.Wrap(err, "failed to decrypt private key") 374 } 375 376 // NOTE: The keyring keystore has no need for a passphrase. 377 kb.writeLocalKey(name, privKey, "", SigningAlgo(algo)) 378 return nil 379 } 380 381 // HasKey returns whether the key exists in the keyring. 382 func (kb keyringKeybase) HasKey(name string) bool { 383 bz, _ := kb.Get(name) 384 return bz != nil 385 } 386 387 // ImportPubKey imports an ASCII-armored public key. It will store a new Info 388 // object holding a public key only, i.e. it will not be possible to sign with 389 // it as it lacks the secret key. 390 func (kb keyringKeybase) ImportPubKey(name string, armor string) error { 391 bz, _ := kb.Get(name) 392 if bz != nil { 393 pubkey := bz.GetPubKey() 394 395 if len(pubkey.Bytes()) > 0 { 396 return fmt.Errorf("cannot overwrite data for name: %s", name) 397 } 398 } 399 400 pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor) 401 if err != nil { 402 return err 403 } 404 405 pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) 406 if err != nil { 407 return err 408 } 409 410 kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo)) 411 return nil 412 } 413 414 // Delete removes key forever, but we must present the proper passphrase before 415 // deleting it (for security). It returns an error if the key doesn't exist or 416 // passphrases don't match. The passphrase is ignored when deleting references to 417 // offline and Ledger / HW wallet keys. 418 func (kb keyringKeybase) Delete(name, _ string, _ bool) error { 419 // verify we have the proper password before deleting 420 info, err := kb.Get(name) 421 if err != nil { 422 return err 423 } 424 425 err = kb.db.Remove(string(addrKey(info.GetAddress()))) 426 if err != nil { 427 return err 428 } 429 430 err = kb.db.Remove(string(infoKey(name))) 431 if err != nil { 432 return err 433 } 434 435 return nil 436 } 437 438 // Update changes the passphrase with which an already stored key is encrypted. 439 // The oldpass must be the current passphrase used for encryption, getNewpass is 440 // a function to get the passphrase to permanently replace the current passphrase. 441 func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { 442 info, err := kb.Get(name) 443 if err != nil { 444 return err 445 } 446 447 switch linfo := info.(type) { 448 case localInfo: 449 key, _, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) 450 if err != nil { 451 return err 452 } 453 454 newpass, err := getNewpass() 455 if err != nil { 456 return err 457 } 458 459 kb.writeLocalKey(name, key, newpass, linfo.GetAlgo()) 460 return nil 461 462 default: 463 return fmt.Errorf("locally stored key required; received: %v", reflect.TypeOf(info).String()) 464 } 465 } 466 467 // SupportedAlgos returns a list of supported signing algorithms. 468 func (kb keyringKeybase) SupportedAlgos() []SigningAlgo { 469 return kb.base.SupportedAlgos() 470 } 471 472 // SupportedAlgosLedger returns a list of supported ledger signing algorithms. 473 func (kb keyringKeybase) SupportedAlgosLedger() []SigningAlgo { 474 return kb.base.SupportedAlgosLedger() 475 } 476 477 // CloseDB releases the lock and closes the storage backend. 478 func (kb keyringKeybase) CloseDB() {} 479 480 func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, _ string, algo SigningAlgo) Info { 481 // encrypt private key using keyring 482 pub := priv.PubKey() 483 info := newLocalInfo(name, pub, string(priv.Bytes()), algo) 484 485 kb.writeInfo(name, info) 486 return info 487 } 488 489 func (kb keyringKeybase) writeInfo(name string, info Info) { 490 // write the info by key 491 key := infoKey(name) 492 serializedInfo := marshalInfo(info) 493 494 err := kb.db.Set(keyring.Item{ 495 Key: string(key), 496 Data: serializedInfo, 497 }) 498 if err != nil { 499 panic(err) 500 } 501 502 err = kb.db.Set(keyring.Item{ 503 Key: string(addrKey(info.GetAddress())), 504 Data: key, 505 }) 506 if err != nil { 507 panic(err) 508 } 509 } 510 511 // FileDir show keyringKeybase absolute position 512 func (kb keyringKeybase) FileDir() (string, error) { 513 return resolvePath(kb.fileDir) 514 } 515 516 func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.Config { 517 if test { 518 return keyring.Config{ 519 AllowedBackends: []keyring.BackendType{keyring.FileBackend}, 520 ServiceName: appName, 521 FileDir: filepath.Join(dir, fmt.Sprintf(testKeyringDirNameFmt, appName)), 522 FilePasswordFunc: func(_ string) (string, error) { 523 return "test", nil 524 }, 525 } 526 } 527 528 return keyring.Config{ 529 ServiceName: appName, 530 FileDir: dir, 531 FilePasswordFunc: newRealPrompt(dir, buf), 532 } 533 } 534 535 func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config { 536 return keyring.Config{ 537 AllowedBackends: []keyring.BackendType{keyring.KWalletBackend}, 538 ServiceName: "kdewallet", 539 KWalletAppID: appName, 540 KWalletFolder: "", 541 } 542 } 543 544 func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Config { 545 prefix := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, appName)) 546 return keyring.Config{ 547 AllowedBackends: []keyring.BackendType{keyring.PassBackend}, 548 ServiceName: appName, 549 PassPrefix: prefix, 550 } 551 } 552 553 func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config { 554 fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name)) 555 return keyring.Config{ 556 AllowedBackends: []keyring.BackendType{keyring.FileBackend}, 557 ServiceName: name, 558 FileDir: fileDir, 559 FilePasswordFunc: newRealPrompt(fileDir, buf), 560 } 561 } 562 563 func newFileBackendKeyringConfigForRPC(name, dir string, buf io.Reader) keyring.Config { 564 fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name)) 565 return keyring.Config{ 566 AllowedBackends: []keyring.BackendType{keyring.FileBackend}, 567 ServiceName: name, 568 FileDir: fileDir, 569 FilePasswordFunc: func(_ string) (string, error) { 570 return "test", nil 571 }, 572 } 573 } 574 575 func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) { 576 return func(prompt string) (string, error) { 577 keyhashStored := false 578 keyhashFilePath := filepath.Join(dir, "keyhash") 579 var keyhash []byte 580 581 // read hashfile from file to check input password 582 _, err := os.Stat(keyhashFilePath) 583 switch { 584 case err == nil: 585 keyhash, err = ioutil.ReadFile(keyhashFilePath) 586 if err != nil { 587 return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err) 588 } 589 590 keyhashStored = true 591 592 case os.IsNotExist(err): 593 keyhashStored = false 594 595 default: 596 return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err) 597 } 598 599 failureCounter := 0 600 for { 601 failureCounter++ 602 if failureCounter > maxPassphraseEntryAttempts { 603 return "", fmt.Errorf("too many failed passphrase attempts") 604 } 605 606 buf := bufio.NewReader(buf) 607 pass, err := input.GetPassword("Enter keyring passphrase:", buf) 608 if err != nil { 609 fmt.Fprintln(os.Stderr, err) 610 continue 611 } 612 613 if keyhashStored { 614 if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil { 615 fmt.Fprintln(os.Stderr, "incorrect passphrase") 616 continue 617 } 618 return pass, nil 619 } 620 621 reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf) 622 if err != nil { 623 fmt.Fprintln(os.Stderr, err) 624 continue 625 } 626 627 if pass != reEnteredPass { 628 fmt.Fprintln(os.Stderr, "passphrase do not match") 629 continue 630 } 631 632 saltBytes := tmcrypto.CRandBytes(16) 633 passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2) 634 if err != nil { 635 fmt.Fprintln(os.Stderr, err) 636 continue 637 } 638 639 if err := ioutil.WriteFile(dir+"/keyhash", passwordHash, 0555); err != nil { 640 return "", err 641 } 642 return pass, nil 643 } 644 } 645 }