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