github.com/bearnetworkchain/go-bearnetwork@v1.10.19-0.20220604150648-d63890c2e42b/cmd/clef/main.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum 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 // go-ethereum 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 go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bufio" 21 "context" 22 "crypto/rand" 23 "crypto/sha256" 24 "encoding/hex" 25 "encoding/json" 26 "fmt" 27 "io" 28 "math/big" 29 "os" 30 "os/signal" 31 "path/filepath" 32 "runtime" 33 "sort" 34 "strings" 35 "time" 36 37 "github.com/bearnetworkchain/go-bearnetwork/accounts" 38 "github.com/bearnetworkchain/go-bearnetwork/accounts/keystore" 39 "github.com/bearnetworkchain/go-bearnetwork/cmd/utils" 40 "github.com/bearnetworkchain/go-bearnetwork/common" 41 "github.com/bearnetworkchain/go-bearnetwork/common/hexutil" 42 "github.com/bearnetworkchain/go-bearnetwork/core/types" 43 "github.com/bearnetworkchain/go-bearnetwork/crypto" 44 "github.com/bearnetworkchain/go-bearnetwork/internal/ethapi" 45 "github.com/bearnetworkchain/go-bearnetwork/internal/flags" 46 "github.com/bearnetworkchain/go-bearnetwork/log" 47 "github.com/bearnetworkchain/go-bearnetwork/node" 48 "github.com/bearnetworkchain/go-bearnetwork/params" 49 "github.com/bearnetworkchain/go-bearnetwork/rlp" 50 "github.com/bearnetworkchain/go-bearnetwork/rpc" 51 "github.com/bearnetworkchain/go-bearnetwork/signer/core" 52 "github.com/bearnetworkchain/go-bearnetwork/signer/core/apitypes" 53 "github.com/bearnetworkchain/go-bearnetwork/signer/fourbyte" 54 "github.com/bearnetworkchain/go-bearnetwork/signer/rules" 55 "github.com/bearnetworkchain/go-bearnetwork/signer/storage" 56 "github.com/mattn/go-colorable" 57 "github.com/mattn/go-isatty" 58 "gopkg.in/urfave/cli.v1" 59 ) 60 61 const legalWarning = ` 62 WARNING! 63 64 Clef is an account management tool. It may, like any software, contain bugs. 65 66 Please take care to 67 - backup your keystore files, 68 - verify that the keystore(s) can be opened with your password. 69 70 Clef is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 71 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 72 PURPOSE. See the GNU General Public License for more details. 73 ` 74 75 var ( 76 logLevelFlag = cli.IntFlag{ 77 Name: "loglevel", 78 Value: 4, 79 Usage: "log level to emit to the screen", 80 } 81 advancedMode = cli.BoolFlag{ 82 Name: "advanced", 83 Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off", 84 } 85 acceptFlag = cli.BoolFlag{ 86 Name: "suppress-bootwarn", 87 Usage: "If set, does not show the warning during boot", 88 } 89 keystoreFlag = cli.StringFlag{ 90 Name: "keystore", 91 Value: filepath.Join(node.DefaultDataDir(), "keystore"), 92 Usage: "Directory for the keystore", 93 } 94 configdirFlag = cli.StringFlag{ 95 Name: "configdir", 96 Value: DefaultConfigDir(), 97 Usage: "Directory for Clef configuration", 98 } 99 chainIdFlag = cli.Int64Flag{ 100 Name: "chainid", 101 Value: params.MainnetChainConfig.ChainID.Int64(), 102 Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)", 103 } 104 rpcPortFlag = cli.IntFlag{ 105 Name: "http.port", 106 Usage: "HTTP-RPC server listening port", 107 Value: node.DefaultHTTPPort + 5, 108 } 109 signerSecretFlag = cli.StringFlag{ 110 Name: "signersecret", 111 Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", 112 } 113 customDBFlag = cli.StringFlag{ 114 Name: "4bytedb-custom", 115 Usage: "File used for writing new 4byte-identifiers submitted via API", 116 Value: "./4byte-custom.json", 117 } 118 auditLogFlag = cli.StringFlag{ 119 Name: "auditlog", 120 Usage: "File used to emit audit logs. Set to \"\" to disable", 121 Value: "audit.log", 122 } 123 ruleFlag = cli.StringFlag{ 124 Name: "rules", 125 Usage: "Path to the rule file to auto-authorize requests with", 126 } 127 stdiouiFlag = cli.BoolFlag{ 128 Name: "stdio-ui", 129 Usage: "Use STDIN/STDOUT as a channel for an external UI. " + 130 "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + 131 "interface, and can be used when Clef is started by an external process.", 132 } 133 testFlag = cli.BoolFlag{ 134 Name: "stdio-ui-test", 135 Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", 136 } 137 app = cli.NewApp() 138 initCommand = cli.Command{ 139 Action: utils.MigrateFlags(initializeSecrets), 140 Name: "init", 141 Usage: "Initialize the signer, generate secret storage", 142 ArgsUsage: "", 143 Flags: []cli.Flag{ 144 logLevelFlag, 145 configdirFlag, 146 }, 147 Description: ` 148 The init command generates a master seed which Clef can use to store credentials and data needed for 149 the rule-engine to work.`, 150 } 151 attestCommand = cli.Command{ 152 Action: utils.MigrateFlags(attestFile), 153 Name: "attest", 154 Usage: "Attest that a js-file is to be used", 155 ArgsUsage: "<sha256sum>", 156 Flags: []cli.Flag{ 157 logLevelFlag, 158 configdirFlag, 159 signerSecretFlag, 160 }, 161 Description: ` 162 The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 163 incoming requests. 164 165 Whenever you make an edit to the rule file, you need to use attestation to tell 166 Clef that the file is 'safe' to execute.`, 167 } 168 setCredentialCommand = cli.Command{ 169 Action: utils.MigrateFlags(setCredential), 170 Name: "setpw", 171 Usage: "Store a credential for a keystore file", 172 ArgsUsage: "<address>", 173 Flags: []cli.Flag{ 174 logLevelFlag, 175 configdirFlag, 176 signerSecretFlag, 177 }, 178 Description: ` 179 The setpw command stores a password for a given address (keyfile). 180 `} 181 delCredentialCommand = cli.Command{ 182 Action: utils.MigrateFlags(removeCredential), 183 Name: "delpw", 184 Usage: "Remove a credential for a keystore file", 185 ArgsUsage: "<address>", 186 Flags: []cli.Flag{ 187 logLevelFlag, 188 configdirFlag, 189 signerSecretFlag, 190 }, 191 Description: ` 192 The delpw command removes a password for a given address (keyfile). 193 `} 194 newAccountCommand = cli.Command{ 195 Action: utils.MigrateFlags(newAccount), 196 Name: "newaccount", 197 Usage: "Create a new account", 198 ArgsUsage: "", 199 Flags: []cli.Flag{ 200 logLevelFlag, 201 keystoreFlag, 202 utils.LightKDFFlag, 203 acceptFlag, 204 }, 205 Description: ` 206 The newaccount command creates a new keystore-backed account. It is a convenience-method 207 which can be used in lieu of an external UI.`, 208 } 209 210 gendocCommand = cli.Command{ 211 Action: GenDoc, 212 Name: "gendoc", 213 Usage: "Generate documentation about json-rpc format", 214 Description: ` 215 The gendoc generates example structures of the json-rpc communication types. 216 `} 217 ) 218 219 // AppHelpFlagGroups is the application flags, grouped by functionality. 220 var AppHelpFlagGroups = []flags.FlagGroup{ 221 { 222 Name: "FLAGS", 223 Flags: []cli.Flag{ 224 logLevelFlag, 225 keystoreFlag, 226 configdirFlag, 227 chainIdFlag, 228 utils.LightKDFFlag, 229 utils.NoUSBFlag, 230 utils.SmartCardDaemonPathFlag, 231 utils.HTTPListenAddrFlag, 232 utils.HTTPVirtualHostsFlag, 233 utils.IPCDisabledFlag, 234 utils.IPCPathFlag, 235 utils.HTTPEnabledFlag, 236 rpcPortFlag, 237 signerSecretFlag, 238 customDBFlag, 239 auditLogFlag, 240 ruleFlag, 241 stdiouiFlag, 242 testFlag, 243 advancedMode, 244 acceptFlag, 245 }, 246 }, 247 } 248 249 func init() { 250 app.Name = "Clef" 251 app.Usage = "Manage Ethereum account operations" 252 app.Flags = []cli.Flag{ 253 logLevelFlag, 254 keystoreFlag, 255 configdirFlag, 256 chainIdFlag, 257 utils.LightKDFFlag, 258 utils.NoUSBFlag, 259 utils.SmartCardDaemonPathFlag, 260 utils.HTTPListenAddrFlag, 261 utils.HTTPVirtualHostsFlag, 262 utils.IPCDisabledFlag, 263 utils.IPCPathFlag, 264 utils.HTTPEnabledFlag, 265 rpcPortFlag, 266 signerSecretFlag, 267 customDBFlag, 268 auditLogFlag, 269 ruleFlag, 270 stdiouiFlag, 271 testFlag, 272 advancedMode, 273 acceptFlag, 274 } 275 app.Action = signer 276 app.Commands = []cli.Command{initCommand, 277 attestCommand, 278 setCredentialCommand, 279 delCredentialCommand, 280 newAccountCommand, 281 gendocCommand} 282 cli.CommandHelpTemplate = flags.CommandHelpTemplate 283 // Override the default app help template 284 cli.AppHelpTemplate = flags.ClefAppHelpTemplate 285 286 // Override the default app help printer, but only for the global app help 287 originalHelpPrinter := cli.HelpPrinter 288 cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { 289 if tmpl == flags.ClefAppHelpTemplate { 290 // Render out custom usage screen 291 originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) 292 } else if tmpl == flags.CommandHelpTemplate { 293 // Iterate over all command specific flags and categorize them 294 categorized := make(map[string][]cli.Flag) 295 for _, flag := range data.(cli.Command).Flags { 296 if _, ok := categorized[flag.String()]; !ok { 297 categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag) 298 } 299 } 300 301 // sort to get a stable ordering 302 sorted := make([]flags.FlagGroup, 0, len(categorized)) 303 for cat, flgs := range categorized { 304 sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs}) 305 } 306 sort.Sort(flags.ByCategory(sorted)) 307 308 // add sorted array to data and render with default printer 309 originalHelpPrinter(w, tmpl, map[string]interface{}{ 310 "cmd": data, 311 "categorizedFlags": sorted, 312 }) 313 } else { 314 originalHelpPrinter(w, tmpl, data) 315 } 316 } 317 } 318 319 func main() { 320 if err := app.Run(os.Args); err != nil { 321 fmt.Fprintln(os.Stderr, err) 322 os.Exit(1) 323 } 324 } 325 326 func initializeSecrets(c *cli.Context) error { 327 // Get past the legal message 328 if err := initialize(c); err != nil { 329 return err 330 } 331 // Ensure the master key does not yet exist, we're not willing to overwrite 332 configDir := c.GlobalString(configdirFlag.Name) 333 if err := os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { 334 return err 335 } 336 location := filepath.Join(configDir, "masterseed.json") 337 if _, err := os.Stat(location); err == nil { 338 return fmt.Errorf("master key %v already exists, will not overwrite", location) 339 } 340 // Key file does not exist yet, generate a new one and encrypt it 341 masterSeed := make([]byte, 256) 342 num, err := io.ReadFull(rand.Reader, masterSeed) 343 if err != nil { 344 return err 345 } 346 if num != len(masterSeed) { 347 return fmt.Errorf("failed to read enough random") 348 } 349 n, p := keystore.StandardScryptN, keystore.StandardScryptP 350 if c.GlobalBool(utils.LightKDFFlag.Name) { 351 n, p = keystore.LightScryptN, keystore.LightScryptP 352 } 353 text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!" 354 var password string 355 for { 356 password = utils.GetPassPhrase(text, true) 357 if err := core.ValidatePasswordFormat(password); err != nil { 358 fmt.Printf("invalid password: %v\n", err) 359 } else { 360 fmt.Println() 361 break 362 } 363 } 364 cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p) 365 if err != nil { 366 return fmt.Errorf("failed to encrypt master seed: %v", err) 367 } 368 // Double check the master key path to ensure nothing wrote there in between 369 if err = os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { 370 return err 371 } 372 if _, err := os.Stat(location); err == nil { 373 return fmt.Errorf("master key %v already exists, will not overwrite", location) 374 } 375 // Write the file and print the usual warning message 376 if err = os.WriteFile(location, cipherSeed, 0400); err != nil { 377 return err 378 } 379 fmt.Printf("A master seed has been generated into %s\n", location) 380 fmt.Printf(` 381 This is required to be able to store credentials, such as: 382 * Passwords for keystores (used by rule engine) 383 * Storage for JavaScript auto-signing rules 384 * Hash of JavaScript rule-file 385 386 You should treat 'masterseed.json' with utmost secrecy and make a backup of it! 387 * The password is necessary but not enough, you need to back up the master seed too! 388 * The master seed does not contain your accounts, those need to be backed up separately! 389 390 `) 391 return nil 392 } 393 func attestFile(ctx *cli.Context) error { 394 if len(ctx.Args()) < 1 { 395 utils.Fatalf("This command requires an argument.") 396 } 397 if err := initialize(ctx); err != nil { 398 return err 399 } 400 401 stretchedKey, err := readMasterKey(ctx, nil) 402 if err != nil { 403 utils.Fatalf(err.Error()) 404 } 405 configDir := ctx.GlobalString(configdirFlag.Name) 406 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 407 confKey := crypto.Keccak256([]byte("config"), stretchedKey) 408 409 // Initialize the encrypted storages 410 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) 411 val := ctx.Args().First() 412 configStorage.Put("ruleset_sha256", val) 413 log.Info("Ruleset attestation updated", "sha256", val) 414 return nil 415 } 416 417 func setCredential(ctx *cli.Context) error { 418 if len(ctx.Args()) < 1 { 419 utils.Fatalf("This command requires an address to be passed as an argument") 420 } 421 if err := initialize(ctx); err != nil { 422 return err 423 } 424 addr := ctx.Args().First() 425 if !common.IsHexAddress(addr) { 426 utils.Fatalf("Invalid address specified: %s", addr) 427 } 428 address := common.HexToAddress(addr) 429 password := utils.GetPassPhrase("Please enter a password to store for this address:", true) 430 fmt.Println() 431 432 stretchedKey, err := readMasterKey(ctx, nil) 433 if err != nil { 434 utils.Fatalf(err.Error()) 435 } 436 configDir := ctx.GlobalString(configdirFlag.Name) 437 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 438 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 439 440 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 441 pwStorage.Put(address.Hex(), password) 442 443 log.Info("Credential store updated", "set", address) 444 return nil 445 } 446 447 func removeCredential(ctx *cli.Context) error { 448 if len(ctx.Args()) < 1 { 449 utils.Fatalf("This command requires an address to be passed as an argument") 450 } 451 if err := initialize(ctx); err != nil { 452 return err 453 } 454 addr := ctx.Args().First() 455 if !common.IsHexAddress(addr) { 456 utils.Fatalf("Invalid address specified: %s", addr) 457 } 458 address := common.HexToAddress(addr) 459 460 stretchedKey, err := readMasterKey(ctx, nil) 461 if err != nil { 462 utils.Fatalf(err.Error()) 463 } 464 configDir := ctx.GlobalString(configdirFlag.Name) 465 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 466 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 467 468 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 469 pwStorage.Del(address.Hex()) 470 471 log.Info("Credential store updated", "unset", address) 472 return nil 473 } 474 475 func newAccount(c *cli.Context) error { 476 if err := initialize(c); err != nil { 477 return err 478 } 479 // The newaccount is meant for users using the CLI, since 'real' external 480 // UIs can use the UI-api instead. So we'll just use the native CLI UI here. 481 var ( 482 ui = core.NewCommandlineUI() 483 pwStorage storage.Storage = &storage.NoStorage{} 484 ksLoc = c.GlobalString(keystoreFlag.Name) 485 lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) 486 ) 487 log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf) 488 am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") 489 // This gives is us access to the external API 490 apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) 491 // This gives us access to the internal API 492 internalApi := core.NewUIServerAPI(apiImpl) 493 addr, err := internalApi.New(context.Background()) 494 if err == nil { 495 fmt.Printf("Generated account %v\n", addr.String()) 496 } 497 return err 498 } 499 500 func initialize(c *cli.Context) error { 501 // Set up the logger to print everything 502 logOutput := os.Stdout 503 if c.GlobalBool(stdiouiFlag.Name) { 504 logOutput = os.Stderr 505 // If using the stdioui, we can't do the 'confirm'-flow 506 if !c.GlobalBool(acceptFlag.Name) { 507 fmt.Fprint(logOutput, legalWarning) 508 } 509 } else if !c.GlobalBool(acceptFlag.Name) { 510 if !confirm(legalWarning) { 511 return fmt.Errorf("aborted by user") 512 } 513 fmt.Println() 514 } 515 usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" 516 output := io.Writer(logOutput) 517 if usecolor { 518 output = colorable.NewColorable(logOutput) 519 } 520 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor)))) 521 522 return nil 523 } 524 525 // ipcEndpoint resolves an IPC endpoint based on a configured value, taking into 526 // account the set data folders as well as the designated platform we're currently 527 // running on. 528 func ipcEndpoint(ipcPath, datadir string) string { 529 // On windows we can only use plain top-level pipes 530 if runtime.GOOS == "windows" { 531 if strings.HasPrefix(ipcPath, `\\.\pipe\`) { 532 return ipcPath 533 } 534 return `\\.\pipe\` + ipcPath 535 } 536 // Resolve names into the data directory full paths otherwise 537 if filepath.Base(ipcPath) == ipcPath { 538 if datadir == "" { 539 return filepath.Join(os.TempDir(), ipcPath) 540 } 541 return filepath.Join(datadir, ipcPath) 542 } 543 return ipcPath 544 } 545 546 func signer(c *cli.Context) error { 547 // If we have some unrecognized command, bail out 548 if args := c.Args(); len(args) > 0 { 549 return fmt.Errorf("invalid command: %q", args[0]) 550 } 551 if err := initialize(c); err != nil { 552 return err 553 } 554 var ( 555 ui core.UIClientAPI 556 ) 557 if c.GlobalBool(stdiouiFlag.Name) { 558 log.Info("Using stdin/stdout as UI-channel") 559 ui = core.NewStdIOUI() 560 } else { 561 log.Info("Using CLI as UI-channel") 562 ui = core.NewCommandlineUI() 563 } 564 // 4bytedb data 565 fourByteLocal := c.GlobalString(customDBFlag.Name) 566 db, err := fourbyte.NewWithFile(fourByteLocal) 567 if err != nil { 568 utils.Fatalf(err.Error()) 569 } 570 embeds, locals := db.Size() 571 log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal) 572 573 var ( 574 api core.ExternalAPI 575 pwStorage storage.Storage = &storage.NoStorage{} 576 ) 577 configDir := c.GlobalString(configdirFlag.Name) 578 if stretchedKey, err := readMasterKey(c, ui); err != nil { 579 log.Warn("Failed to open master, rules disabled", "err", err) 580 } else { 581 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 582 583 // Generate domain specific keys 584 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 585 jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) 586 confkey := crypto.Keccak256([]byte("config"), stretchedKey) 587 588 // Initialize the encrypted storages 589 pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 590 jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) 591 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) 592 593 // Do we have a rule-file? 594 if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" { 595 ruleJS, err := os.ReadFile(ruleFile) 596 if err != nil { 597 log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err) 598 } else { 599 shasum := sha256.Sum256(ruleJS) 600 foundShaSum := hex.EncodeToString(shasum[:]) 601 storedShasum, _ := configStorage.Get("ruleset_sha256") 602 if storedShasum != foundShaSum { 603 log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum) 604 } else { 605 // Initialize rules 606 ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage) 607 if err != nil { 608 utils.Fatalf(err.Error()) 609 } 610 ruleEngine.Init(string(ruleJS)) 611 ui = ruleEngine 612 log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) 613 } 614 } 615 } 616 } 617 var ( 618 chainId = c.GlobalInt64(chainIdFlag.Name) 619 ksLoc = c.GlobalString(keystoreFlag.Name) 620 lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) 621 advanced = c.GlobalBool(advancedMode.Name) 622 nousb = c.GlobalBool(utils.NoUSBFlag.Name) 623 scpath = c.GlobalString(utils.SmartCardDaemonPathFlag.Name) 624 ) 625 log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, 626 "light-kdf", lightKdf, "advanced", advanced) 627 am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath) 628 apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage) 629 630 // Establish the bidirectional communication, by creating a new UI backend and registering 631 // it with the UI. 632 ui.RegisterUIServer(core.NewUIServerAPI(apiImpl)) 633 api = apiImpl 634 // Audit logging 635 if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" { 636 api, err = core.NewAuditLogger(logfile, api) 637 if err != nil { 638 utils.Fatalf(err.Error()) 639 } 640 log.Info("Audit logs configured", "file", logfile) 641 } 642 // register signer API with server 643 var ( 644 extapiURL = "n/a" 645 ipcapiURL = "n/a" 646 ) 647 rpcAPI := []rpc.API{ 648 { 649 Namespace: "account", 650 Public: true, 651 Service: api, 652 Version: "1.0"}, 653 } 654 if c.GlobalBool(utils.HTTPEnabledFlag.Name) { 655 vhosts := utils.SplitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name)) 656 cors := utils.SplitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name)) 657 658 srv := rpc.NewServer() 659 err := node.RegisterApis(rpcAPI, []string{"account"}, srv, false) 660 if err != nil { 661 utils.Fatalf("Could not register API: %w", err) 662 } 663 handler := node.NewHTTPHandlerStack(srv, cors, vhosts, nil) 664 665 // set port 666 port := c.Int(rpcPortFlag.Name) 667 668 // start http server 669 httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) 670 httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) 671 if err != nil { 672 utils.Fatalf("Could not start RPC api: %v", err) 673 } 674 extapiURL = fmt.Sprintf("http://%v/", addr) 675 log.Info("HTTP endpoint opened", "url", extapiURL) 676 677 defer func() { 678 // Don't bother imposing a timeout here. 679 httpServer.Shutdown(context.Background()) 680 log.Info("HTTP endpoint closed", "url", extapiURL) 681 }() 682 } 683 if !c.GlobalBool(utils.IPCDisabledFlag.Name) { 684 givenPath := c.GlobalString(utils.IPCPathFlag.Name) 685 ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir) 686 listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) 687 if err != nil { 688 utils.Fatalf("Could not start IPC api: %v", err) 689 } 690 log.Info("IPC endpoint opened", "url", ipcapiURL) 691 defer func() { 692 listener.Close() 693 log.Info("IPC endpoint closed", "url", ipcapiURL) 694 }() 695 } 696 697 if c.GlobalBool(testFlag.Name) { 698 log.Info("Performing UI test") 699 go testExternalUI(apiImpl) 700 } 701 ui.OnSignerStartup(core.StartupInfo{ 702 Info: map[string]interface{}{ 703 "intapi_version": core.InternalAPIVersion, 704 "extapi_version": core.ExternalAPIVersion, 705 "extapi_http": extapiURL, 706 "extapi_ipc": ipcapiURL, 707 }, 708 }) 709 710 abortChan := make(chan os.Signal, 1) 711 signal.Notify(abortChan, os.Interrupt) 712 713 sig := <-abortChan 714 log.Info("Exiting...", "signal", sig) 715 716 return nil 717 } 718 719 // DefaultConfigDir is the default config directory to use for the vaults and other 720 // persistence requirements. 721 func DefaultConfigDir() string { 722 // Try to place the data folder in the user's home dir 723 home := utils.HomeDir() 724 if home != "" { 725 if runtime.GOOS == "darwin" { 726 return filepath.Join(home, "Library", "Signer") 727 } else if runtime.GOOS == "windows" { 728 appdata := os.Getenv("APPDATA") 729 if appdata != "" { 730 return filepath.Join(appdata, "Signer") 731 } 732 return filepath.Join(home, "AppData", "Roaming", "Signer") 733 } 734 return filepath.Join(home, ".clef") 735 } 736 // As we cannot guess a stable location, return empty and handle later 737 return "" 738 } 739 740 func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { 741 var ( 742 file string 743 configDir = ctx.GlobalString(configdirFlag.Name) 744 ) 745 if ctx.GlobalIsSet(signerSecretFlag.Name) { 746 file = ctx.GlobalString(signerSecretFlag.Name) 747 } else { 748 file = filepath.Join(configDir, "masterseed.json") 749 } 750 if err := checkFile(file); err != nil { 751 return nil, err 752 } 753 cipherKey, err := os.ReadFile(file) 754 if err != nil { 755 return nil, err 756 } 757 var password string 758 // If ui is not nil, get the password from ui. 759 if ui != nil { 760 resp, err := ui.OnInputRequired(core.UserInputRequest{ 761 Title: "Master Password", 762 Prompt: "Please enter the password to decrypt the master seed", 763 IsPassword: true}) 764 if err != nil { 765 return nil, err 766 } 767 password = resp.Text 768 } else { 769 password = utils.GetPassPhrase("Decrypt master seed of clef", false) 770 } 771 masterSeed, err := decryptSeed(cipherKey, password) 772 if err != nil { 773 return nil, fmt.Errorf("failed to decrypt the master seed of clef") 774 } 775 if len(masterSeed) < 256 { 776 return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed)) 777 } 778 // Create vault location 779 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10])) 780 err = os.Mkdir(vaultLocation, 0700) 781 if err != nil && !os.IsExist(err) { 782 return nil, err 783 } 784 return masterSeed, nil 785 } 786 787 // checkFile is a convenience function to check if a file 788 // * exists 789 // * is mode 0400 (unix only) 790 func checkFile(filename string) error { 791 info, err := os.Stat(filename) 792 if err != nil { 793 return fmt.Errorf("failed stat on %s: %v", filename, err) 794 } 795 // Check the unix permission bits 796 // However, on windows, we cannot use the unix perm-bits, see 797 // https://github.com/bearnetworkchain/go-bearnetwork/issues/20123 798 if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 { 799 return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) 800 } 801 return nil 802 } 803 804 // confirm displays a text and asks for user confirmation 805 func confirm(text string) bool { 806 fmt.Print(text) 807 fmt.Printf("\nEnter 'ok' to proceed:\n> ") 808 809 text, err := bufio.NewReader(os.Stdin).ReadString('\n') 810 if err != nil { 811 log.Crit("Failed to read user input", "err", err) 812 } 813 if text := strings.TrimSpace(text); text == "ok" { 814 return true 815 } 816 return false 817 } 818 819 func testExternalUI(api *core.SignerAPI) { 820 821 ctx := context.WithValue(context.Background(), "remote", "clef binary") 822 ctx = context.WithValue(ctx, "scheme", "in-proc") 823 ctx = context.WithValue(ctx, "local", "main") 824 errs := make([]string, 0) 825 826 a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") 827 addErr := func(errStr string) { 828 log.Info("Test error", "err", errStr) 829 errs = append(errs, errStr) 830 } 831 832 queryUser := func(q string) string { 833 resp, err := api.UI.OnInputRequired(core.UserInputRequest{ 834 Title: "Testing", 835 Prompt: q, 836 }) 837 if err != nil { 838 addErr(err.Error()) 839 } 840 return resp.Text 841 } 842 expectResponse := func(testcase, question, expect string) { 843 if got := queryUser(question); got != expect { 844 addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect)) 845 } 846 } 847 expectApprove := func(testcase string, err error) { 848 if err == nil || err == accounts.ErrUnknownAccount { 849 return 850 } 851 addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) 852 } 853 expectDeny := func(testcase string, err error) { 854 if err == nil || err != core.ErrRequestDenied { 855 addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) 856 } 857 } 858 var delay = 1 * time.Second 859 // Test display of info and error 860 { 861 api.UI.ShowInfo("If you see this message, enter 'yes' to next question") 862 time.Sleep(delay) 863 expectResponse("showinfo", "Did you see the message? [yes/no]", "yes") 864 api.UI.ShowError("If you see this message, enter 'yes' to the next question") 865 time.Sleep(delay) 866 expectResponse("showerror", "Did you see the message? [yes/no]", "yes") 867 } 868 { // Sign data test - clique header 869 api.UI.ShowInfo("Please approve the next request for signing a clique header") 870 time.Sleep(delay) 871 cliqueHeader := types.Header{ 872 ParentHash: common.HexToHash("0000H45H"), 873 UncleHash: common.HexToHash("0000H45H"), 874 Coinbase: common.HexToAddress("0000H45H"), 875 Root: common.HexToHash("0000H00H"), 876 TxHash: common.HexToHash("0000H45H"), 877 ReceiptHash: common.HexToHash("0000H45H"), 878 Difficulty: big.NewInt(1337), 879 Number: big.NewInt(1337), 880 GasLimit: 1338, 881 GasUsed: 1338, 882 Time: 1338, 883 Extra: []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), 884 MixDigest: common.HexToHash("0x0000H45H"), 885 } 886 cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) 887 if err != nil { 888 utils.Fatalf("Should not error: %v", err) 889 } 890 addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") 891 _, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp)) 892 expectApprove("signdata - clique header", err) 893 } 894 { // Sign data test - typed data 895 api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data") 896 time.Sleep(delay) 897 addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") 898 data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}` 899 //_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data))) 900 var typedData apitypes.TypedData 901 json.Unmarshal([]byte(data), &typedData) 902 _, err := api.SignTypedData(ctx, *addr, typedData) 903 expectApprove("sign 712 typed data", err) 904 } 905 { // Sign data test - plain text 906 api.UI.ShowInfo("Please approve the next request for signing text") 907 time.Sleep(delay) 908 addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") 909 _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) 910 expectApprove("signdata - text", err) 911 } 912 { // Sign data test - plain text reject 913 api.UI.ShowInfo("Please deny the next request for signing text") 914 time.Sleep(delay) 915 addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") 916 _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) 917 expectDeny("signdata - text", err) 918 } 919 { // Sign transaction 920 921 api.UI.ShowInfo("Please reject next transaction") 922 time.Sleep(delay) 923 data := hexutil.Bytes([]byte{}) 924 to := common.NewMixedcaseAddress(a) 925 tx := apitypes.SendTxArgs{ 926 Data: &data, 927 Nonce: 0x1, 928 Value: hexutil.Big(*big.NewInt(6)), 929 From: common.NewMixedcaseAddress(a), 930 To: &to, 931 GasPrice: (*hexutil.Big)(big.NewInt(5)), 932 Gas: 1000, 933 Input: nil, 934 } 935 _, err := api.SignTransaction(ctx, tx, nil) 936 expectDeny("signtransaction [1]", err) 937 expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no") 938 } 939 { // Listing 940 api.UI.ShowInfo("Please reject listing-request") 941 time.Sleep(delay) 942 _, err := api.List(ctx) 943 expectDeny("list", err) 944 } 945 { // Import 946 api.UI.ShowInfo("Please reject new account-request") 947 time.Sleep(delay) 948 _, err := api.New(ctx) 949 expectDeny("newaccount", err) 950 } 951 { // Metadata 952 api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)") 953 time.Sleep(delay) 954 api.List(context.WithValue(ctx, "Origin", "origin.com")) 955 expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes") 956 } 957 958 for _, e := range errs { 959 log.Error(e) 960 } 961 result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n")) 962 api.UI.ShowInfo(result) 963 964 } 965 966 type encryptedSeedStorage struct { 967 Description string `json:"description"` 968 Version int `json:"version"` 969 Params keystore.CryptoJSON `json:"params"` 970 } 971 972 // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping, 973 // to encrypt the master seed 974 func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) { 975 cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP) 976 if err != nil { 977 return nil, err 978 } 979 return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct}) 980 } 981 982 // decryptSeed decrypts the master seed 983 func decryptSeed(keyjson []byte, auth string) ([]byte, error) { 984 var encSeed encryptedSeedStorage 985 if err := json.Unmarshal(keyjson, &encSeed); err != nil { 986 return nil, err 987 } 988 if encSeed.Version != 1 { 989 log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version)) 990 } 991 seed, err := keystore.DecryptDataV3(encSeed.Params, auth) 992 if err != nil { 993 return nil, err 994 } 995 return seed, err 996 } 997 998 // GenDoc outputs examples of all structures used in json-rpc communication 999 func GenDoc(ctx *cli.Context) { 1000 1001 var ( 1002 a = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") 1003 b = common.HexToAddress("0x1111111122222222222233333333334444444444") 1004 meta = core.Metadata{ 1005 Scheme: "http", 1006 Local: "localhost:8545", 1007 Origin: "www.malicious.ru", 1008 Remote: "localhost:9999", 1009 UserAgent: "Firefox 3.2", 1010 } 1011 output []string 1012 add = func(name, desc string, v interface{}) { 1013 if data, err := json.MarshalIndent(v, "", " "); err == nil { 1014 output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data)) 1015 } else { 1016 log.Error("Error generating output", "err", err) 1017 } 1018 } 1019 ) 1020 1021 { // Sign plain text request 1022 desc := "SignDataRequest contains information about a pending request to sign some data. " + 1023 "The data to be signed can be of various types, defined by content-type. Clef has done most " + 1024 "of the work in canonicalizing and making sense of the data, and it's up to the UI to present" + 1025 "the user with the contents of the `message`" 1026 sighash, msg := accounts.TextAndHash([]byte("hello world")) 1027 messages := []*apitypes.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}} 1028 1029 add("SignDataRequest", desc, &core.SignDataRequest{ 1030 Address: common.NewMixedcaseAddress(a), 1031 Meta: meta, 1032 ContentType: accounts.MimetypeTextPlain, 1033 Rawdata: []byte(msg), 1034 Messages: messages, 1035 Hash: sighash}) 1036 } 1037 { // Sign plain text response 1038 add("SignDataResponse - approve", "Response to SignDataRequest", 1039 &core.SignDataResponse{Approved: true}) 1040 add("SignDataResponse - deny", "Response to SignDataRequest", 1041 &core.SignDataResponse{}) 1042 } 1043 { // Sign transaction request 1044 desc := "SignTxRequest contains information about a pending request to sign a transaction. " + 1045 "Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " + 1046 "messages of various types, that the user should be informed of." + 1047 "\n\n" + 1048 "As in any request, it's important to consider that the `meta` info also contains untrusted data." + 1049 "\n\n" + 1050 "The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " + 1051 "they must be identical, otherwise an error is generated. " + 1052 "However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)" 1053 1054 data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04}) 1055 add("SignTxRequest", desc, &core.SignTxRequest{ 1056 Meta: meta, 1057 Callinfo: []apitypes.ValidationInfo{ 1058 {Typ: "Warning", Message: "Something looks odd, show this message as a warning"}, 1059 {Typ: "Info", Message: "User should see this as well"}, 1060 }, 1061 Transaction: apitypes.SendTxArgs{ 1062 Data: &data, 1063 Nonce: 0x1, 1064 Value: hexutil.Big(*big.NewInt(6)), 1065 From: common.NewMixedcaseAddress(a), 1066 To: nil, 1067 GasPrice: (*hexutil.Big)(big.NewInt(5)), 1068 Gas: 1000, 1069 Input: nil, 1070 }}) 1071 } 1072 { // Sign tx response 1073 data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01}) 1074 add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+ 1075 ", because the UI is free to make modifications to the transaction.", 1076 &core.SignTxResponse{Approved: true, 1077 Transaction: apitypes.SendTxArgs{ 1078 Data: &data, 1079 Nonce: 0x4, 1080 Value: hexutil.Big(*big.NewInt(6)), 1081 From: common.NewMixedcaseAddress(a), 1082 To: nil, 1083 GasPrice: (*hexutil.Big)(big.NewInt(5)), 1084 Gas: 1000, 1085 Input: nil, 1086 }}) 1087 add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+ 1088 "provide the transaction in return", 1089 &core.SignTxResponse{}) 1090 } 1091 { // WHen a signed tx is ready to go out 1092 desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" + 1093 "\n\n" + 1094 "This occurs _after_ successful completion of the entire signing procedure, but right before the signed " + 1095 "transaction is passed to the external caller. This method (and data) can be used by the UI to signal " + 1096 "to the user that the transaction was signed, but it is primarily useful for ruleset implementations." + 1097 "\n\n" + 1098 "A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " + 1099 "interface. By hooking into this methods, the ruleset can maintain track of that count." + 1100 "\n\n" + 1101 "**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" + 1102 " (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " + 1103 "\n\n" + 1104 "The `OnApproved` method cannot be responded to, it's purely informative" 1105 1106 rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") 1107 var tx types.Transaction 1108 tx.UnmarshalBinary(rlpdata) 1109 add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) 1110 1111 } 1112 { // User input 1113 add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)", 1114 &core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"}) 1115 add("UserInputResponse", "Response to UserInputRequest", 1116 &core.UserInputResponse{Text: "The textual response from user"}) 1117 } 1118 { // List request 1119 add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+ 1120 "full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+ 1121 "who only sees the `address`es. ", 1122 &core.ListRequest{ 1123 Meta: meta, 1124 Accounts: []accounts.Account{ 1125 {Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}}, 1126 {Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}}, 1127 }) 1128 1129 add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+ 1130 "Note: the UI is free to respond with any address the caller, regardless of whether it exists or not", 1131 &core.ListResponse{ 1132 Accounts: []accounts.Account{ 1133 { 1134 Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"), 1135 URL: accounts.URL{Path: ".. ignored .."}, 1136 }, 1137 { 1138 Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"), 1139 }, 1140 }}) 1141 } 1142 1143 fmt.Println(`## UI Client interface 1144 1145 These data types are defined in the channel between clef and the UI`) 1146 for _, elem := range output { 1147 fmt.Println(elem) 1148 } 1149 }