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