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