forked from TrueCloudLab/frostfs-node
[#1216] neofs-cli: Reuse key retrieving code between modules
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
006d6e8b48
commit
8d79168129
5 changed files with 169 additions and 127 deletions
26
cmd/neofs-cli/internal/key/nep2.go
Normal file
26
cmd/neofs-cli/internal/key/nep2.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
const nep2Base58Length = 58
|
||||||
|
|
||||||
|
// FromNEP2 extracts private key from NEP2-encrypted string.
|
||||||
|
func FromNEP2(encryptedWif string) (*ecdsa.PrivateKey, error) {
|
||||||
|
pass, err := getPassword()
|
||||||
|
if err != nil {
|
||||||
|
printVerbose("Can't read password: %v", err)
|
||||||
|
return nil, ErrInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := keys.NEP2Decrypt(encryptedWif, pass, keys.NEP2ScryptParams())
|
||||||
|
if err != nil {
|
||||||
|
printVerbose("Invalid key or password: %v", err)
|
||||||
|
return nil, ErrInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
return &k.PrivateKey, nil
|
||||||
|
}
|
54
cmd/neofs-cli/internal/key/raw.go
Normal file
54
cmd/neofs-cli/internal/key/raw.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get returns private key from the followind sources:
|
||||||
|
// 1. WIF
|
||||||
|
// 2. Raw binary key
|
||||||
|
// 3. Wallet file
|
||||||
|
// 4. NEP-2 encrypted WIF.
|
||||||
|
// Ideally we want to touch file-system on the last step.
|
||||||
|
// However, asking for NEP-2 password seems to be confusing if we provide a wallet.
|
||||||
|
func Get(keyDesc string, address string) (*ecdsa.PrivateKey, error) {
|
||||||
|
priv, err := keys.NewPrivateKeyFromWIF(keyDesc)
|
||||||
|
if err == nil {
|
||||||
|
return &priv.PrivateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := getKeyFromFile(keyDesc)
|
||||||
|
if err == nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile(keyDesc)
|
||||||
|
if err == nil {
|
||||||
|
return FromWallet(w, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyDesc) == nep2Base58Length {
|
||||||
|
return FromNEP2(keyDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyFromFile(keyPath string) (*ecdsa.PrivateKey, error) {
|
||||||
|
data, err := os.ReadFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := keys.NewPrivateKeyFromBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &priv.PrivateKey, nil
|
||||||
|
}
|
74
cmd/neofs-cli/internal/key/wallet.go
Normal file
74
cmd/neofs-cli/internal/key/wallet.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key-related errors.
|
||||||
|
var (
|
||||||
|
ErrInvalidKey = errors.New("provided key is incorrect")
|
||||||
|
ErrInvalidAddress = errors.New("--address option must be specified and valid")
|
||||||
|
ErrInvalidPassword = errors.New("invalid password for the encrypted key")
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromWallet returns private key of the wallet account.
|
||||||
|
func FromWallet(w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, error) {
|
||||||
|
var (
|
||||||
|
addr util.Uint160
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if addrStr == "" {
|
||||||
|
printVerbose("Using default wallet address")
|
||||||
|
addr = w.GetChangeAddress()
|
||||||
|
} else {
|
||||||
|
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 := getPassword()
|
||||||
|
if err != nil {
|
||||||
|
printVerbose("Can't read password: %v", err)
|
||||||
|
return nil, ErrInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := acc.Decrypt(pass, keys.NEP2ScryptParams()); err != nil {
|
||||||
|
printVerbose("Can't decrypt account: %v", err)
|
||||||
|
return nil, ErrInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
return &acc.PrivateKey().PrivateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPassword() (string, error) {
|
||||||
|
// this check allows empty passwords
|
||||||
|
if viper.IsSet("password") {
|
||||||
|
return viper.GetString("password"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.ReadPassword("Enter password > ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVerbose(format string, a ...interface{}) {
|
||||||
|
if viper.GetBool("verbose") {
|
||||||
|
fmt.Printf(format+"\n", a...)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
@ -55,13 +56,13 @@ func Test_getKey(t *testing.T) {
|
||||||
Writer: io.Discard,
|
Writer: io.Discard,
|
||||||
}, "")
|
}, "")
|
||||||
|
|
||||||
checkKeyError(t, filepath.Join(dir, "badfile"), errInvalidKey)
|
checkKeyError(t, filepath.Join(dir, "badfile"), key.ErrInvalidKey)
|
||||||
|
|
||||||
t.Run("wallet", func(t *testing.T) {
|
t.Run("wallet", func(t *testing.T) {
|
||||||
checkKeyError(t, wallPath, errInvalidPassword)
|
checkKeyError(t, wallPath, key.ErrInvalidPassword)
|
||||||
|
|
||||||
in.WriteString("invalid\r")
|
in.WriteString("invalid\r")
|
||||||
checkKeyError(t, wallPath, errInvalidPassword)
|
checkKeyError(t, wallPath, key.ErrInvalidPassword)
|
||||||
|
|
||||||
in.WriteString("pass\r")
|
in.WriteString("pass\r")
|
||||||
checkKey(t, wallPath, acc2.PrivateKey()) // default account
|
checkKey(t, wallPath, acc2.PrivateKey()) // default account
|
||||||
|
@ -71,12 +72,12 @@ func Test_getKey(t *testing.T) {
|
||||||
checkKey(t, wallPath, acc1.PrivateKey())
|
checkKey(t, wallPath, acc1.PrivateKey())
|
||||||
|
|
||||||
viper.Set(address, "not an address")
|
viper.Set(address, "not an address")
|
||||||
checkKeyError(t, wallPath, errInvalidAddress)
|
checkKeyError(t, wallPath, key.ErrInvalidAddress)
|
||||||
|
|
||||||
acc, err := wallet.NewAccount()
|
acc, err := wallet.NewAccount()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
viper.Set(address, acc.Address)
|
viper.Set(address, acc.Address)
|
||||||
checkKeyError(t, wallPath, errInvalidAddress)
|
checkKeyError(t, wallPath, key.ErrInvalidAddress)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("WIF", func(t *testing.T) {
|
t.Run("WIF", func(t *testing.T) {
|
||||||
|
@ -84,20 +85,20 @@ func Test_getKey(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NEP-2", func(t *testing.T) {
|
t.Run("NEP-2", func(t *testing.T) {
|
||||||
checkKeyError(t, nep2, errInvalidPassword)
|
checkKeyError(t, nep2, key.ErrInvalidPassword)
|
||||||
|
|
||||||
in.WriteString("invalid\r")
|
in.WriteString("invalid\r")
|
||||||
checkKeyError(t, nep2, errInvalidPassword)
|
checkKeyError(t, nep2, key.ErrInvalidPassword)
|
||||||
|
|
||||||
in.WriteString("pass\r")
|
in.WriteString("pass\r")
|
||||||
checkKey(t, nep2, nep2Key)
|
checkKey(t, nep2, nep2Key)
|
||||||
|
|
||||||
t.Run("password from config", func(t *testing.T) {
|
t.Run("password from config", func(t *testing.T) {
|
||||||
viper.Set(password, "invalid")
|
viper.Set("password", "invalid")
|
||||||
in.WriteString("pass\r")
|
in.WriteString("pass\r")
|
||||||
checkKeyError(t, nep2, errInvalidPassword)
|
checkKeyError(t, nep2, key.ErrInvalidPassword)
|
||||||
|
|
||||||
viper.Set(password, "pass")
|
viper.Set("password", "pass")
|
||||||
in.WriteString("invalid\r")
|
in.WriteString("invalid\r")
|
||||||
checkKey(t, nep2, nep2Key)
|
checkKey(t, nep2, nep2Key)
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,12 +9,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
||||||
internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client"
|
internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client"
|
||||||
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl"
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl"
|
||||||
bearerCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/bearer"
|
bearerCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/bearer"
|
||||||
"github.com/nspcc-dev/neofs-node/misc"
|
"github.com/nspcc-dev/neofs-node/misc"
|
||||||
|
@ -56,8 +53,6 @@ const (
|
||||||
addressDefault = ""
|
addressDefault = ""
|
||||||
addressUsage = "address of wallet account"
|
addressUsage = "address of wallet account"
|
||||||
|
|
||||||
password = "password"
|
|
||||||
|
|
||||||
rpc = "rpc-endpoint"
|
rpc = "rpc-endpoint"
|
||||||
rpcShorthand = "r"
|
rpcShorthand = "r"
|
||||||
rpcDefault = ""
|
rpcDefault = ""
|
||||||
|
@ -94,11 +89,8 @@ and much more!`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidKey = errors.New("provided key is incorrect")
|
|
||||||
errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
||||||
errCantGenerateKey = errors.New("can't generate new private key")
|
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.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
@ -171,8 +163,6 @@ func initConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nep2Base58Length = 58
|
|
||||||
|
|
||||||
// getKey returns private key that was provided in global arguments.
|
// getKey returns private key that was provided in global arguments.
|
||||||
func getKey() (*ecdsa.PrivateKey, error) {
|
func getKey() (*ecdsa.PrivateKey, error) {
|
||||||
if viper.GetBool(generateKey) {
|
if viper.GetBool(generateKey) {
|
||||||
|
@ -186,110 +176,7 @@ func getKey() (*ecdsa.PrivateKey, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeyNoGenerate() (*ecdsa.PrivateKey, error) {
|
func getKeyNoGenerate() (*ecdsa.PrivateKey, error) {
|
||||||
// Ideally we want to touch file-system on the last step.
|
return key.Get(viper.GetString(walletPath), viper.GetString(address))
|
||||||
// However, asking for NEP-2 password seems to be confusing if we provide a wallet.
|
|
||||||
// Thus we try keys in the following order:
|
|
||||||
// 1. WIF
|
|
||||||
// 2. Raw binary key
|
|
||||||
// 3. Wallet file
|
|
||||||
// 4. NEP-2 encrypted WIF.
|
|
||||||
keyDesc := viper.GetString(walletPath)
|
|
||||||
priv, err := keys.NewPrivateKeyFromWIF(keyDesc)
|
|
||||||
if err == nil {
|
|
||||||
return &priv.PrivateKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := getKeyFromFile(keyDesc)
|
|
||||||
if err == nil {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := wallet.NewWalletFromFile(keyDesc)
|
|
||||||
if err == nil {
|
|
||||||
return getKeyFromWallet(w, viper.GetString(address))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keyDesc) == nep2Base58Length {
|
|
||||||
return getKeyFromNEP2(keyDesc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errInvalidKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPassword() (string, error) {
|
|
||||||
// this check allows empty passwords
|
|
||||||
if viper.IsSet(password) {
|
|
||||||
return viper.GetString(password), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return input.ReadPassword("Enter password > ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyFromFile(keyPath string) (*ecdsa.PrivateKey, error) {
|
|
||||||
data, err := os.ReadFile(keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %v", errInvalidKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := keys.NewPrivateKeyFromBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %v", errInvalidKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &priv.PrivateKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyFromNEP2(encryptedWif string) (*ecdsa.PrivateKey, error) {
|
|
||||||
pass, err := getPassword()
|
|
||||||
if err != nil {
|
|
||||||
printVerbose("Can't read password: %v", err)
|
|
||||||
return nil, errInvalidPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := keys.NEP2Decrypt(encryptedWif, pass, keys.NEP2ScryptParams())
|
|
||||||
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) {
|
|
||||||
var (
|
|
||||||
addr util.Uint160
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if addrStr == "" {
|
|
||||||
printVerbose("Using default wallet address")
|
|
||||||
addr = w.GetChangeAddress()
|
|
||||||
} else {
|
|
||||||
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 := getPassword()
|
|
||||||
if err != nil {
|
|
||||||
printVerbose("Can't read password: %v", err)
|
|
||||||
return nil, errInvalidPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := acc.Decrypt(pass, keys.NEP2ScryptParams()); 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
|
// getEndpointAddress returns network address structure that stores multiaddr
|
||||||
|
@ -311,10 +198,10 @@ type clientWithKey interface {
|
||||||
|
|
||||||
// reads private key from command args and call prepareAPIClientWithKey with it.
|
// reads private key from command args and call prepareAPIClientWithKey with it.
|
||||||
func prepareAPIClient(cmd *cobra.Command, dst ...clientWithKey) {
|
func prepareAPIClient(cmd *cobra.Command, dst ...clientWithKey) {
|
||||||
key, err := getKey()
|
p, err := getKey()
|
||||||
exitOnErr(cmd, errf("get private key: %w", err))
|
exitOnErr(cmd, errf("get private key: %w", err))
|
||||||
|
|
||||||
prepareAPIClientWithKey(cmd, key, dst...)
|
prepareAPIClientWithKey(cmd, p, dst...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates NeoFS API client and writes it to target along with the private key.
|
// creates NeoFS API client and writes it to target along with the private key.
|
||||||
|
|
Loading…
Reference in a new issue