github.com/Goplush/go-ethereum@v0.0.0-20191031044858-21506be82b68/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/Fantom-foundation/go-ethereum/accounts"
    39  	"github.com/Fantom-foundation/go-ethereum/accounts/keystore"
    40  	"github.com/Fantom-foundation/go-ethereum/cmd/utils"
    41  	"github.com/Fantom-foundation/go-ethereum/common"
    42  	"github.com/Fantom-foundation/go-ethereum/common/hexutil"
    43  	"github.com/Fantom-foundation/go-ethereum/console"
    44  	"github.com/Fantom-foundation/go-ethereum/core/types"
    45  	"github.com/Fantom-foundation/go-ethereum/crypto"
    46  	"github.com/Fantom-foundation/go-ethereum/internal/ethapi"
    47  	"github.com/Fantom-foundation/go-ethereum/log"
    48  	"github.com/Fantom-foundation/go-ethereum/node"
    49  	"github.com/Fantom-foundation/go-ethereum/params"
    50  	"github.com/Fantom-foundation/go-ethereum/rlp"
    51  	"github.com/Fantom-foundation/go-ethereum/rpc"
    52  	"github.com/Fantom-foundation/go-ethereum/signer/core"
    53  	"github.com/Fantom-foundation/go-ethereum/signer/fourbyte"
    54  	"github.com/Fantom-foundation/go-ethereum/signer/rules"
    55  	"github.com/Fantom-foundation/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  // ipcEndpoint resolves an IPC endpoint based on a configured value, taking into
   408  // account the set data folders as well as the designated platform we're currently
   409  // running on.
   410  func ipcEndpoint(ipcPath, datadir string) string {
   411  	// On windows we can only use plain top-level pipes
   412  	if runtime.GOOS == "windows" {
   413  		if strings.HasPrefix(ipcPath, `\\.\pipe\`) {
   414  			return ipcPath
   415  		}
   416  		return `\\.\pipe\` + ipcPath
   417  	}
   418  	// Resolve names into the data directory full paths otherwise
   419  	if filepath.Base(ipcPath) == ipcPath {
   420  		if datadir == "" {
   421  			return filepath.Join(os.TempDir(), ipcPath)
   422  		}
   423  		return filepath.Join(datadir, ipcPath)
   424  	}
   425  	return ipcPath
   426  }
   427  
   428  func signer(c *cli.Context) error {
   429  	// If we have some unrecognized command, bail out
   430  	if args := c.Args(); len(args) > 0 {
   431  		return fmt.Errorf("invalid command: %q", args[0])
   432  	}
   433  	if err := initialize(c); err != nil {
   434  		return err
   435  	}
   436  	var (
   437  		ui core.UIClientAPI
   438  	)
   439  	if c.GlobalBool(stdiouiFlag.Name) {
   440  		log.Info("Using stdin/stdout as UI-channel")
   441  		ui = core.NewStdIOUI()
   442  	} else {
   443  		log.Info("Using CLI as UI-channel")
   444  		ui = core.NewCommandlineUI()
   445  	}
   446  	// 4bytedb data
   447  	fourByteLocal := c.GlobalString(customDBFlag.Name)
   448  	db, err := fourbyte.NewWithFile(fourByteLocal)
   449  	if err != nil {
   450  		utils.Fatalf(err.Error())
   451  	}
   452  	embeds, locals := db.Size()
   453  	log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal)
   454  
   455  	var (
   456  		api       core.ExternalAPI
   457  		pwStorage storage.Storage = &storage.NoStorage{}
   458  	)
   459  
   460  	configDir := c.GlobalString(configdirFlag.Name)
   461  	if stretchedKey, err := readMasterKey(c, ui); err != nil {
   462  		log.Warn("Failed to open master, rules disabled", "err", err)
   463  	} else {
   464  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   465  
   466  		// Generate domain specific keys
   467  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   468  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   469  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   470  
   471  		// Initialize the encrypted storages
   472  		pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   473  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   474  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   475  
   476  		// Do we have a rule-file?
   477  		if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
   478  			ruleJS, err := ioutil.ReadFile(ruleFile)
   479  			if err != nil {
   480  				log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err)
   481  			} else {
   482  				shasum := sha256.Sum256(ruleJS)
   483  				foundShaSum := hex.EncodeToString(shasum[:])
   484  				storedShasum, _ := configStorage.Get("ruleset_sha256")
   485  				if storedShasum != foundShaSum {
   486  					log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum)
   487  				} else {
   488  					// Initialize rules
   489  					ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
   490  					if err != nil {
   491  						utils.Fatalf(err.Error())
   492  					}
   493  					ruleEngine.Init(string(ruleJS))
   494  					ui = ruleEngine
   495  					log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   496  				}
   497  			}
   498  		}
   499  	}
   500  	var (
   501  		chainId  = c.GlobalInt64(chainIdFlag.Name)
   502  		ksLoc    = c.GlobalString(keystoreFlag.Name)
   503  		lightKdf = c.GlobalBool(utils.LightKDFFlag.Name)
   504  		advanced = c.GlobalBool(advancedMode.Name)
   505  		nousb    = c.GlobalBool(utils.NoUSBFlag.Name)
   506  		scpath   = c.GlobalString(utils.SmartCardDaemonPathFlag.Name)
   507  	)
   508  	log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
   509  		"light-kdf", lightKdf, "advanced", advanced)
   510  	am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
   511  	apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
   512  
   513  	// Establish the bidirectional communication, by creating a new UI backend and registering
   514  	// it with the UI.
   515  	ui.RegisterUIServer(core.NewUIServerAPI(apiImpl))
   516  	api = apiImpl
   517  	// Audit logging
   518  	if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" {
   519  		api, err = core.NewAuditLogger(logfile, api)
   520  		if err != nil {
   521  			utils.Fatalf(err.Error())
   522  		}
   523  		log.Info("Audit logs configured", "file", logfile)
   524  	}
   525  	// register signer API with server
   526  	var (
   527  		extapiURL = "n/a"
   528  		ipcapiURL = "n/a"
   529  	)
   530  	rpcAPI := []rpc.API{
   531  		{
   532  			Namespace: "account",
   533  			Public:    true,
   534  			Service:   api,
   535  			Version:   "1.0"},
   536  	}
   537  	if c.GlobalBool(utils.RPCEnabledFlag.Name) {
   538  		vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
   539  		cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
   540  
   541  		// start http server
   542  		httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   543  		listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
   544  		if err != nil {
   545  			utils.Fatalf("Could not start RPC api: %v", err)
   546  		}
   547  		extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
   548  		log.Info("HTTP endpoint opened", "url", extapiURL)
   549  
   550  		defer func() {
   551  			listener.Close()
   552  			log.Info("HTTP endpoint closed", "url", httpEndpoint)
   553  		}()
   554  	}
   555  	if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
   556  		givenPath := c.GlobalString(utils.IPCPathFlag.Name)
   557  		ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir)
   558  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   559  		if err != nil {
   560  			utils.Fatalf("Could not start IPC api: %v", err)
   561  		}
   562  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   563  		defer func() {
   564  			listener.Close()
   565  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   566  		}()
   567  	}
   568  
   569  	if c.GlobalBool(testFlag.Name) {
   570  		log.Info("Performing UI test")
   571  		go testExternalUI(apiImpl)
   572  	}
   573  	ui.OnSignerStartup(core.StartupInfo{
   574  		Info: map[string]interface{}{
   575  			"intapi_version": core.InternalAPIVersion,
   576  			"extapi_version": core.ExternalAPIVersion,
   577  			"extapi_http":    extapiURL,
   578  			"extapi_ipc":     ipcapiURL,
   579  		},
   580  	})
   581  
   582  	abortChan := make(chan os.Signal)
   583  	signal.Notify(abortChan, os.Interrupt)
   584  
   585  	sig := <-abortChan
   586  	log.Info("Exiting...", "signal", sig)
   587  
   588  	return nil
   589  }
   590  
   591  // splitAndTrim splits input separated by a comma
   592  // and trims excessive white space from the substrings.
   593  func splitAndTrim(input string) []string {
   594  	result := strings.Split(input, ",")
   595  	for i, r := range result {
   596  		result[i] = strings.TrimSpace(r)
   597  	}
   598  	return result
   599  }
   600  
   601  // DefaultConfigDir is the default config directory to use for the vaults and other
   602  // persistence requirements.
   603  func DefaultConfigDir() string {
   604  	// Try to place the data folder in the user's home dir
   605  	home := homeDir()
   606  	if home != "" {
   607  		if runtime.GOOS == "darwin" {
   608  			return filepath.Join(home, "Library", "Signer")
   609  		} else if runtime.GOOS == "windows" {
   610  			appdata := os.Getenv("APPDATA")
   611  			if appdata != "" {
   612  				return filepath.Join(appdata, "Signer")
   613  			} else {
   614  				return filepath.Join(home, "AppData", "Roaming", "Signer")
   615  			}
   616  		} else {
   617  			return filepath.Join(home, ".clef")
   618  		}
   619  	}
   620  	// As we cannot guess a stable location, return empty and handle later
   621  	return ""
   622  }
   623  
   624  func homeDir() string {
   625  	if home := os.Getenv("HOME"); home != "" {
   626  		return home
   627  	}
   628  	if usr, err := user.Current(); err == nil {
   629  		return usr.HomeDir
   630  	}
   631  	return ""
   632  }
   633  func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
   634  	var (
   635  		file      string
   636  		configDir = ctx.GlobalString(configdirFlag.Name)
   637  	)
   638  	if ctx.GlobalIsSet(signerSecretFlag.Name) {
   639  		file = ctx.GlobalString(signerSecretFlag.Name)
   640  	} else {
   641  		file = filepath.Join(configDir, "masterseed.json")
   642  	}
   643  	if err := checkFile(file); err != nil {
   644  		return nil, err
   645  	}
   646  	cipherKey, err := ioutil.ReadFile(file)
   647  	if err != nil {
   648  		return nil, err
   649  	}
   650  	var password string
   651  	// If ui is not nil, get the password from ui.
   652  	if ui != nil {
   653  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   654  			Title:      "Master Password",
   655  			Prompt:     "Please enter the password to decrypt the master seed",
   656  			IsPassword: true})
   657  		if err != nil {
   658  			return nil, err
   659  		}
   660  		password = resp.Text
   661  	} else {
   662  		password = getPassPhrase("Decrypt master seed of clef", false)
   663  	}
   664  	masterSeed, err := decryptSeed(cipherKey, password)
   665  	if err != nil {
   666  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   667  	}
   668  	if len(masterSeed) < 256 {
   669  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   670  	}
   671  	// Create vault location
   672  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   673  	err = os.Mkdir(vaultLocation, 0700)
   674  	if err != nil && !os.IsExist(err) {
   675  		return nil, err
   676  	}
   677  	return masterSeed, nil
   678  }
   679  
   680  // checkFile is a convenience function to check if a file
   681  // * exists
   682  // * is mode 0400
   683  func checkFile(filename string) error {
   684  	info, err := os.Stat(filename)
   685  	if err != nil {
   686  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   687  	}
   688  	// Check the unix permission bits
   689  	if info.Mode().Perm()&0377 != 0 {
   690  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   691  	}
   692  	return nil
   693  }
   694  
   695  // confirm displays a text and asks for user confirmation
   696  func confirm(text string) bool {
   697  	fmt.Printf(text)
   698  	fmt.Printf("\nEnter 'ok' to proceed:\n> ")
   699  
   700  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   701  	if err != nil {
   702  		log.Crit("Failed to read user input", "err", err)
   703  	}
   704  	if text := strings.TrimSpace(text); text == "ok" {
   705  		return true
   706  	}
   707  	return false
   708  }
   709  
   710  func testExternalUI(api *core.SignerAPI) {
   711  
   712  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   713  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   714  	ctx = context.WithValue(ctx, "local", "main")
   715  	errs := make([]string, 0)
   716  
   717  	a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   718  	addErr := func(errStr string) {
   719  		log.Info("Test error", "err", errStr)
   720  		errs = append(errs, errStr)
   721  	}
   722  
   723  	queryUser := func(q string) string {
   724  		resp, err := api.UI.OnInputRequired(core.UserInputRequest{
   725  			Title:  "Testing",
   726  			Prompt: q,
   727  		})
   728  		if err != nil {
   729  			addErr(err.Error())
   730  		}
   731  		return resp.Text
   732  	}
   733  	expectResponse := func(testcase, question, expect string) {
   734  		if got := queryUser(question); got != expect {
   735  			addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
   736  		}
   737  	}
   738  	expectApprove := func(testcase string, err error) {
   739  		if err == nil || err == accounts.ErrUnknownAccount {
   740  			return
   741  		}
   742  		addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
   743  	}
   744  	expectDeny := func(testcase string, err error) {
   745  		if err == nil || err != core.ErrRequestDenied {
   746  			addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
   747  		}
   748  	}
   749  	var delay = 1 * time.Second
   750  	// Test display of info and error
   751  	{
   752  		api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
   753  		time.Sleep(delay)
   754  		expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
   755  		api.UI.ShowError("If you see this message, enter 'yes' to the next question")
   756  		time.Sleep(delay)
   757  		expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
   758  	}
   759  	{ // Sign data test - clique header
   760  		api.UI.ShowInfo("Please approve the next request for signing a clique header")
   761  		time.Sleep(delay)
   762  		cliqueHeader := types.Header{
   763  			common.HexToHash("0000H45H"),
   764  			common.HexToHash("0000H45H"),
   765  			common.HexToAddress("0000H45H"),
   766  			common.HexToHash("0000H00H"),
   767  			common.HexToHash("0000H45H"),
   768  			common.HexToHash("0000H45H"),
   769  			types.Bloom{},
   770  			big.NewInt(1337),
   771  			big.NewInt(1337),
   772  			1338,
   773  			1338,
   774  			1338,
   775  			[]byte("Extra data Extra data Extra data  Extra data  Extra data  Extra data  Extra data Extra data"),
   776  			common.HexToHash("0x0000H45H"),
   777  			types.BlockNonce{},
   778  		}
   779  		cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
   780  		if err != nil {
   781  			utils.Fatalf("Should not error: %v", err)
   782  		}
   783  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   784  		_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
   785  		expectApprove("signdata - clique header", err)
   786  	}
   787  	{ // Sign data test - typed data
   788  		api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
   789  		time.Sleep(delay)
   790  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   791  		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!"}}`
   792  		//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
   793  		var typedData core.TypedData
   794  		json.Unmarshal([]byte(data), &typedData)
   795  		_, err := api.SignTypedData(ctx, *addr, typedData)
   796  		expectApprove("sign 712 typed data", err)
   797  	}
   798  	{ // Sign data test - plain text
   799  		api.UI.ShowInfo("Please approve the next request for signing text")
   800  		time.Sleep(delay)
   801  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   802  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   803  		expectApprove("signdata - text", err)
   804  	}
   805  	{ // Sign data test - plain text reject
   806  		api.UI.ShowInfo("Please deny the next request for signing text")
   807  		time.Sleep(delay)
   808  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   809  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   810  		expectDeny("signdata - text", err)
   811  	}
   812  	{ // Sign transaction
   813  
   814  		api.UI.ShowInfo("Please reject next transaction")
   815  		time.Sleep(delay)
   816  		data := hexutil.Bytes([]byte{})
   817  		to := common.NewMixedcaseAddress(a)
   818  		tx := core.SendTxArgs{
   819  			Data:     &data,
   820  			Nonce:    0x1,
   821  			Value:    hexutil.Big(*big.NewInt(6)),
   822  			From:     common.NewMixedcaseAddress(a),
   823  			To:       &to,
   824  			GasPrice: hexutil.Big(*big.NewInt(5)),
   825  			Gas:      1000,
   826  			Input:    nil,
   827  		}
   828  		_, err := api.SignTransaction(ctx, tx, nil)
   829  		expectDeny("signtransaction [1]", err)
   830  		expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
   831  	}
   832  	{ // Listing
   833  		api.UI.ShowInfo("Please reject listing-request")
   834  		time.Sleep(delay)
   835  		_, err := api.List(ctx)
   836  		expectDeny("list", err)
   837  	}
   838  	{ // Import
   839  		api.UI.ShowInfo("Please reject new account-request")
   840  		time.Sleep(delay)
   841  		_, err := api.New(ctx)
   842  		expectDeny("newaccount", err)
   843  	}
   844  	{ // Metadata
   845  		api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
   846  		time.Sleep(delay)
   847  		api.List(context.WithValue(ctx, "Origin", "origin.com"))
   848  		expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
   849  	}
   850  
   851  	for _, e := range errs {
   852  		log.Error(e)
   853  	}
   854  	result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
   855  	api.UI.ShowInfo(result)
   856  
   857  }
   858  
   859  // getPassPhrase retrieves the password associated with clef, either fetched
   860  // from a list of preloaded passphrases, or requested interactively from the user.
   861  // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
   862  func getPassPhrase(prompt string, confirmation bool) string {
   863  	fmt.Println(prompt)
   864  	password, err := console.Stdin.PromptPassword("Password: ")
   865  	if err != nil {
   866  		utils.Fatalf("Failed to read password: %v", err)
   867  	}
   868  	if confirmation {
   869  		confirm, err := console.Stdin.PromptPassword("Repeat password: ")
   870  		if err != nil {
   871  			utils.Fatalf("Failed to read password confirmation: %v", err)
   872  		}
   873  		if password != confirm {
   874  			utils.Fatalf("Passwords do not match")
   875  		}
   876  	}
   877  	return password
   878  }
   879  
   880  type encryptedSeedStorage struct {
   881  	Description string              `json:"description"`
   882  	Version     int                 `json:"version"`
   883  	Params      keystore.CryptoJSON `json:"params"`
   884  }
   885  
   886  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   887  // to encrypt the master seed
   888  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   889  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   890  	if err != nil {
   891  		return nil, err
   892  	}
   893  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   894  }
   895  
   896  // decryptSeed decrypts the master seed
   897  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
   898  	var encSeed encryptedSeedStorage
   899  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
   900  		return nil, err
   901  	}
   902  	if encSeed.Version != 1 {
   903  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
   904  	}
   905  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
   906  	if err != nil {
   907  		return nil, err
   908  	}
   909  	return seed, err
   910  }
   911  
   912  // GenDoc outputs examples of all structures used in json-rpc communication
   913  func GenDoc(ctx *cli.Context) {
   914  
   915  	var (
   916  		a    = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   917  		b    = common.HexToAddress("0x1111111122222222222233333333334444444444")
   918  		meta = core.Metadata{
   919  			Scheme:    "http",
   920  			Local:     "localhost:8545",
   921  			Origin:    "www.malicious.ru",
   922  			Remote:    "localhost:9999",
   923  			UserAgent: "Firefox 3.2",
   924  		}
   925  		output []string
   926  		add    = func(name, desc string, v interface{}) {
   927  			if data, err := json.MarshalIndent(v, "", "  "); err == nil {
   928  				output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data))
   929  			} else {
   930  				log.Error("Error generating output", err)
   931  			}
   932  		}
   933  	)
   934  
   935  	{ // Sign plain text request
   936  		desc := "SignDataRequest contains information about a pending request to sign some data. " +
   937  			"The data to be signed can be of various types, defined by content-type. Clef has done most " +
   938  			"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
   939  			"the user with the contents of the `message`"
   940  		sighash, msg := accounts.TextAndHash([]byte("hello world"))
   941  		messages := []*core.NameValueType{{"message", msg, accounts.MimetypeTextPlain}}
   942  
   943  		add("SignDataRequest", desc, &core.SignDataRequest{
   944  			Address:     common.NewMixedcaseAddress(a),
   945  			Meta:        meta,
   946  			ContentType: accounts.MimetypeTextPlain,
   947  			Rawdata:     []byte(msg),
   948  			Messages:    messages,
   949  			Hash:        sighash})
   950  	}
   951  	{ // Sign plain text response
   952  		add("SignDataResponse - approve", "Response to SignDataRequest",
   953  			&core.SignDataResponse{Approved: true})
   954  		add("SignDataResponse - deny", "Response to SignDataRequest",
   955  			&core.SignDataResponse{})
   956  	}
   957  	{ // Sign transaction request
   958  		desc := "SignTxRequest contains information about a pending request to sign a transaction. " +
   959  			"Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " +
   960  			"messages of various types, that the user should be informed of." +
   961  			"\n\n" +
   962  			"As in any request, it's important to consider that the `meta` info also contains untrusted data." +
   963  			"\n\n" +
   964  			"The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " +
   965  			"they must be identical, otherwise an error is generated. " +
   966  			"However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)"
   967  
   968  		data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04})
   969  		add("SignTxRequest", desc, &core.SignTxRequest{
   970  			Meta: meta,
   971  			Callinfo: []core.ValidationInfo{
   972  				{"Warning", "Something looks odd, show this message as a warning"},
   973  				{"Info", "User should see this aswell"},
   974  			},
   975  			Transaction: core.SendTxArgs{
   976  				Data:     &data,
   977  				Nonce:    0x1,
   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  	}
   986  	{ // Sign tx response
   987  		data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
   988  		add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
   989  			", because the UI is free to make modifications to the transaction.",
   990  			&core.SignTxResponse{Approved: true,
   991  				Transaction: core.SendTxArgs{
   992  					Data:     &data,
   993  					Nonce:    0x4,
   994  					Value:    hexutil.Big(*big.NewInt(6)),
   995  					From:     common.NewMixedcaseAddress(a),
   996  					To:       nil,
   997  					GasPrice: hexutil.Big(*big.NewInt(5)),
   998  					Gas:      1000,
   999  					Input:    nil,
  1000  				}})
  1001  		add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
  1002  			"provide the transaction in return",
  1003  			&core.SignTxResponse{})
  1004  	}
  1005  	{ // WHen a signed tx is ready to go out
  1006  		desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
  1007  			"\n\n" +
  1008  			"This occurs _after_ successful completion of the entire signing procedure, but right before the signed " +
  1009  			"transaction is passed to the external caller. This method (and data) can be used by the UI to signal " +
  1010  			"to the user that the transaction was signed, but it is primarily useful for ruleset implementations." +
  1011  			"\n\n" +
  1012  			"A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " +
  1013  			"interface. By hooking into this methods, the ruleset can maintain track of that count." +
  1014  			"\n\n" +
  1015  			"**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" +
  1016  			" (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " +
  1017  			"\n\n" +
  1018  			"The `OnApproved` method cannot be responded to, it's purely informative"
  1019  
  1020  		rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed")
  1021  		var tx types.Transaction
  1022  		rlp.DecodeBytes(rlpdata, &tx)
  1023  		add("OnApproved - SignTransactionResult", desc, &ethapi.SignTransactionResult{Raw: rlpdata, Tx: &tx})
  1024  
  1025  	}
  1026  	{ // User input
  1027  		add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
  1028  			&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
  1029  		add("UserInputResponse", "Response to UserInputRequest",
  1030  			&core.UserInputResponse{Text: "The textual response from user"})
  1031  	}
  1032  	{ // List request
  1033  		add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+
  1034  			"full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+
  1035  			"who only sees the `address`es. ",
  1036  			&core.ListRequest{
  1037  				Meta: meta,
  1038  				Accounts: []accounts.Account{
  1039  					{a, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}},
  1040  					{b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
  1041  			})
  1042  
  1043  		add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
  1044  			"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
  1045  			&core.ListResponse{
  1046  				Accounts: []accounts.Account{
  1047  					{common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"), accounts.URL{Path: ".. ignored .."}},
  1048  					{common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"), accounts.URL{}},
  1049  				}})
  1050  	}
  1051  
  1052  	fmt.Println(`## UI Client interface
  1053  
  1054  These data types are defined in the channel between clef and the UI`)
  1055  	for _, elem := range output {
  1056  		fmt.Println(elem)
  1057  	}
  1058  }