From 7f27af0cca3f98bb7c5852226ca20bf65a4f15c6 Mon Sep 17 00:00:00 2001 From: Peter Bukva Date: Thu, 31 Oct 2024 02:05:42 +0000 Subject: [PATCH] Suport for input file & stdin propmt, impr. tolerance for key file content --- client/keys/import.go | 117 +++++++++++++++++++++++++++++++++++--- client/keys/root.go | 1 + crypto/keyring/keyring.go | 23 +++----- 3 files changed, 119 insertions(+), 22 deletions(-) diff --git a/client/keys/import.go b/client/keys/import.go index e8775f1d59..b051567b9d 100644 --- a/client/keys/import.go +++ b/client/keys/import.go @@ -2,9 +2,12 @@ package keys import ( "bufio" + "encoding/hex" "fmt" "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "os" + "strings" "github.com/spf13/cobra" @@ -25,30 +28,128 @@ func ImportKeyCommand() *cobra.Command { return err } buf := bufio.NewReader(clientCtx.Input) + passphrase, err := input.GetPassword("Enter passphrase to decrypt your key:", buf) + if err != nil { + return err + } bz, err := os.ReadFile(args[1]) if err != nil { return err } - unarmored, _ := cmd.Flags().GetBool(flagUnarmoredHex) + return clientCtx.Keyring.ImportPrivKey(args[0], string(bz), passphrase) + }, + } + + return cmd +} + +// ImportUnarmoredKeyCommand imports private keys from a keyfile. +func ImportUnarmoredKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "import-unarmored [keyfile]", + Short: "Import unarmored private key into the local keybase", + Long: `Import hex encoded unarmored private key into the local keybase + +Key must be hex encoded, and can be passed in either via file, or via +user password prompt. +If the 2nd positional argument [keyfile] has been provided, private key +will be read from that file. The keyfile must contain hex encoded +unarmored raw private key on the very 1st line, and that line must +contain only the private key. +Otherwise, if the [keyfile] is not provided, the private key will be +requested via password prompt where it will be read from stdin. +At the moment, only the secp256k1 curve/algo is supported.`, + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + buf := bufio.NewReader(clientCtx.Input) + var privKeyHex string + if len(args) == 1 { + privKeyHex, err = input.GetPassword("Enter hex encoded private key:", buf) + if err != nil { + return err + } + } else { + filename := args[1] + f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm) + if err != nil { + return fmt.Errorf("open file \"%s\" error: %w", filename, err) + } + defer f.Close() + + sc := bufio.NewScanner(f) + if sc.Scan() { + firstLine := sc.Text() + privKeyHex = strings.TrimSpace(firstLine) + } else { + return fmt.Errorf("unable to read 1st line from the \"%s\" file", filename) + } + + if err := sc.Err(); err != nil { + return fmt.Errorf("error while scanning the \"%s\" file: %w", filename, err) + } + } + algo, _ := cmd.Flags().GetString(flagUnarmoredKeyAlgo) - if unarmored { - return clientCtx.Keyring.ImportUnarmoredPrivKey(args[0], string(bz), algo) + privKeyHexLC := strings.ToLower(privKeyHex) + if strings.HasPrefix(privKeyHexLC, "0x") { + privKeyHexLC = privKeyHexLC[2:] + } else if strings.HasPrefix(privKeyHexLC, "x") { + privKeyHexLC = privKeyHexLC[1:] } - passphrase, err := input.GetPassword("Enter passphrase to decrypt your key:", buf) + privKeyRaw, err := hex.DecodeString(privKeyHexLC) if err != nil { - return err + return fmt.Errorf("failed to decode provided hex value of private key: %w", err) } - return clientCtx.Keyring.ImportPrivKey(args[0], string(bz), passphrase) + info, err := clientCtx.Keyring.ImportUnarmoredPrivKey(args[0], privKeyRaw, algo) + if err != nil { + return fmt.Errorf("importing unarmored private key: %w", err) + + } + + if err := printCreateUnarmored(cmd, info, clientCtx.OutputFormat); err != nil { + return fmt.Errorf("printing private key info: %w", err) + } + + return nil }, } - cmd.Flags().Bool(flagUnarmoredHex, false, "Import unarmored hex privkey") - cmd.Flags().String(flagUnarmoredKeyAlgo, string(hd.Secp256k1Type), fmt.Sprintf("defines cryptographic scheme algorithm of the private key (%s, %s, %s, %s)", hd.Secp256k1Type, hd.Ed25519Type, hd.Sr25519Type, hd.MultiType)) + cmd.Flags().String(flagUnarmoredKeyAlgo, string(hd.Secp256k1Type), fmt.Sprintf("defines cryptographic scheme algorithm of the private key (\"%s\", \"%s\"). At the moent *ONLY* the \"%s\" is supported. Defaults to \"%s\".", hd.Secp256k1Type, hd.Ed25519Type, hd.Secp256k1Type, hd.Secp256k1Type)) return cmd } + +func printCreateUnarmored(cmd *cobra.Command, info keyring.Info, outputFormat string) error { + switch outputFormat { + case OutputFormatText: + cmd.PrintErrln() + printKeyInfo(cmd.OutOrStdout(), info, keyring.MkAccKeyOutput, outputFormat) + case OutputFormatJSON: + out, err := keyring.MkAccKeyOutput(info) + if err != nil { + return err + } + + jsonString, err := KeysCdc.MarshalJSON(out) + if err != nil { + return err + } + + cmd.Println(string(jsonString)) + + default: + return fmt.Errorf("invalid output format %s", outputFormat) + } + + return nil +} diff --git a/client/keys/root.go b/client/keys/root.go index 938a0d53a1..e5c97b8b87 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -42,6 +42,7 @@ The pass backend requires GnuPG: https://gnupg.org/ AddKeyCommand(), ExportKeyCommand(), ImportKeyCommand(), + ImportUnarmoredKeyCommand(), ListKeysCmd(), ShowKeysCmd(), DeleteKeyCommand(), diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index dabfdd3351..348586f1dc 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -112,8 +112,8 @@ type Importer interface { // ImportPrivKey imports ASCII armored passphrase-encrypted private keys. ImportPrivKey(uid, armor, passphrase string) error - // ImportUnarmoredPrivKey imports HEX encoded UNARMORED private keys. - ImportUnarmoredPrivKey(uid, unarmoredPrivKeyHex, algo string) error + // ImportUnarmoredPrivKey imports UNARMORED private key. + ImportUnarmoredPrivKey(uid string, unarmoredPrivKeyRaw []byte, algo string) (Info, error) // ImportPubKey imports ASCII armored public keys. ImportPubKey(uid string, armor string) error @@ -308,20 +308,15 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error { return nil } -func (ks keystore) ImportUnarmoredPrivKey(uid, unarmoredPrivKeyHex, algo string) error { +func (ks keystore) ImportUnarmoredPrivKey(uid string, unarmoredPrivKeyRaw []byte, algo string) (Info, error) { if _, err := ks.Key(uid); err == nil { - return fmt.Errorf("cannot overwrite key: %s", uid) - } - - privKeyRaw, err := hex.DecodeString(unarmoredPrivKeyHex) - if err != nil { - return errors.Wrap(err, "failed to decode provided hex value of private key") + return nil, fmt.Errorf("cannot overwrite key: %s", uid) } var privKey types.PrivKey switch hd.PubKeyType(algo) { case hd.Secp256k1Type: - privKey = &secp256k1.PrivKey{Key: privKeyRaw} + privKey = &secp256k1.PrivKey{Key: unarmoredPrivKeyRaw} case hd.Ed25519Type: fallthrough case hd.Sr25519Type: @@ -329,7 +324,7 @@ func (ks keystore) ImportUnarmoredPrivKey(uid, unarmoredPrivKeyHex, algo string) case hd.MultiType: fallthrough default: - return fmt.Errorf("only the \"%s\" algo is supported at the moment", hd.Secp256k1Type) + return nil, fmt.Errorf("only the \"%s\" algo is supported at the moment", hd.Secp256k1Type) } //privKey, err := legacy.PrivKeyFromBytes(privKeyRaw) @@ -337,12 +332,12 @@ func (ks keystore) ImportUnarmoredPrivKey(uid, unarmoredPrivKeyHex, algo string) // return errors.Wrap(err, "failed to create private key from provided hex value") //} - _, err = ks.writeLocalKey(uid, privKey, hd.PubKeyType(algo)) + info, err := ks.writeLocalKey(uid, privKey, hd.PubKeyType(algo)) if err != nil { - return err + return nil, err } - return nil + return info, nil } func (ks keystore) ImportPubKey(uid string, armor string) error {