github.com/MaynardMiner/ethereumprogpow@v1.8.23/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  	"os"
    32  	"os/signal"
    33  	"os/user"
    34  	"path/filepath"
    35  	"runtime"
    36  	"strings"
    37  
    38  	"github.com/ethereumprogpow/ethereumprogpow/accounts/keystore"
    39  	"github.com/ethereumprogpow/ethereumprogpow/cmd/utils"
    40  	"github.com/ethereumprogpow/ethereumprogpow/common"
    41  	"github.com/ethereumprogpow/ethereumprogpow/console"
    42  	"github.com/ethereumprogpow/ethereumprogpow/crypto"
    43  	"github.com/ethereumprogpow/ethereumprogpow/log"
    44  	"github.com/ethereumprogpow/ethereumprogpow/node"
    45  	"github.com/ethereumprogpow/ethereumprogpow/rpc"
    46  	"github.com/ethereumprogpow/ethereumprogpow/signer/core"
    47  	"github.com/ethereumprogpow/ethereumprogpow/signer/rules"
    48  	"github.com/ethereumprogpow/ethereumprogpow/signer/storage"
    49  	"gopkg.in/urfave/cli.v1"
    50  )
    51  
    52  // ExternalAPIVersion -- see extapi_changelog.md
    53  const ExternalAPIVersion = "4.0.0"
    54  
    55  // InternalAPIVersion -- see intapi_changelog.md
    56  const InternalAPIVersion = "3.0.0"
    57  
    58  const legalWarning = `
    59  WARNING! 
    60  
    61  Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there
    62  are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software
    63  unless you agree to take full responsibility for doing so, and know what you are doing. 
    64  
    65  TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 
    66  
    67  `
    68  
    69  var (
    70  	logLevelFlag = cli.IntFlag{
    71  		Name:  "loglevel",
    72  		Value: 4,
    73  		Usage: "log level to emit to the screen",
    74  	}
    75  	advancedMode = cli.BoolFlag{
    76  		Name:  "advanced",
    77  		Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off",
    78  	}
    79  	keystoreFlag = cli.StringFlag{
    80  		Name:  "keystore",
    81  		Value: filepath.Join(node.DefaultDataDir(), "keystore"),
    82  		Usage: "Directory for the keystore",
    83  	}
    84  	configdirFlag = cli.StringFlag{
    85  		Name:  "configdir",
    86  		Value: DefaultConfigDir(),
    87  		Usage: "Directory for Clef configuration",
    88  	}
    89  	rpcPortFlag = cli.IntFlag{
    90  		Name:  "rpcport",
    91  		Usage: "HTTP-RPC server listening port",
    92  		Value: node.DefaultHTTPPort + 5,
    93  	}
    94  	signerSecretFlag = cli.StringFlag{
    95  		Name:  "signersecret",
    96  		Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
    97  	}
    98  	dBFlag = cli.StringFlag{
    99  		Name:  "4bytedb",
   100  		Usage: "File containing 4byte-identifiers",
   101  		Value: "./4byte.json",
   102  	}
   103  	customDBFlag = cli.StringFlag{
   104  		Name:  "4bytedb-custom",
   105  		Usage: "File used for writing new 4byte-identifiers submitted via API",
   106  		Value: "./4byte-custom.json",
   107  	}
   108  	auditLogFlag = cli.StringFlag{
   109  		Name:  "auditlog",
   110  		Usage: "File used to emit audit logs. Set to \"\" to disable",
   111  		Value: "audit.log",
   112  	}
   113  	ruleFlag = cli.StringFlag{
   114  		Name:  "rules",
   115  		Usage: "Enable rule-engine",
   116  		Value: "rules.json",
   117  	}
   118  	stdiouiFlag = cli.BoolFlag{
   119  		Name: "stdio-ui",
   120  		Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
   121  			"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
   122  			"interface, and can be used when Clef is started by an external process.",
   123  	}
   124  	testFlag = cli.BoolFlag{
   125  		Name:  "stdio-ui-test",
   126  		Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.",
   127  	}
   128  	app         = cli.NewApp()
   129  	initCommand = cli.Command{
   130  		Action:    utils.MigrateFlags(initializeSecrets),
   131  		Name:      "init",
   132  		Usage:     "Initialize the signer, generate secret storage",
   133  		ArgsUsage: "",
   134  		Flags: []cli.Flag{
   135  			logLevelFlag,
   136  			configdirFlag,
   137  		},
   138  		Description: `
   139  The init command generates a master seed which Clef can use to store credentials and data needed for 
   140  the rule-engine to work.`,
   141  	}
   142  	attestCommand = cli.Command{
   143  		Action:    utils.MigrateFlags(attestFile),
   144  		Name:      "attest",
   145  		Usage:     "Attest that a js-file is to be used",
   146  		ArgsUsage: "<sha256sum>",
   147  		Flags: []cli.Flag{
   148  			logLevelFlag,
   149  			configdirFlag,
   150  			signerSecretFlag,
   151  		},
   152  		Description: `
   153  The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 
   154  incoming requests. 
   155  
   156  Whenever you make an edit to the rule file, you need to use attestation to tell 
   157  Clef that the file is 'safe' to execute.`,
   158  	}
   159  
   160  	setCredentialCommand = cli.Command{
   161  		Action:    utils.MigrateFlags(setCredential),
   162  		Name:      "setpw",
   163  		Usage:     "Store a credential for a keystore file",
   164  		ArgsUsage: "<address>",
   165  		Flags: []cli.Flag{
   166  			logLevelFlag,
   167  			configdirFlag,
   168  			signerSecretFlag,
   169  		},
   170  		Description: `
   171  		The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will 
   172  remove any stored credential for that address (keyfile)
   173  `,
   174  	}
   175  )
   176  
   177  func init() {
   178  	app.Name = "Clef"
   179  	app.Usage = "Manage Ethereum account operations"
   180  	app.Flags = []cli.Flag{
   181  		logLevelFlag,
   182  		keystoreFlag,
   183  		configdirFlag,
   184  		utils.NetworkIdFlag,
   185  		utils.LightKDFFlag,
   186  		utils.NoUSBFlag,
   187  		utils.RPCListenAddrFlag,
   188  		utils.RPCVirtualHostsFlag,
   189  		utils.IPCDisabledFlag,
   190  		utils.IPCPathFlag,
   191  		utils.RPCEnabledFlag,
   192  		rpcPortFlag,
   193  		signerSecretFlag,
   194  		dBFlag,
   195  		customDBFlag,
   196  		auditLogFlag,
   197  		ruleFlag,
   198  		stdiouiFlag,
   199  		testFlag,
   200  		advancedMode,
   201  	}
   202  	app.Action = signer
   203  	app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand}
   204  
   205  }
   206  func main() {
   207  	if err := app.Run(os.Args); err != nil {
   208  		fmt.Fprintln(os.Stderr, err)
   209  		os.Exit(1)
   210  	}
   211  }
   212  
   213  func initializeSecrets(c *cli.Context) error {
   214  	if err := initialize(c); err != nil {
   215  		return err
   216  	}
   217  	configDir := c.GlobalString(configdirFlag.Name)
   218  
   219  	masterSeed := make([]byte, 256)
   220  	num, err := io.ReadFull(rand.Reader, masterSeed)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	if num != len(masterSeed) {
   225  		return fmt.Errorf("failed to read enough random")
   226  	}
   227  
   228  	n, p := keystore.StandardScryptN, keystore.StandardScryptP
   229  	if c.GlobalBool(utils.LightKDFFlag.Name) {
   230  		n, p = keystore.LightScryptN, keystore.LightScryptP
   231  	}
   232  	text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password."
   233  	var password string
   234  	for {
   235  		password = getPassPhrase(text, true)
   236  		if err := core.ValidatePasswordFormat(password); err != nil {
   237  			fmt.Printf("invalid password: %v\n", err)
   238  		} else {
   239  			break
   240  		}
   241  	}
   242  	cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p)
   243  	if err != nil {
   244  		return fmt.Errorf("failed to encrypt master seed: %v", err)
   245  	}
   246  
   247  	err = os.Mkdir(configDir, 0700)
   248  	if err != nil && !os.IsExist(err) {
   249  		return err
   250  	}
   251  	location := filepath.Join(configDir, "masterseed.json")
   252  	if _, err := os.Stat(location); err == nil {
   253  		return fmt.Errorf("file %v already exists, will not overwrite", location)
   254  	}
   255  	err = ioutil.WriteFile(location, cipherSeed, 0400)
   256  	if err != nil {
   257  		return err
   258  	}
   259  	fmt.Printf("A master seed has been generated into %s\n", location)
   260  	fmt.Printf(`
   261  This is required to be able to store credentials, such as : 
   262  * Passwords for keystores (used by rule engine)
   263  * Storage for javascript rules
   264  * Hash of rule-file
   265  
   266  You should treat that file with utmost secrecy, and make a backup of it. 
   267  NOTE: This file does not contain your accounts. Those need to be backed up separately!
   268  
   269  `)
   270  	return nil
   271  }
   272  func attestFile(ctx *cli.Context) error {
   273  	if len(ctx.Args()) < 1 {
   274  		utils.Fatalf("This command requires an argument.")
   275  	}
   276  	if err := initialize(ctx); err != nil {
   277  		return err
   278  	}
   279  
   280  	stretchedKey, err := readMasterKey(ctx, nil)
   281  	if err != nil {
   282  		utils.Fatalf(err.Error())
   283  	}
   284  	configDir := ctx.GlobalString(configdirFlag.Name)
   285  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   286  	confKey := crypto.Keccak256([]byte("config"), stretchedKey)
   287  
   288  	// Initialize the encrypted storages
   289  	configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
   290  	val := ctx.Args().First()
   291  	configStorage.Put("ruleset_sha256", val)
   292  	log.Info("Ruleset attestation updated", "sha256", val)
   293  	return nil
   294  }
   295  
   296  func setCredential(ctx *cli.Context) error {
   297  	if len(ctx.Args()) < 1 {
   298  		utils.Fatalf("This command requires an address to be passed as an argument.")
   299  	}
   300  	if err := initialize(ctx); err != nil {
   301  		return err
   302  	}
   303  
   304  	address := ctx.Args().First()
   305  	password := getPassPhrase("Enter a passphrase to store with this address.", true)
   306  
   307  	stretchedKey, err := readMasterKey(ctx, nil)
   308  	if err != nil {
   309  		utils.Fatalf(err.Error())
   310  	}
   311  	configDir := ctx.GlobalString(configdirFlag.Name)
   312  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   313  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   314  
   315  	// Initialize the encrypted storages
   316  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   317  	pwStorage.Put(address, password)
   318  	log.Info("Credential store updated", "key", address)
   319  	return nil
   320  }
   321  
   322  func initialize(c *cli.Context) error {
   323  	// Set up the logger to print everything
   324  	logOutput := os.Stdout
   325  	if c.GlobalBool(stdiouiFlag.Name) {
   326  		logOutput = os.Stderr
   327  		// If using the stdioui, we can't do the 'confirm'-flow
   328  		fmt.Fprintf(logOutput, legalWarning)
   329  	} else {
   330  		if !confirm(legalWarning) {
   331  			return fmt.Errorf("aborted by user")
   332  		}
   333  	}
   334  
   335  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
   336  	return nil
   337  }
   338  
   339  func signer(c *cli.Context) error {
   340  	if err := initialize(c); err != nil {
   341  		return err
   342  	}
   343  	var (
   344  		ui core.SignerUI
   345  	)
   346  	if c.GlobalBool(stdiouiFlag.Name) {
   347  		log.Info("Using stdin/stdout as UI-channel")
   348  		ui = core.NewStdIOUI()
   349  	} else {
   350  		log.Info("Using CLI as UI-channel")
   351  		ui = core.NewCommandlineUI()
   352  	}
   353  	fourByteDb := c.GlobalString(dBFlag.Name)
   354  	fourByteLocal := c.GlobalString(customDBFlag.Name)
   355  	db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal)
   356  	if err != nil {
   357  		utils.Fatalf(err.Error())
   358  	}
   359  	log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
   360  
   361  	var (
   362  		api core.ExternalAPI
   363  	)
   364  
   365  	configDir := c.GlobalString(configdirFlag.Name)
   366  	if stretchedKey, err := readMasterKey(c, ui); err != nil {
   367  		log.Info("No master seed provided, rules disabled", "error", err)
   368  	} else {
   369  
   370  		if err != nil {
   371  			utils.Fatalf(err.Error())
   372  		}
   373  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   374  
   375  		// Generate domain specific keys
   376  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   377  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   378  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   379  
   380  		// Initialize the encrypted storages
   381  		pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   382  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   383  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   384  
   385  		//Do we have a rule-file?
   386  		ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name))
   387  		if err != nil {
   388  			log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
   389  		} else {
   390  			hasher := sha256.New()
   391  			hasher.Write(ruleJS)
   392  			shasum := hasher.Sum(nil)
   393  			storedShasum := configStorage.Get("ruleset_sha256")
   394  			if storedShasum != hex.EncodeToString(shasum) {
   395  				log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum)
   396  			} else {
   397  				// Initialize rules
   398  				ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage)
   399  				if err != nil {
   400  					utils.Fatalf(err.Error())
   401  				}
   402  				ruleEngine.Init(string(ruleJS))
   403  				ui = ruleEngine
   404  				log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   405  			}
   406  		}
   407  	}
   408  
   409  	apiImpl := core.NewSignerAPI(
   410  		c.GlobalInt64(utils.NetworkIdFlag.Name),
   411  		c.GlobalString(keystoreFlag.Name),
   412  		c.GlobalBool(utils.NoUSBFlag.Name),
   413  		ui, db,
   414  		c.GlobalBool(utils.LightKDFFlag.Name),
   415  		c.GlobalBool(advancedMode.Name))
   416  	api = apiImpl
   417  	// Audit logging
   418  	if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" {
   419  		api, err = core.NewAuditLogger(logfile, api)
   420  		if err != nil {
   421  			utils.Fatalf(err.Error())
   422  		}
   423  		log.Info("Audit logs configured", "file", logfile)
   424  	}
   425  	// register signer API with server
   426  	var (
   427  		extapiURL = "n/a"
   428  		ipcapiURL = "n/a"
   429  	)
   430  	rpcAPI := []rpc.API{
   431  		{
   432  			Namespace: "account",
   433  			Public:    true,
   434  			Service:   api,
   435  			Version:   "1.0"},
   436  	}
   437  	if c.GlobalBool(utils.RPCEnabledFlag.Name) {
   438  
   439  		vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
   440  		cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
   441  
   442  		// start http server
   443  		httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   444  		listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
   445  		if err != nil {
   446  			utils.Fatalf("Could not start RPC api: %v", err)
   447  		}
   448  		extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
   449  		log.Info("HTTP endpoint opened", "url", extapiURL)
   450  
   451  		defer func() {
   452  			listener.Close()
   453  			log.Info("HTTP endpoint closed", "url", httpEndpoint)
   454  		}()
   455  
   456  	}
   457  	if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
   458  		if c.IsSet(utils.IPCPathFlag.Name) {
   459  			ipcapiURL = c.GlobalString(utils.IPCPathFlag.Name)
   460  		} else {
   461  			ipcapiURL = filepath.Join(configDir, "clef.ipc")
   462  		}
   463  
   464  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   465  		if err != nil {
   466  			utils.Fatalf("Could not start IPC api: %v", err)
   467  		}
   468  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   469  		defer func() {
   470  			listener.Close()
   471  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   472  		}()
   473  
   474  	}
   475  
   476  	if c.GlobalBool(testFlag.Name) {
   477  		log.Info("Performing UI test")
   478  		go testExternalUI(apiImpl)
   479  	}
   480  	ui.OnSignerStartup(core.StartupInfo{
   481  		Info: map[string]interface{}{
   482  			"extapi_version": ExternalAPIVersion,
   483  			"intapi_version": InternalAPIVersion,
   484  			"extapi_http":    extapiURL,
   485  			"extapi_ipc":     ipcapiURL,
   486  		},
   487  	})
   488  
   489  	abortChan := make(chan os.Signal)
   490  	signal.Notify(abortChan, os.Interrupt)
   491  
   492  	sig := <-abortChan
   493  	log.Info("Exiting...", "signal", sig)
   494  
   495  	return nil
   496  }
   497  
   498  // splitAndTrim splits input separated by a comma
   499  // and trims excessive white space from the substrings.
   500  func splitAndTrim(input string) []string {
   501  	result := strings.Split(input, ",")
   502  	for i, r := range result {
   503  		result[i] = strings.TrimSpace(r)
   504  	}
   505  	return result
   506  }
   507  
   508  // DefaultConfigDir is the default config directory to use for the vaults and other
   509  // persistence requirements.
   510  func DefaultConfigDir() string {
   511  	// Try to place the data folder in the user's home dir
   512  	home := homeDir()
   513  	if home != "" {
   514  		if runtime.GOOS == "darwin" {
   515  			return filepath.Join(home, "Library", "Signer")
   516  		} else if runtime.GOOS == "windows" {
   517  			return filepath.Join(home, "AppData", "Roaming", "Signer")
   518  		} else {
   519  			return filepath.Join(home, ".clef")
   520  		}
   521  	}
   522  	// As we cannot guess a stable location, return empty and handle later
   523  	return ""
   524  }
   525  
   526  func homeDir() string {
   527  	if home := os.Getenv("HOME"); home != "" {
   528  		return home
   529  	}
   530  	if usr, err := user.Current(); err == nil {
   531  		return usr.HomeDir
   532  	}
   533  	return ""
   534  }
   535  func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
   536  	var (
   537  		file      string
   538  		configDir = ctx.GlobalString(configdirFlag.Name)
   539  	)
   540  	if ctx.GlobalIsSet(signerSecretFlag.Name) {
   541  		file = ctx.GlobalString(signerSecretFlag.Name)
   542  	} else {
   543  		file = filepath.Join(configDir, "masterseed.json")
   544  	}
   545  	if err := checkFile(file); err != nil {
   546  		return nil, err
   547  	}
   548  	cipherKey, err := ioutil.ReadFile(file)
   549  	if err != nil {
   550  		return nil, err
   551  	}
   552  	var password string
   553  	// If ui is not nil, get the password from ui.
   554  	if ui != nil {
   555  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   556  			Title:      "Master Password",
   557  			Prompt:     "Please enter the password to decrypt the master seed",
   558  			IsPassword: true})
   559  		if err != nil {
   560  			return nil, err
   561  		}
   562  		password = resp.Text
   563  	} else {
   564  		password = getPassPhrase("Decrypt master seed of clef", false)
   565  	}
   566  	masterSeed, err := decryptSeed(cipherKey, password)
   567  	if err != nil {
   568  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   569  	}
   570  	if len(masterSeed) < 256 {
   571  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   572  	}
   573  
   574  	// Create vault location
   575  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   576  	err = os.Mkdir(vaultLocation, 0700)
   577  	if err != nil && !os.IsExist(err) {
   578  		return nil, err
   579  	}
   580  	return masterSeed, nil
   581  }
   582  
   583  // checkFile is a convenience function to check if a file
   584  // * exists
   585  // * is mode 0400
   586  func checkFile(filename string) error {
   587  	info, err := os.Stat(filename)
   588  	if err != nil {
   589  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   590  	}
   591  	// Check the unix permission bits
   592  	if info.Mode().Perm()&0377 != 0 {
   593  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   594  	}
   595  	return nil
   596  }
   597  
   598  // confirm displays a text and asks for user confirmation
   599  func confirm(text string) bool {
   600  	fmt.Printf(text)
   601  	fmt.Printf("\nEnter 'ok' to proceed:\n>")
   602  
   603  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   604  	if err != nil {
   605  		log.Crit("Failed to read user input", "err", err)
   606  	}
   607  
   608  	if text := strings.TrimSpace(text); text == "ok" {
   609  		return true
   610  	}
   611  	return false
   612  }
   613  
   614  func testExternalUI(api *core.SignerAPI) {
   615  
   616  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   617  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   618  	ctx = context.WithValue(ctx, "local", "main")
   619  
   620  	errs := make([]string, 0)
   621  
   622  	api.UI.ShowInfo("Testing 'ShowInfo'")
   623  	api.UI.ShowError("Testing 'ShowError'")
   624  
   625  	checkErr := func(method string, err error) {
   626  		if err != nil && err != core.ErrRequestDenied {
   627  			errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error()))
   628  		}
   629  	}
   630  	var err error
   631  
   632  	_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
   633  	checkErr("SignTransaction", err)
   634  	_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
   635  	checkErr("Sign", err)
   636  	_, err = api.List(ctx)
   637  	checkErr("List", err)
   638  	_, err = api.New(ctx)
   639  	checkErr("New", err)
   640  	_, err = api.Export(ctx, common.Address{})
   641  	checkErr("Export", err)
   642  	_, err = api.Import(ctx, json.RawMessage{})
   643  	checkErr("Import", err)
   644  
   645  	api.UI.ShowInfo("Tests completed")
   646  
   647  	if len(errs) > 0 {
   648  		log.Error("Got errors")
   649  		for _, e := range errs {
   650  			log.Error(e)
   651  		}
   652  	} else {
   653  		log.Info("No errors")
   654  	}
   655  
   656  }
   657  
   658  // getPassPhrase retrieves the password associated with clef, either fetched
   659  // from a list of preloaded passphrases, or requested interactively from the user.
   660  // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
   661  func getPassPhrase(prompt string, confirmation bool) string {
   662  	fmt.Println(prompt)
   663  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   664  	if err != nil {
   665  		utils.Fatalf("Failed to read passphrase: %v", err)
   666  	}
   667  	if confirmation {
   668  		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
   669  		if err != nil {
   670  			utils.Fatalf("Failed to read passphrase confirmation: %v", err)
   671  		}
   672  		if password != confirm {
   673  			utils.Fatalf("Passphrases do not match")
   674  		}
   675  	}
   676  	return password
   677  }
   678  
   679  type encryptedSeedStorage struct {
   680  	Description string              `json:"description"`
   681  	Version     int                 `json:"version"`
   682  	Params      keystore.CryptoJSON `json:"params"`
   683  }
   684  
   685  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   686  // to encrypt the master seed
   687  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   688  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   689  	if err != nil {
   690  		return nil, err
   691  	}
   692  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   693  }
   694  
   695  // decryptSeed decrypts the master seed
   696  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
   697  	var encSeed encryptedSeedStorage
   698  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
   699  		return nil, err
   700  	}
   701  	if encSeed.Version != 1 {
   702  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
   703  	}
   704  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
   705  	if err != nil {
   706  		return nil, err
   707  	}
   708  	return seed, err
   709  }
   710  
   711  /**
   712  //Create Account
   713  
   714  curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
   715  
   716  // List accounts
   717  
   718  curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
   719  
   720  // Make Transaction
   721  // safeSend(0x12)
   722  // 4401a6e40000000000000000000000000000000000000000000000000000000000000012
   723  
   724  // supplied abi
   725  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/
   726  
   727  // Not supplied
   728  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/
   729  
   730  // Sign data
   731  
   732  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/
   733  
   734  
   735  **/