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