diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 6c440551a1..f4d94f0274 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", @@ -173,6 +171,19 @@ Clef that the file is 'safe' to execute.`, }, Description: ` 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: "
", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The delpw command removes a password for a given address (keyfile). `} gendocCommand = cli.Command{ Action: GenDoc, @@ -209,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) @@ -317,7 +328,6 @@ func setCredential(ctx *cli.Context) error { if err := initialize(ctx); err != nil { return err } - addr := ctx.Args().First() if !common.IsHexAddress(addr) { utils.Fatalf("Invalid address specified: %s", addr) @@ -334,10 +344,38 @@ 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.Hex(), password) - log.Info("Credential store updated", "key", address) + + 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 { + 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) + + pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + pwStorage.Del(address.Hex()) + + log.Info("Credential store updated", "unset", address) return nil } @@ -359,6 +397,10 @@ func initialize(c *cli.Context) error { } 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 } diff --git a/signer/rules/rules.go b/signer/rules/rules.go index cafca9ec24..f731dac40a 100644 --- a/signer/rules/rules.go +++ b/signer/rules/rules.go @@ -83,7 +83,12 @@ func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (otto.Value, error vm.Set("storage", struct{}{}) storageObj, _ := vm.Get("storage") storageObj.Object().Set("put", func(call otto.FunctionCall) otto.Value { - r.storage.Put(call.Argument(0).String(), call.Argument(1).String()) + key, val := call.Argument(0).String(), call.Argument(1).String() + if val == "" { + r.storage.Del(key) + } else { + r.storage.Put(key, val) + } return otto.NullValue() }) storageObj.Object().Set("get", func(call otto.FunctionCall) otto.Value { diff --git a/signer/storage/aes_gcm_storage.go b/signer/storage/aes_gcm_storage.go index d10d280c09..e6a8f145cc 100644 --- a/signer/storage/aes_gcm_storage.go +++ b/signer/storage/aes_gcm_storage.go @@ -53,7 +53,7 @@ func NewAESEncryptedStorage(filename string, key []byte) *AESEncryptedStorage { } } -// Put stores a value by key. 0-length keys results in no-op +// Put stores a value by key. 0-length keys results in noop. func (s *AESEncryptedStorage) Put(key, value string) { if len(key) == 0 { return @@ -99,6 +99,19 @@ func (s *AESEncryptedStorage) Get(key string) (string, error) { return string(entry), nil } +// Del removes a key-value pair. If the key doesn't exist, the method is a noop. +func (s *AESEncryptedStorage) Del(key string) { + data, err := s.readEncryptedStorage() + if err != nil { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + return + } + delete(data, key) + if err = s.writeEncryptedStorage(data); err != nil { + log.Warn("Failed to write entry", "err", err) + } +} + // readEncryptedStorage reads the file with encrypted creds func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) { creds := make(map[string]storedCredential) diff --git a/signer/storage/storage.go b/signer/storage/storage.go index c94fba3178..c1f593d960 100644 --- a/signer/storage/storage.go +++ b/signer/storage/storage.go @@ -28,12 +28,15 @@ var ( ) type Storage interface { - // Put stores a value by key. 0-length keys results in no-op + // Put stores a value by key. 0-length keys results in noop. Put(key, value string) // Get returns the previously stored value, or an error if the key is 0-length // or unknown. Get(key string) (string, error) + + // Del removes a key-value pair. If the key doesn't exist, the method is a noop. + Del(key string) } // EphemeralStorage is an in-memory storage that does @@ -43,6 +46,7 @@ type EphemeralStorage struct { namespace string } +// Put stores a value by key. 0-length keys results in noop. func (s *EphemeralStorage) Put(key, value string) { if len(key) == 0 { return @@ -62,6 +66,11 @@ func (s *EphemeralStorage) Get(key string) (string, error) { return "", ErrNotFound } +// Del removes a key-value pair. If the key doesn't exist, the method is a noop. +func (s *EphemeralStorage) Del(key string) { + delete(s.data, key) +} + func NewEphemeralStorage() Storage { s := &EphemeralStorage{ data: make(map[string]string), @@ -73,6 +82,7 @@ func NewEphemeralStorage() Storage { type NoStorage struct{} func (s *NoStorage) Put(key, value string) {} +func (s *NoStorage) Del(key string) {} func (s *NoStorage) Get(key string) (string, error) { return "", errors.New("I forgot") }