Skip to content

Commit

Permalink
feat: In the gnokey CLI, add command to update the password
Browse files Browse the repository at this point in the history
Signed-off-by: Jeff Thompson <jeff@thefirst.org>
  • Loading branch information
jefft0 committed Aug 14, 2024
1 parent 4553879 commit 832e404
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 0 deletions.
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),
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",
ShortUsage: "update [flags] <key-name>",
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)
}
}

0 comments on commit 832e404

Please sign in to comment.