Skip to content

Commit ca4b7dc

Browse files
fix: file keyring fails to add/import/export keys when input is not stdin (fix #9566) (backport #9821) (#9880)
* fix: file keyring fails to add/import/export keys when input is not stdin (fix #9566) (#9821) ## Description Add a test case to reproduce the issue described in #9566. The test currently fails, and I've pointed some possible solutions over #9566 (comment). But I feel this needs more works in order to provide a more robust solution. I'll keep poking at better options, but taking any pointers if anyone has ideas. (cherry picked from commit f479b51) # Conflicts: # client/keys/add.go # client/keys/add_test.go # client/keys/export_test.go * fix: merge conflict backporting pr-9821 (#9888) Co-authored-by: daeMOn <[email protected]>
1 parent ed5d165 commit ca4b7dc

File tree

7 files changed

+232
-80
lines changed

7 files changed

+232
-80
lines changed

client/context.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package client
22

33
import (
4+
"bufio"
45
"encoding/json"
56
"io"
67
"os"
@@ -60,7 +61,10 @@ func (ctx Context) WithKeyring(k keyring.Keyring) Context {
6061

6162
// WithInput returns a copy of the context with an updated input.
6263
func (ctx Context) WithInput(r io.Reader) Context {
63-
ctx.Input = r
64+
// convert to a bufio.Reader to have a shared buffer between the keyring and the
65+
// the Commands, ensuring a read from one advance the read pointer for the other.
66+
// see https://github.com/cosmos/cosmos-sdk/issues/9566.
67+
ctx.Input = bufio.NewReader(r)
6468
return ctx
6569
}
6670

client/keys/add.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ the flag --nosort is set.
8383
}
8484

8585
func runAddCmdPrepare(cmd *cobra.Command, args []string) error {
86-
buf := bufio.NewReader(cmd.InOrStdin())
8786
clientCtx, err := client.GetClientQueryContext(cmd)
8887
if err != nil {
8988
return err
9089
}
9190

91+
buf := bufio.NewReader(clientCtx.Input)
9292
return RunAddCmd(clientCtx, cmd, args, buf)
9393
}
9494

client/keys/add_test.go

+46-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/cosmos/cosmos-sdk/crypto/keyring"
1616
"github.com/cosmos/cosmos-sdk/testutil"
1717
sdk "github.com/cosmos/cosmos-sdk/types"
18+
bip39 "github.com/cosmos/go-bip39"
1819
)
1920

2021
func Test_runAddCmdBasic(t *testing.T) {
@@ -27,7 +28,7 @@ func Test_runAddCmdBasic(t *testing.T) {
2728
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, kbHome, mockIn)
2829
require.NoError(t, err)
2930

30-
clientCtx := client.Context{}.WithKeyringDir(kbHome)
31+
clientCtx := client.Context{}.WithKeyringDir(kbHome).WithInput(mockIn)
3132
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
3233

3334
t.Cleanup(func() {
@@ -114,3 +115,47 @@ func Test_runAddCmdBasic(t *testing.T) {
114115
mockIn.Reset("\n" + password + "\n" + "fail" + "\n")
115116
require.Error(t, cmd.ExecuteContext(ctx))
116117
}
118+
119+
func TestAddRecoverFileBackend(t *testing.T) {
120+
cmd := AddKeyCommand()
121+
cmd.Flags().AddFlagSet(Commands("home").PersistentFlags())
122+
123+
mockIn := testutil.ApplyMockIODiscardOutErr(cmd)
124+
kbHome := t.TempDir()
125+
126+
clientCtx := client.Context{}.WithKeyringDir(kbHome).WithInput(mockIn)
127+
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
128+
129+
cmd.SetArgs([]string{
130+
"keyname1",
131+
fmt.Sprintf("--%s=%s", flags.FlagHome, kbHome),
132+
fmt.Sprintf("--%s=%s", cli.OutputFlag, OutputFormatText),
133+
fmt.Sprintf("--%s=%s", flags.FlagKeyAlgorithm, string(hd.Secp256k1Type)),
134+
fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendFile),
135+
fmt.Sprintf("--%s", flagRecover),
136+
})
137+
138+
keyringPassword := "12345678"
139+
140+
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
141+
require.NoError(t, err)
142+
143+
mnemonic, err := bip39.NewMnemonic(entropySeed)
144+
require.NoError(t, err)
145+
146+
mockIn.Reset(fmt.Sprintf("%s\n%s\n%s\n", mnemonic, keyringPassword, keyringPassword))
147+
require.NoError(t, cmd.ExecuteContext(ctx))
148+
149+
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendFile, kbHome, mockIn)
150+
require.NoError(t, err)
151+
152+
t.Cleanup(func() {
153+
mockIn.Reset(fmt.Sprintf("%s\n%s\n", keyringPassword, keyringPassword))
154+
_ = kb.Delete("keyname1")
155+
})
156+
157+
mockIn.Reset(fmt.Sprintf("%s\n%s\n", keyringPassword, keyringPassword))
158+
info, err := kb.Key("keyname1")
159+
require.NoError(t, err)
160+
require.Equal(t, "keyname1", info.GetName())
161+
}

client/keys/export.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ FULLY AWARE OF THE RISKS. If you are unsure, you may want to do some research
3131
and export your keys in ASCII-armored encrypted format.`,
3232
Args: cobra.ExactArgs(1),
3333
RunE: func(cmd *cobra.Command, args []string) error {
34-
buf := bufio.NewReader(cmd.InOrStdin())
3534
clientCtx, err := client.GetClientQueryContext(cmd)
3635
if err != nil {
3736
return err
3837
}
38+
buf := bufio.NewReader(clientCtx.Input)
3939
unarmored, _ := cmd.Flags().GetBool(flagUnarmoredHex)
4040
unsafe, _ := cmd.Flags().GetBool(flagUnsafe)
4141

client/keys/export_test.go

+90-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package keys
22

33
import (
4+
"bufio"
45
"context"
56
"fmt"
67
"testing"
@@ -17,55 +18,95 @@ import (
1718
)
1819

1920
func Test_runExportCmd(t *testing.T) {
20-
cmd := ExportKeyCommand()
21-
cmd.Flags().AddFlagSet(Commands("home").PersistentFlags())
22-
mockIn := testutil.ApplyMockIODiscardOutErr(cmd)
23-
24-
// Now add a temporary keybase
25-
kbHome := t.TempDir()
26-
27-
// create a key
28-
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, kbHome, mockIn)
29-
require.NoError(t, err)
30-
t.Cleanup(func() {
31-
kb.Delete("keyname1") // nolint:errcheck
32-
})
33-
34-
path := sdk.GetConfig().GetFullFundraiserPath()
35-
_, err = kb.NewAccount("keyname1", testutil.TestMnemonic, "", path, hd.Secp256k1)
36-
require.NoError(t, err)
37-
38-
// Now enter password
39-
args := []string{
40-
"keyname1",
41-
fmt.Sprintf("--%s=%s", flags.FlagHome, kbHome),
42-
fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest),
21+
testCases := []struct {
22+
name string
23+
keyringBackend string
24+
extraArgs []string
25+
userInput string
26+
mustFail bool
27+
expectedOutput string
28+
}{
29+
{
30+
name: "--unsafe only must fail",
31+
keyringBackend: keyring.BackendTest,
32+
extraArgs: []string{"--unsafe"},
33+
mustFail: true,
34+
},
35+
{
36+
name: "--unarmored-hex must fail",
37+
keyringBackend: keyring.BackendTest,
38+
extraArgs: []string{"--unarmored-hex"},
39+
mustFail: true,
40+
},
41+
{
42+
name: "--unsafe --unarmored-hex fail with no user confirmation",
43+
keyringBackend: keyring.BackendTest,
44+
extraArgs: []string{"--unsafe", "--unarmored-hex"},
45+
userInput: "",
46+
mustFail: true,
47+
expectedOutput: "",
48+
},
49+
{
50+
name: "--unsafe --unarmored-hex succeed",
51+
keyringBackend: keyring.BackendTest,
52+
extraArgs: []string{"--unsafe", "--unarmored-hex"},
53+
userInput: "y\n",
54+
mustFail: false,
55+
expectedOutput: "2485e33678db4175dc0ecef2d6e1fc493d4a0d7f7ce83324b6ed70afe77f3485\n",
56+
},
57+
{
58+
name: "file keyring backend properly read password and user confirmation",
59+
keyringBackend: keyring.BackendFile,
60+
extraArgs: []string{"--unsafe", "--unarmored-hex"},
61+
// first 2 pass for creating the key, then unsafe export confirmation, then unlock keyring pass
62+
userInput: "12345678\n12345678\ny\n12345678\n",
63+
mustFail: false,
64+
expectedOutput: "2485e33678db4175dc0ecef2d6e1fc493d4a0d7f7ce83324b6ed70afe77f3485\n",
65+
},
4366
}
4467

45-
mockIn.Reset("123456789\n123456789\n")
46-
cmd.SetArgs(args)
47-
48-
clientCtx := client.Context{}.
49-
WithKeyringDir(kbHome).
50-
WithKeyring(kb)
51-
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
52-
53-
require.NoError(t, cmd.ExecuteContext(ctx))
54-
55-
argsUnsafeOnly := append(args, "--unsafe")
56-
cmd.SetArgs(argsUnsafeOnly)
57-
require.Error(t, cmd.ExecuteContext(ctx))
58-
59-
argsUnarmoredHexOnly := append(args, "--unarmored-hex")
60-
cmd.SetArgs(argsUnarmoredHexOnly)
61-
require.Error(t, cmd.ExecuteContext(ctx))
62-
63-
argsUnsafeUnarmoredHex := append(args, "--unsafe", "--unarmored-hex")
64-
cmd.SetArgs(argsUnsafeUnarmoredHex)
65-
require.Error(t, cmd.ExecuteContext(ctx))
66-
67-
mockIn, mockOut := testutil.ApplyMockIO(cmd)
68-
mockIn.Reset("y\n")
69-
require.NoError(t, cmd.ExecuteContext(ctx))
70-
require.Equal(t, "2485e33678db4175dc0ecef2d6e1fc493d4a0d7f7ce83324b6ed70afe77f3485\n", mockOut.String())
68+
for _, tc := range testCases {
69+
t.Run(tc.name, func(t *testing.T) {
70+
kbHome := t.TempDir()
71+
defaultArgs := []string{
72+
"keyname1",
73+
fmt.Sprintf("--%s=%s", flags.FlagHome, kbHome),
74+
fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, tc.keyringBackend),
75+
}
76+
77+
cmd := ExportKeyCommand()
78+
cmd.Flags().AddFlagSet(Commands("home").PersistentFlags())
79+
80+
cmd.SetArgs(append(defaultArgs, tc.extraArgs...))
81+
mockIn, mockOut := testutil.ApplyMockIO(cmd)
82+
83+
mockIn.Reset(tc.userInput)
84+
mockInBuf := bufio.NewReader(mockIn)
85+
86+
// create a key
87+
kb, err := keyring.New(sdk.KeyringServiceName(), tc.keyringBackend, kbHome, bufio.NewReader(mockInBuf))
88+
require.NoError(t, err)
89+
t.Cleanup(func() {
90+
kb.Delete("keyname1") // nolint:errcheck
91+
})
92+
93+
path := sdk.GetConfig().GetFullFundraiserPath()
94+
_, err = kb.NewAccount("keyname1", testutil.TestMnemonic, "", path, hd.Secp256k1)
95+
require.NoError(t, err)
96+
97+
clientCtx := client.Context{}.
98+
WithKeyringDir(kbHome).
99+
WithKeyring(kb).
100+
WithInput(mockInBuf)
101+
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
102+
103+
err = cmd.ExecuteContext(ctx)
104+
if tc.mustFail {
105+
require.Error(t, err)
106+
} else {
107+
require.NoError(t, err)
108+
require.Equal(t, tc.expectedOutput, mockOut.String())
109+
}
110+
})
111+
}
71112
}

client/keys/import.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ func ImportKeyCommand() *cobra.Command {
1818
Long: "Import a ASCII armored private key into the local keybase.",
1919
Args: cobra.ExactArgs(2),
2020
RunE: func(cmd *cobra.Command, args []string) error {
21-
buf := bufio.NewReader(cmd.InOrStdin())
2221
clientCtx, err := client.GetClientQueryContext(cmd)
2322
if err != nil {
2423
return err
2524
}
25+
buf := bufio.NewReader(clientCtx.Input)
2626

2727
bz, err := ioutil.ReadFile(args[1])
2828
if err != nil {

0 commit comments

Comments
 (0)