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