aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/clef/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/clef/main.go')
-rw-r--r--cmd/clef/main.go166
1 files changed, 95 insertions, 71 deletions
diff --git a/cmd/clef/main.go b/cmd/clef/main.go
index 0ea6f36f1..f4d94f027 100644
--- a/cmd/clef/main.go
+++ b/cmd/clef/main.go
@@ -93,7 +93,7 @@ var (
chainIdFlag = cli.Int64Flag{
Name: "chainid",
Value: params.MainnetChainConfig.ChainID.Int64(),
- Usage: "Chain id to use for signing (1=mainnet, 3=ropsten, 4=rinkeby, 5=Goerli)",
+ Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)",
}
rpcPortFlag = cli.IntFlag{
Name: "rpcport",
@@ -116,8 +116,7 @@ var (
}
ruleFlag = cli.StringFlag{
Name: "rules",
- Usage: "Enable rule-engine",
- Value: "",
+ Usage: "Path to the rule file to auto-authorize requests with",
}
stdiouiFlag = cli.BoolFlag{
Name: "stdio-ui",
@@ -160,7 +159,6 @@ incoming requests.
Whenever you make an edit to the rule file, you need to use attestation to tell
Clef that the file is 'safe' to execute.`,
}
-
setCredentialCommand = cli.Command{
Action: utils.MigrateFlags(setCredential),
Name: "setpw",
@@ -172,8 +170,20 @@ Clef that the file is 'safe' to execute.`,
signerSecretFlag,
},
Description: `
-The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will
-remove any stored credential for that address (keyfile)
+The setpw command stores a password for a given address (keyfile).
+`}
+ delCredentialCommand = cli.Command{
+ Action: utils.MigrateFlags(removeCredential),
+ Name: "delpw",
+ Usage: "Remove a credential for a keystore file",
+ ArgsUsage: "<address>",
+ Flags: []cli.Flag{
+ logLevelFlag,
+ configdirFlag,
+ signerSecretFlag,
+ },
+ Description: `
+The delpw command removes a password for a given address (keyfile).
`}
gendocCommand = cli.Command{
Action: GenDoc,
@@ -210,9 +220,9 @@ func init() {
advancedMode,
}
app.Action = signer
- app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, gendocCommand}
-
+ app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, delCredentialCommand, gendocCommand}
}
+
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -221,11 +231,20 @@ func main() {
}
func initializeSecrets(c *cli.Context) error {
+ // Get past the legal message
if err := initialize(c); err != nil {
return err
}
+ // Ensure the master key does not yet exist, we're not willing to overwrite
configDir := c.GlobalString(configdirFlag.Name)
-
+ if err := os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) {
+ return err
+ }
+ location := filepath.Join(configDir, "masterseed.json")
+ if _, err := os.Stat(location); err == nil {
+ return fmt.Errorf("master key %v already exists, will not overwrite", location)
+ }
+ // Key file does not exist yet, generate a new one and encrypt it
masterSeed := make([]byte, 256)
num, err := io.ReadFull(rand.Reader, masterSeed)
if err != nil {
@@ -234,18 +253,18 @@ func initializeSecrets(c *cli.Context) error {
if num != len(masterSeed) {
return fmt.Errorf("failed to read enough random")
}
-
n, p := keystore.StandardScryptN, keystore.StandardScryptP
if c.GlobalBool(utils.LightKDFFlag.Name) {
n, p = keystore.LightScryptN, keystore.LightScryptP
}
- text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password."
+ text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!"
var password string
for {
password = getPassPhrase(text, true)
if err := core.ValidatePasswordFormat(password); err != nil {
fmt.Printf("invalid password: %v\n", err)
} else {
+ fmt.Println()
break
}
}
@@ -253,28 +272,27 @@ func initializeSecrets(c *cli.Context) error {
if err != nil {
return fmt.Errorf("failed to encrypt master seed: %v", err)
}
-
- err = os.Mkdir(configDir, 0700)
- if err != nil && !os.IsExist(err) {
+ // Double check the master key path to ensure nothing wrote there in between
+ if err = os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) {
return err
}
- location := filepath.Join(configDir, "masterseed.json")
if _, err := os.Stat(location); err == nil {
- return fmt.Errorf("file %v already exists, will not overwrite", location)
+ return fmt.Errorf("master key %v already exists, will not overwrite", location)
}
- err = ioutil.WriteFile(location, cipherSeed, 0400)
- if err != nil {
+ // Write the file and print the usual warning message
+ if err = ioutil.WriteFile(location, cipherSeed, 0400); err != nil {
return err
}
fmt.Printf("A master seed has been generated into %s\n", location)
fmt.Printf(`
-This is required to be able to store credentials, such as :
+This is required to be able to store credentials, such as:
* Passwords for keystores (used by rule engine)
-* Storage for javascript rules
-* Hash of rule-file
+* Storage for JavaScript auto-signing rules
+* Hash of JavaScript rule-file
-You should treat that file with utmost secrecy, and make a backup of it.
-NOTE: This file does not contain your accounts. Those need to be backed up separately!
+You should treat 'masterseed.json' with utmost secrecy and make a backup of it!
+* The password is necessary but not enough, you need to back up the master seed too!
+* The master seed does not contain your accounts, those need to be backed up separately!
`)
return nil
@@ -305,14 +323,46 @@ func attestFile(ctx *cli.Context) error {
func setCredential(ctx *cli.Context) error {
if len(ctx.Args()) < 1 {
- utils.Fatalf("This command requires an address to be passed as an argument.")
+ utils.Fatalf("This command requires an address to be passed as an argument")
}
if err := initialize(ctx); err != nil {
return err
}
+ addr := ctx.Args().First()
+ if !common.IsHexAddress(addr) {
+ utils.Fatalf("Invalid address specified: %s", addr)
+ }
+ address := common.HexToAddress(addr)
+ password := getPassPhrase("Please enter a passphrase to store for this address:", true)
+ fmt.Println()
+
+ stretchedKey, err := readMasterKey(ctx, nil)
+ if err != nil {
+ utils.Fatalf(err.Error())
+ }
+ configDir := ctx.GlobalString(configdirFlag.Name)
+ vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
+ pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
- address := ctx.Args().First()
- password := getPassPhrase("Enter a passphrase to store with this address.", true)
+ pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
+ pwStorage.Put(address.Hex(), password)
+
+ log.Info("Credential store updated", "set", address)
+ return nil
+}
+
+func removeCredential(ctx *cli.Context) error {
+ if len(ctx.Args()) < 1 {
+ utils.Fatalf("This command requires an address to be passed as an argument")
+ }
+ if err := initialize(ctx); err != nil {
+ return err
+ }
+ addr := ctx.Args().First()
+ if !common.IsHexAddress(addr) {
+ utils.Fatalf("Invalid address specified: %s", addr)
+ }
+ address := common.HexToAddress(addr)
stretchedKey, err := readMasterKey(ctx, nil)
if err != nil {
@@ -322,10 +372,10 @@ func setCredential(ctx *cli.Context) error {
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
- // Initialize the encrypted storages
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
- pwStorage.Put(address, password)
- log.Info("Credential store updated", "key", address)
+ pwStorage.Del(address.Hex())
+
+ log.Info("Credential store updated", "unset", address)
return nil
}
@@ -340,13 +390,17 @@ func initialize(c *cli.Context) error {
if !confirm(legalWarning) {
return fmt.Errorf("aborted by user")
}
+ fmt.Println()
}
-
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
return nil
}
func signer(c *cli.Context) error {
+ // If we have some unrecognized command, bail out
+ if args := c.Args(); len(args) > 0 {
+ return fmt.Errorf("invalid command: %q", args[0])
+ }
if err := initialize(c); err != nil {
return err
}
@@ -376,7 +430,7 @@ func signer(c *cli.Context) error {
configDir := c.GlobalString(configdirFlag.Name)
if stretchedKey, err := readMasterKey(c, ui); err != nil {
- log.Info("No master seed provided, rules disabled", "error", err)
+ log.Warn("Failed to open master, rules disabled", "err", err)
} else {
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
@@ -390,17 +444,17 @@ func signer(c *cli.Context) error {
jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
- //Do we have a rule-file?
+ // Do we have a rule-file?
if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
- ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFile))
+ ruleJS, err := ioutil.ReadFile(ruleFile)
if err != nil {
- log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
+ log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err)
} else {
shasum := sha256.Sum256(ruleJS)
foundShaSum := hex.EncodeToString(shasum[:])
- storedShasum := configStorage.Get("ruleset_sha256")
+ storedShasum, _ := configStorage.Get("ruleset_sha256")
if storedShasum != foundShaSum {
- log.Info("Could not validate ruleset hash, rules not enabled", "got", foundShaSum, "expected", storedShasum)
+ log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum)
} else {
// Initialize rules
ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
@@ -452,7 +506,6 @@ func signer(c *cli.Context) error {
Version: "1.0"},
}
if c.GlobalBool(utils.RPCEnabledFlag.Name) {
-
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
@@ -469,7 +522,6 @@ func signer(c *cli.Context) error {
listener.Close()
log.Info("HTTP endpoint closed", "url", httpEndpoint)
}()
-
}
if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
if c.IsSet(utils.IPCPathFlag.Name) {
@@ -496,8 +548,8 @@ func signer(c *cli.Context) error {
}
ui.OnSignerStartup(core.StartupInfo{
Info: map[string]interface{}{
- "extapi_version": core.ExternalAPIVersion,
"intapi_version": core.InternalAPIVersion,
+ "extapi_version": core.ExternalAPIVersion,
"extapi_http": extapiURL,
"extapi_ipc": ipcapiURL,
},
@@ -592,7 +644,6 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
if len(masterSeed) < 256 {
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
}
-
// Create vault location
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
err = os.Mkdir(vaultLocation, 0700)
@@ -620,13 +671,12 @@ func checkFile(filename string) error {
// confirm displays a text and asks for user confirmation
func confirm(text string) bool {
fmt.Printf(text)
- fmt.Printf("\nEnter 'ok' to proceed:\n>")
+ fmt.Printf("\nEnter 'ok' to proceed:\n> ")
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
log.Crit("Failed to read user input", "err", err)
}
-
if text := strings.TrimSpace(text); text == "ok" {
return true
}
@@ -642,7 +692,7 @@ func testExternalUI(api *core.SignerAPI) {
a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
addErr := func(errStr string) {
- log.Info("Test error", "error", errStr)
+ log.Info("Test error", "err", errStr)
errs = append(errs, errStr)
}
@@ -864,14 +914,14 @@ func GenDoc(ctx *cli.Context) {
"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
"the user with the contents of the `message`"
sighash, msg := accounts.TextAndHash([]byte("hello world"))
- message := []*core.NameValueType{{"message", msg, accounts.MimetypeTextPlain}}
+ messages := []*core.NameValueType{{"message", msg, accounts.MimetypeTextPlain}}
add("SignDataRequest", desc, &core.SignDataRequest{
Address: common.NewMixedcaseAddress(a),
Meta: meta,
ContentType: accounts.MimetypeTextPlain,
Rawdata: []byte(msg),
- Message: message,
+ Messages: messages,
Hash: sighash})
}
{ // Sign plain text response
@@ -982,29 +1032,3 @@ These data types are defined in the channel between clef and the UI`)
fmt.Println(elem)
}
}
-
-/**
-//Create Account
-
-curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
-
-// List accounts
-
-curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
-
-// Make Transaction
-// safeSend(0x12)
-// 4401a6e40000000000000000000000000000000000000000000000000000000000000012
-
-// supplied abi
-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/
-
-// Not supplied
-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/
-
-// Sign data
-
-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/
-
-
-**/