Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: In the gnokey CLI, add command to update the password #2700

Merged
merged 8 commits into from
Oct 16, 2024
13 changes: 13 additions & 0 deletions docs/getting-started/local-setup/working-with-key-pairs.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ SUBCOMMANDS
export Exports private key armor
import Imports encrypted private key armor
list Lists all keys in the keybase
update Update the password of a key in the keybase
sign Signs the document
verify Verifies the document signature
query Makes an ABCI query
Expand Down Expand Up @@ -159,6 +160,18 @@ you can recover it using the key's mnemonic, or by importing it if it was export
at a previous point in time.
:::


## Updating the password of a private key
To update the password of a private key from the `gnokey` keystore, we need to know the name or
address of the key to remove.
After we have this information, we can run the following command:

```bash
gnokey update MyKey
```

After entering the current key decryption password and the new password, the password of the key will be updated in the keystore.

## Exporting a private key
Private keys stored in the `gnokey` keystore can be exported to a desired place
on the user's filesystem.
Expand Down
1 change: 1 addition & 0 deletions gno.land/pkg/keyscli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func NewRootCmd(io commands.IO, base client.BaseOptions) *commands.Command {
client.NewExportCmd(cfg, io),
client.NewImportCmd(cfg, io),
client.NewListCmd(cfg, io),
client.NewUpdateCmd(cfg, io),
jefft0 marked this conversation as resolved.
Show resolved Hide resolved
client.NewSignCmd(cfg, io),
client.NewVerifyCmd(cfg, io),
client.NewQueryCmd(cfg, io),
Expand Down
1 change: 1 addition & 0 deletions tm2/pkg/crypto/keys/client/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewRootCmdWithBaseConfig(io commands.IO, base BaseOptions) *commands.Comman
NewExportCmd(cfg, io),
NewImportCmd(cfg, io),
NewListCmd(cfg, io),
NewUpdateCmd(cfg, io),
NewSignCmd(cfg, io),
NewVerifyCmd(cfg, io),
NewQueryCmd(cfg, io),
Expand Down
75 changes: 75 additions & 0 deletions tm2/pkg/crypto/keys/client/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package client

import (
"context"
"flag"
"fmt"

"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
)

type UpdateCfg struct {
RootCfg *BaseCfg

Force bool
}

func NewUpdateCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command {
cfg := &UpdateCfg{
RootCfg: rootCfg,
}

return commands.NewCommand(
commands.Metadata{
Name: "update",
thehowl marked this conversation as resolved.
Show resolved Hide resolved
ShortUsage: "update [flags] <key-name>",
thehowl marked this conversation as resolved.
Show resolved Hide resolved
ShortHelp: "update the password of a key in the keybase",
},
cfg,
func(_ context.Context, args []string) error {
return execUpdate(cfg, args, io)
},

Check warning on line 32 in tm2/pkg/crypto/keys/client/update.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/crypto/keys/client/update.go#L31-L32

Added lines #L31 - L32 were not covered by tests
)
}

func (c *UpdateCfg) RegisterFlags(fs *flag.FlagSet) {
}

func execUpdate(cfg *UpdateCfg, args []string, io commands.IO) error {
if len(args) != 1 {
return flag.ErrHelp

Check warning on line 41 in tm2/pkg/crypto/keys/client/update.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/crypto/keys/client/update.go#L41

Added line #L41 was not covered by tests
}

nameOrBech32 := args[0]

kb, err := keys.NewKeyBaseFromDir(cfg.RootCfg.Home)
if err != nil {
return err

Check warning on line 48 in tm2/pkg/crypto/keys/client/update.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/crypto/keys/client/update.go#L48

Added line #L48 was not covered by tests
}

oldpass, err := io.GetPassword("Enter the current password:", cfg.RootCfg.InsecurePasswordStdin)
if err != nil {
return err

Check warning on line 53 in tm2/pkg/crypto/keys/client/update.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/crypto/keys/client/update.go#L53

Added line #L53 was not covered by tests
}

newpass, err := io.GetCheckPassword(
[2]string{
"Enter the new password to encrypt your key to disk:",
"Repeat the password:",
},
cfg.RootCfg.InsecurePasswordStdin,
)
if err != nil {
return fmt.Errorf("unable to parse provided password, %w", err)
}

getNewpass := func() (string, error) { return newpass, nil }
err = kb.Update(nameOrBech32, oldpass, getNewpass)
if err != nil {
return err
}
io.ErrPrintln("Password updated")

return nil
}
95 changes: 95 additions & 0 deletions tm2/pkg/crypto/keys/client/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package client

import (
"strings"
"testing"

"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_execUpdate(t *testing.T) {
t.Parallel()

// make new test dir
kbHome, kbCleanUp := testutils.NewTestCaseDir(t)
defer kbCleanUp()

// initialize test options
cfg := &UpdateCfg{
RootCfg: &BaseCfg{
BaseOptions: BaseOptions{
Home: kbHome,
InsecurePasswordStdin: true,
},
},
}

io := commands.NewTestIO()

// Add test accounts to keybase.
kb, err := keys.NewKeyBaseFromDir(kbHome)
assert.NoError(t, err)

keyName := "updateApp_Key1"
p1, p2 := "1234", "foobar"
mnemonic := "equip will roof matter pink blind book anxiety banner elbow sun young"

_, err = kb.CreateAccount(keyName, mnemonic, "", p1, 0, 0)
assert.NoError(t, err)

{
// test: Key not found
args := []string{"blah"}
io.SetIn(strings.NewReader(p1 + "\n" + p2 + "\n" + p2 + "\n"))
err = execUpdate(cfg, args, io)
require.Error(t, err)
require.Equal(t, "Key blah not found", err.Error())
}

{
// test: Wrong password
args := []string{keyName}
io.SetIn(strings.NewReader("blah" + "\n" + p2 + "\n" + p2 + "\n"))
err = execUpdate(cfg, args, io)
require.Error(t, err)
require.Equal(t, "invalid account password", err.Error())
}

{
// test: New passwords don't match
args := []string{keyName}
io.SetIn(strings.NewReader(p1 + "\n" + p2 + "\n" + "blah" + "\n"))
err = execUpdate(cfg, args, io)
require.Error(t, err)
require.Equal(t, "unable to parse provided password, passphrases don't match", err.Error())
}

{
// Update the password
args := []string{keyName}
io.SetIn(strings.NewReader(p1 + "\n" + p2 + "\n" + p2 + "\n"))
err = execUpdate(cfg, args, io)
require.NoError(t, err)
}

{
// test: The old password shouldn't work
args := []string{keyName}
io.SetIn(strings.NewReader(p1 + "\n" + p1 + "\n" + p1 + "\n"))
err = execUpdate(cfg, args, io)
require.Error(t, err)
require.Equal(t, "invalid account password", err.Error())
}

{
// Updating the new password to itself should work
args := []string{keyName}
io.SetIn(strings.NewReader(p2 + "\n" + p2 + "\n" + p2 + "\n"))
err = execUpdate(cfg, args, io)
require.NoError(t, err)
}
}
Loading