From 60d74bce67d72037eec73f8b0128567757056749 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 21 Apr 2021 15:27:32 +0300 Subject: [PATCH] [#466] cli: support NEP-2 and NEP-6 key formats NEP-2 is contains WIF encrypted with password. NEP-6 is format used for neo-go wallets. Signed-off-by: Evgenii Stratonikov --- cmd/neofs-cli/modules/accounting.go | 12 ++-- cmd/neofs-cli/modules/container.go | 54 ++++++++++++++---- cmd/neofs-cli/modules/control.go | 6 +- cmd/neofs-cli/modules/netmap.go | 23 ++++++-- cmd/neofs-cli/modules/object.go | 6 +- cmd/neofs-cli/modules/root.go | 86 +++++++++++++++++++++++++---- go.sum | 2 + 7 files changed, 152 insertions(+), 37 deletions(-) diff --git a/cmd/neofs-cli/modules/accounting.go b/cmd/neofs-cli/modules/accounting.go index 7a459f892..73d539427 100644 --- a/cmd/neofs-cli/modules/accounting.go +++ b/cmd/neofs-cli/modules/accounting.go @@ -34,17 +34,17 @@ var accountingBalanceCmd = &cobra.Command{ ctx = context.Background() ) - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } if balanceOwner == "" { - key, err := getKey() - if err != nil { - return err - } - wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) if err != nil { return err diff --git a/cmd/neofs-cli/modules/container.go b/cmd/neofs-cli/modules/container.go index 00b5d3013..da52d2266 100644 --- a/cmd/neofs-cli/modules/container.go +++ b/cmd/neofs-cli/modules/container.go @@ -73,17 +73,17 @@ var listContainersCmd = &cobra.Command{ ctx = context.Background() ) - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } if containerOwner == "" { - key, err := getKey() - if err != nil { - return err - } - wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) if err != nil { return err @@ -117,7 +117,12 @@ It will be stored in sidechain when inner ring will accepts it.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -183,7 +188,12 @@ Only owner of the container has a permission to remove container.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -227,7 +237,12 @@ var listContainerObjectsCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -288,7 +303,12 @@ var getContainerInfoCmd = &cobra.Command{ return errors.Wrap(err, "can't unmarshal container") } } else { - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -341,7 +361,12 @@ var getExtendedACLCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -400,7 +425,12 @@ Container ID in EACL table will be substituted with ID from the CLI.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } diff --git a/cmd/neofs-cli/modules/control.go b/cmd/neofs-cli/modules/control.go index ff091ae83..3cf204cf7 100644 --- a/cmd/neofs-cli/modules/control.go +++ b/cmd/neofs-cli/modules/control.go @@ -78,7 +78,7 @@ func healthCheck(cmd *cobra.Command, _ []string) error { return err } - cli, err := getSDKClient() + cli, err := getSDKClient(key) if err != nil { return err } @@ -130,7 +130,7 @@ func setNetmapStatus(cmd *cobra.Command, _ []string) error { return err } - cli, err := getSDKClient() + cli, err := getSDKClient(key) if err != nil { return err } @@ -196,7 +196,7 @@ var dropObjectsCmd = &cobra.Command{ return err } - cli, err := getSDKClient() + cli, err := getSDKClient(key) if err != nil { return err } diff --git a/cmd/neofs-cli/modules/netmap.go b/cmd/neofs-cli/modules/netmap.go index 74e1e727d..8ea8e5f63 100644 --- a/cmd/neofs-cli/modules/netmap.go +++ b/cmd/neofs-cli/modules/netmap.go @@ -51,7 +51,12 @@ var getEpochCmd = &cobra.Command{ Short: "Get current epoch number", Long: "Get current epoch number", RunE: func(cmd *cobra.Command, args []string) error { - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -72,7 +77,12 @@ var localNodeInfoCmd = &cobra.Command{ Short: "Get local node info", Long: `Get local node info`, RunE: func(cmd *cobra.Command, args []string) error { - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } @@ -105,7 +115,7 @@ var snapshotCmd = &cobra.Command{ return err } - cli, err := getSDKClient() + cli, err := getSDKClient(key) if err != nil { return err } @@ -134,7 +144,12 @@ var netInfoCmd = &cobra.Command{ Short: "Get information about NeoFS network", Long: "Get information about NeoFS network", RunE: func(cmd *cobra.Command, args []string) error { - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return err + } + + cli, err := getSDKClient(key) if err != nil { return err } diff --git a/cmd/neofs-cli/modules/object.go b/cmd/neofs-cli/modules/object.go index 8c39daeb4..f689a723d 100644 --- a/cmd/neofs-cli/modules/object.go +++ b/cmd/neofs-cli/modules/object.go @@ -194,7 +194,11 @@ func init() { } func initSession(ctx context.Context) (client.Client, *token.SessionToken, error) { - cli, err := getSDKClient() + key, err := getKey() + if err != nil { + return nil, nil, fmt.Errorf("can't fetch private key: %w", err) + } + cli, err := getSDKClient(key) if err != nil { return nil, nil, fmt.Errorf("can't create client: %w", err) } diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index bd7319bb5..269dbefda 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -10,6 +10,10 @@ import ( "strings" "github.com/mitchellh/go-homedir" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neofs-api-go/pkg" "github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/owner" @@ -53,6 +57,8 @@ var ( errInvalidKey = errors.New("provided key is incorrect") errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect") errCantGenerateKey = errors.New("can't generate new private key") + errInvalidAddress = errors.New("--address option must be specified and valid") + errInvalidPassword = errors.New("invalid password for the encrypted key") ) // Execute adds all child commands to the root command and sets flags appropriately. @@ -75,9 +81,12 @@ func init() { rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/neofs-cli/config.yaml)") - rootCmd.PersistentFlags().StringP("key", "k", "", "private key in hex, WIF or filepath (use `--key new` to generate key for request)") + rootCmd.PersistentFlags().StringP("key", "k", "", "private key in hex, WIF, NEP-2 or filepath (use `--key new` to generate key for request)") _ = viper.BindPFlag("key", rootCmd.PersistentFlags().Lookup("key")) + rootCmd.PersistentFlags().StringP("address", "", "", "address of wallet account") + _ = viper.BindPFlag("address", rootCmd.PersistentFlags().Lookup("address")) + rootCmd.PersistentFlags().StringP("rpc-endpoint", "r", "", "remote node address (as 'multiaddr' or ':')") _ = viper.BindPFlag("rpc", rootCmd.PersistentFlags().Lookup("rpc-endpoint")) @@ -122,6 +131,8 @@ func initConfig() { } } +const nep2Base58Length = 58 + // getKey returns private key that was provided in global arguments. func getKey() (*ecdsa.PrivateKey, error) { privateKey := viper.GetString("key") @@ -139,11 +150,68 @@ func getKey() (*ecdsa.PrivateKey, error) { } key, err := crypto.LoadPrivateKey(privateKey) - if err != nil { - return nil, errInvalidKey + if err == nil { + return key, nil } - return key, nil + w, err := wallet.NewWalletFromFile(privateKey) + if err == nil { + return getKeyFromWallet(w, viper.GetString("address")) + } + + if len(privateKey) == nep2Base58Length { + return getKeyFromNEP2(privateKey) + } + + return nil, errInvalidKey +} + +func getKeyFromNEP2(encryptedWif string) (*ecdsa.PrivateKey, error) { + pass, err := input.ReadPassword("Enter password > ") + if err != nil { + printVerbose("Can't read password: %v", err) + return nil, errInvalidPassword + } + + k, err := keys.NEP2Decrypt(encryptedWif, pass) + if err != nil { + printVerbose("Invalid key or password: %v", err) + return nil, errInvalidPassword + } + + return &k.PrivateKey, nil +} + +func getKeyFromWallet(w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, error) { + if addrStr == "" { + printVerbose("Address is empty") + return nil, errInvalidAddress + } + + addr, err := flags.ParseAddress(addrStr) + if err != nil { + printVerbose("Can't parse address: %s", addrStr) + return nil, errInvalidAddress + } + + acc := w.GetAccount(addr) + if acc == nil { + printVerbose("Can't find wallet account for %s", addrStr) + return nil, errInvalidAddress + } + + pass, err := input.ReadPassword("Enter password > ") + if err != nil { + printVerbose("Can't read password: %v", err) + return nil, errInvalidPassword + } + + if err := acc.Decrypt(pass); err != nil { + printVerbose("Can't decrypt account: %v", err) + return nil, errInvalidPassword + } + + return &acc.PrivateKey().PrivateKey, nil } // getEndpointAddress returns network address structure that stores multiaddr @@ -161,12 +229,7 @@ func getEndpointAddress() (*network.Address, error) { // getSDKClient returns default neofs-api-go sdk client. Consider using // opts... to provide TTL or other global configuration flags. -func getSDKClient() (client.Client, error) { - key, err := getKey() - if err != nil { - return nil, err - } - +func getSDKClient(key *ecdsa.PrivateKey) (client.Client, error) { netAddr, err := getEndpointAddress() if err != nil { return nil, err @@ -177,7 +240,8 @@ func getSDKClient() (client.Client, error) { return nil, errInvalidEndpoint } - return client.New(client.WithAddress(ipAddr), client.WithDefaultPrivateKey(key)) + c, err := client.New(client.WithAddress(ipAddr), client.WithDefaultPrivateKey(key)) + return c, err } func getTTL() uint32 { diff --git a/go.sum b/go.sum index b6801bc06..caebbf473 100644 --- a/go.sum +++ b/go.sum @@ -383,6 +383,7 @@ github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2K github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -499,6 +500,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=