github.com/puffscoin/go-puffscoin@v0.0.0-20190701205704-e48ad5c90fa1/cmd/clef/main.go (about)

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