package main import ( "encoding/hex" "encoding/json" "errors" "fmt" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/plugin" "gopkg.in/urfave/cli.v1" ) var ( quorumAccountPluginCommands = cli.Command{ Name: "plugin", Usage: "Manage 'account' plugin accounts", Description: ` geth account plugin Quorum supports alternate account management methods through the use of 'account' plugins. See docs.goquorum.com for more info. `, Subcommands: []cli.Command{ { Name: "list", Usage: "Print summary of existing 'account' plugin accounts", Action: utils.MigrateFlags(listPluginAccountsCLIAction), Flags: []cli.Flag{ utils.PluginSettingsFlag, // flag is used implicitly by makeConfigNode() utils.PluginLocalVerifyFlag, utils.PluginPublicKeyFlag, utils.PluginSkipVerifyFlag, }, Description: ` geth account plugin list Print a short summary of all accounts for the given plugin settings`, }, { Name: "new", Usage: "Create a new account using an 'account' plugin", Action: utils.MigrateFlags(createPluginAccountCLIAction), Flags: []cli.Flag{ utils.PluginSettingsFlag, utils.PluginLocalVerifyFlag, utils.PluginPublicKeyFlag, utils.PluginSkipVerifyFlag, utils.AccountPluginNewAccountConfigFlag, }, Description: fmt.Sprintf(` geth account plugin new Creates a new account using an 'account' plugin and prints the address. --%v and --%v flags are required. Each 'account' plugin will have different requirements for the value of --%v. For more info see the documentation for the particular 'account' plugin being used. `, utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name), }, { Name: "import", Usage: "Import a private key into a new account using an 'account' plugin", Action: utils.MigrateFlags(importPluginAccountCLIAction), Flags: []cli.Flag{ utils.PluginSettingsFlag, utils.PluginLocalVerifyFlag, utils.PluginPublicKeyFlag, utils.PluginSkipVerifyFlag, utils.AccountPluginNewAccountConfigFlag, }, ArgsUsage: "", Description: ` geth account plugin import Imports an unencrypted private key from and creates a new account using an 'account' plugin. Prints the address. The keyfile must contain an unencrypted private key in hexadecimal format. --%v and --%v flags are required. Note: Before using this import mechanism to transfer accounts that are already 'account' plugin-managed between nodes, consult the documentation for the particular 'account' plugin being used as it may support alternate methods for transferring. `, }, }, } // supportedPlugins is the list of supported plugins for the account subcommand supportedPlugins = []plugin.PluginInterfaceName{plugin.AccountPluginInterfaceName} invalidPluginFlagsErr = fmt.Errorf("--%v and --%v flags must be set", utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name) // makeConfigNodeDelegate is a wrapper for the makeConfigNode function. // It can be replaced with a stub for testing. makeConfigNodeDelegate configNodeMaker = standardConfigNodeMaker{} ) func listPluginAccountsCLIAction(ctx *cli.Context) error { accts, err := listPluginAccounts(ctx) if err != nil { utils.Fatalf("%v", err) } var index int for _, acct := range accts { fmt.Printf("Account #%d: {%x} %s\n", index, acct.Address, &acct.URL) index++ } return nil } func listPluginAccounts(ctx *cli.Context) ([]accounts.Account, error) { if !ctx.IsSet(utils.PluginSettingsFlag.Name) { return []accounts.Account{}, fmt.Errorf("--%v required", utils.PluginSettingsFlag.Name) } p, err := setupAccountPluginForCLI(ctx) if err != nil { return []accounts.Account{}, err } defer func() { if err := p.teardown(); err != nil { log.Error("error tearing down account plugin", "err", err) } }() return p.accounts(), nil } func createPluginAccountCLIAction(ctx *cli.Context) error { account, err := createPluginAccount(ctx) if err != nil { utils.Fatalf("unable to create plugin-backed account: %v", err) } writePluginAccountToStdOut(account) return nil } func createPluginAccount(ctx *cli.Context) (accounts.Account, error) { if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { return accounts.Account{}, invalidPluginFlagsErr } newAcctCfg, err := getNewAccountConfigFromCLI(ctx) if err != nil { return accounts.Account{}, err } p, err := setupAccountPluginForCLI(ctx) if err != nil { return accounts.Account{}, err } defer func() { if err := p.teardown(); err != nil { log.Error("error tearing down account plugin", "err", err) } }() return p.NewAccount(newAcctCfg) } func importPluginAccountCLIAction(ctx *cli.Context) error { account, err := importPluginAccount(ctx) if err != nil { utils.Fatalf("unable to import key and create plugin-backed account: %v", err) } writePluginAccountToStdOut(account) return nil } func importPluginAccount(ctx *cli.Context) (accounts.Account, error) { keyfile := ctx.Args().First() if len(keyfile) == 0 { return accounts.Account{}, errors.New("keyfile must be given as argument") } key, err := crypto.LoadECDSA(keyfile) if err != nil { return accounts.Account{}, fmt.Errorf("Failed to load the private key: %v", err) } keyBytes := crypto.FromECDSA(key) keyHex := hex.EncodeToString(keyBytes) if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { return accounts.Account{}, invalidPluginFlagsErr } newAcctCfg, err := getNewAccountConfigFromCLI(ctx) if err != nil { return accounts.Account{}, err } p, err := setupAccountPluginForCLI(ctx) if err != nil { return accounts.Account{}, err } defer func() { if err := p.teardown(); err != nil { log.Error("error tearing down account plugin", "err", err) } }() return p.ImportRawKey(keyHex, newAcctCfg) } func getNewAccountConfigFromCLI(ctx *cli.Context) (map[string]interface{}, error) { data := ctx.String(utils.AccountPluginNewAccountConfigFlag.Name) conf, err := plugin.ReadMultiFormatConfig(data) if err != nil { return nil, fmt.Errorf("invalid account creation config provided: %v", err) } // plugin backend expects config to be json map confMap := new(map[string]interface{}) if err := json.Unmarshal(conf, confMap); err != nil { return nil, fmt.Errorf("invalid account creation config provided: %v", err) } return *confMap, nil } type accountPlugin struct { pluggable.AccountCreator am *accounts.Manager pm *plugin.PluginManager } func (c *accountPlugin) teardown() error { return c.pm.Stop() } func (c *accountPlugin) accounts() []accounts.Account { b := c.am.Backends(pluggable.BackendType) if b == nil { return []accounts.Account{} } var accts []accounts.Account for _, wallet := range b[0].Wallets() { accts = append(accts, wallet.Accounts()...) } return accts } // startPluginManagerForAccountCLI is a helper func for use with the account plugin CLI. // It creates and starts a new PluginManager with the provided CLI flags. // The caller should call teardown on the returned accountPlugin to stop the plugin after use. // The returned accountPlugin provides several methods necessary for the account plugin CLI, abstracting the underlying plugin/account types. // // This func should not be used for anything other than the account CLI. // The account plugin, if present, is registered with the existing pluggable.Backend in the stack's AccountManager. // This allows the AccountManager to use the account plugin even though the PluginManager is not registered with the stack. // Instead of registering a plugin manager with the stack this is manually creating a plugin manager. // This means that the plugin manager can be started without having to start the whole stack (P2P client, IPC interface, ...). // The purpose of this is to help prevent issues/conflicts if an existing node is already running on this host. // func setupAccountPluginForCLI(ctx *cli.Context) (*accountPlugin, error) { stack, cfg := makeConfigNodeDelegate.makeConfigNode(ctx) if cfg.Node.Plugins == nil { return nil, errors.New("no plugin config provided") } if err := cfg.Node.Plugins.CheckSettingsAreSupported(supportedPlugins); err != nil { return nil, err } if err := cfg.Node.ResolvePluginBaseDir(); err != nil { return nil, fmt.Errorf("unable to resolve plugin base dir due to %s", err) } pm, err := plugin.NewPluginManager( cfg.Node.UserIdent, cfg.Node.Plugins, ctx.Bool(utils.PluginSkipVerifyFlag.Name), ctx.Bool(utils.PluginLocalVerifyFlag.Name), ctx.String(utils.PluginPublicKeyFlag.Name), ) if err != nil { return nil, fmt.Errorf("unable to create plugin manager: %v", err) } if err := pm.Start(); err != nil { return nil, fmt.Errorf("unable to start plugin manager: %v", err) } b := stack.AccountManager().Backends(pluggable.BackendType)[0].(*pluggable.Backend) if err := pm.AddAccountPluginToBackend(b); err != nil { return nil, fmt.Errorf("unable to load pluggable account backend: %v", err) } return &accountPlugin{ AccountCreator: b, am: stack.AccountManager(), pm: pm, }, nil } func writePluginAccountToStdOut(account accounts.Account) { fmt.Printf("\nYour new plugin-backed account was generated\n\n") fmt.Printf("Public address of the account: %s\n", account.Address.Hex()) fmt.Printf("Account URL: %s\n\n", account.URL.Path) fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") fmt.Printf("- Consider BACKING UP your account! The specifics of backing up will depend on the plugin backend being used.\n") fmt.Printf("- The plugin backend may require you to REMEMBER part/all of the new account config to retrieve the key in the future!\n See the plugin specific documentation for more info.\n\n") fmt.Printf("- See the documentation for the plugin being used for more info.\n\n") } type configNodeMaker interface { makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) } // standardConfigNodeMaker is a wrapper around the makeConfigNode function to enable mocking in testing type standardConfigNodeMaker struct{} func (f standardConfigNodeMaker) makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { return makeConfigNode(ctx) }