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