diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md index 38424f06b9..f7e6993cf5 100644 --- a/cmd/clef/intapi_changelog.md +++ b/cmd/clef/intapi_changelog.md @@ -10,6 +10,17 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +### 7.0.1 + +Added `clef_New` to the internal API calleable from a UI. + +> `New` creates a new password protected Account. The private key is protected with +> the given password. Users are responsible to backup the private key that is stored +> in the keystore location that was specified when this API was created. +> This method is the same as New on the external API, the difference being that +> this implementation does not ask for confirmation, since it's initiated by +> the user + ### 7.0.0 - The `message` field was renamed to `messages` in all data signing request methods to better reflect that it's a list, not a value. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index dd898871d5..f4533a6e85 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -187,6 +187,21 @@ The setpw command stores a password for a given address (keyfile). Description: ` The delpw command removes a password for a given address (keyfile). `} + newAccountCommand = cli.Command{ + Action: utils.MigrateFlags(newAccount), + Name: "newaccount", + Usage: "Create a new account", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + }, + Description: ` +The newaccount command creates a new keystore-backed account. It is a convenience-method +which can be used in lieu of an external UI.`, + } + gendocCommand = cli.Command{ Action: GenDoc, Name: "gendoc", @@ -222,7 +237,12 @@ func init() { advancedMode, } app.Action = signer - app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, delCredentialCommand, gendocCommand} + app.Commands = []cli.Command{initCommand, + attestCommand, + setCredentialCommand, + delCredentialCommand, + newAccountCommand, + gendocCommand} cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate } @@ -382,6 +402,31 @@ func removeCredential(ctx *cli.Context) error { return nil } +func newAccount(c *cli.Context) error { + if err := initialize(c); err != nil { + return err + } + // The newaccount is meant for users using the CLI, since 'real' external + // UIs can use the UI-api instead. So we'll just use the native CLI UI here. + var ( + ui = core.NewCommandlineUI() + pwStorage storage.Storage = &storage.NoStorage{} + ksLoc = c.GlobalString(keystoreFlag.Name) + lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) + ) + log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf) + am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") + // This gives is us access to the external API + apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) + // This gives us access to the internal API + internalApi := core.NewUIServerAPI(apiImpl) + addr, err := internalApi.New(context.Background()) + if err == nil { + fmt.Printf("Generated account %v\n", addr.String()) + } + return err +} + func initialize(c *cli.Context) error { // Set up the logger to print everything logOutput := os.Stdout @@ -457,7 +502,6 @@ func signer(c *cli.Context) error { api core.ExternalAPI pwStorage storage.Storage = &storage.NoStorage{} ) - configDir := c.GlobalString(configdirFlag.Name) if stretchedKey, err := readMasterKey(c, ui); err != nil { log.Warn("Failed to open master, rules disabled", "err", err) diff --git a/signer/core/api.go b/signer/core/api.go index c10bf578d2..7e6ece997f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -43,7 +43,7 @@ const ( // ExternalAPIVersion -- see extapi_changelog.md ExternalAPIVersion = "6.0.0" // InternalAPIVersion -- see intapi_changelog.md - InternalAPIVersion = "7.0.0" + InternalAPIVersion = "7.0.1" ) // ExternalAPI defines the external API through which signing requests are made. @@ -395,8 +395,7 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { // the given password. Users are responsible to backup the private key that is stored // in the keystore location thas was specified when this API was created. func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { - be := api.am.Backends(keystore.KeyStoreType) - if len(be) == 0 { + if be := api.am.Backends(keystore.KeyStoreType); len(be) == 0 { return common.Address{}, errors.New("password based accounts not supported") } if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { @@ -404,7 +403,16 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { } else if !resp.Approved { return common.Address{}, ErrRequestDenied } + return api.newAccount() +} +// newAccount is the internal method to create a new account. It should be used +// _after_ user-approval has been obtained +func (api *SignerAPI) newAccount() (common.Address, error) { + be := api.am.Backends(keystore.KeyStoreType) + if len(be) == 0 { + return common.Address{}, errors.New("password based accounts not supported") + } // Three retries to get a valid password for i := 0; i < 3; i++ { resp, err := api.UI.OnInputRequired(UserInputRequest{ diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go index 36735d37ee..25a587de56 100644 --- a/signer/core/uiapi.go +++ b/signer/core/uiapi.go @@ -195,6 +195,16 @@ func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, old return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase) } +// New creates a new password protected Account. The private key is protected with +// the given password. Users are responsible to backup the private key that is stored +// in the keystore location that was specified when this API was created. +// This method is the same as New on the external API, the difference being that +// this implementation does not ask for confirmation, since it's initiated by +// the user +func (api *UIServerAPI) New(ctx context.Context) (common.Address, error) { + return api.extApi.newAccount() +} + // Other methods to be added, not yet implemented are: // - Ruleset interaction: add rules, attest rulefiles // - Store metadata about accounts, e.g. naming of accounts