Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
stefann-01 authored Oct 16, 2024
2 parents 724f0e2 + 641d2fd commit 2ed45e3
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 12 deletions.
1 change: 0 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
13 changes: 13 additions & 0 deletions docs/gno-tooling/cli/gnokey/working-with-key-pairs.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ gno.land keychain & client
SUBCOMMANDS
add adds key to the keybase
delete deletes a key from the keybase
rotate rotate the password of a key in the keybase to a new password
generate generates a bip39 mnemonic
export exports private key armor
import imports encrypted private key armor
Expand Down Expand Up @@ -161,6 +162,18 @@ you can recover it using the key's mnemonic, or by importing it if it was export
at a previous point in time.
:::


## Rotating the password of a private key to a new password
To rotate the password of a private key from the `gnokey` keystore to a new password, 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 rotate 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 @@ -30,6 +30,7 @@ func NewRootCmd(io commands.IO, base client.BaseOptions) *commands.Command {
cmd.AddSubCommands(
client.NewAddCmd(cfg, io),
client.NewDeleteCmd(cfg, io),
client.NewRotateCmd(cfg, io),
client.NewGenerateCmd(cfg, io),
client.NewExportCmd(cfg, io),
client.NewImportCmd(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),
NewRotateCmd(cfg, io),
NewSignCmd(cfg, io),
NewVerifyCmd(cfg, io),
NewQueryCmd(cfg, io),
Expand Down
75 changes: 75 additions & 0 deletions tm2/pkg/crypto/keys/client/rotate.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 RotateCfg struct {
RootCfg *BaseCfg

Force bool
}

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

return commands.NewCommand(
commands.Metadata{
Name: "rotate",
ShortUsage: "rotate [flags] <key-name>",
ShortHelp: "rotate the password of a key in the keybase to a new password",
},
cfg,
func(_ context.Context, args []string) error {
return execRotate(cfg, args, io)
},
)
}

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

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

nameOrBech32 := args[0]

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

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

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.Rotate(nameOrBech32, oldpass, getNewpass)
if err != nil {
return err
}
io.ErrPrintln("Password rotated")

return nil
}
95 changes: 95 additions & 0 deletions tm2/pkg/crypto/keys/client/rotate_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_execRotate(t *testing.T) {
t.Parallel()

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

// initialize test options
cfg := &RotateCfg{
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 := "rotateApp_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 = execRotate(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 = execRotate(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 = execRotate(cfg, args, io)
require.Error(t, err)
require.Equal(t, "unable to parse provided password, passphrases don't match", err.Error())
}

{
// Rotate the password
args := []string{keyName}
io.SetIn(strings.NewReader(p1 + "\n" + p2 + "\n" + p2 + "\n"))
err = execRotate(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 = execRotate(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 = execRotate(cfg, args, io)
require.NoError(t, err)
}
}
4 changes: 2 additions & 2 deletions tm2/pkg/crypto/keys/keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,13 @@ func (kb dbKeybase) Delete(nameOrBech32, passphrase string, skipPass bool) error
return nil
}

// Update changes the passphrase with which an already stored key is
// Rotate changes the passphrase with which an already stored key is
// encrypted.
//
// oldpass must be the current passphrase used for encryption,
// getNewpass is a function to get the passphrase to permanently replace
// the current passphrase
func (kb dbKeybase) Update(nameOrBech32, oldpass string, getNewpass func() (string, error)) error {
func (kb dbKeybase) Rotate(nameOrBech32, oldpass string, getNewpass func() (string, error)) error {
info, err := kb.GetByNameOrAddress(nameOrBech32)
if err != nil {
return err
Expand Down
12 changes: 6 additions & 6 deletions tm2/pkg/crypto/keys/keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) {
t.Helper()

getNewpass := func() (string, error) { return pass, nil }
err := cstore.Update(name, badpass, getNewpass)
err := cstore.Rotate(name, badpass, getNewpass)
require.NotNil(t, err)
err = cstore.Update(name, pass, getNewpass)
err = cstore.Rotate(name, pass, getNewpass)
require.Nil(t, err, "%+v", err)
}

Expand Down Expand Up @@ -280,7 +280,7 @@ func TestExportImportPubKey(t *testing.T) {
require.NotNil(t, err)
}

// TestAdvancedKeyManagement verifies update, import, export functionality
// TestAdvancedKeyManagement verifies rotate, import, export functionality
func TestAdvancedKeyManagement(t *testing.T) {
t.Parallel()

Expand All @@ -297,14 +297,14 @@ func TestAdvancedKeyManagement(t *testing.T) {
require.Nil(t, err, "%+v", err)
assertPassword(t, cstore, n1, p1, p2)

// update password requires the existing password
// rotate password requires the existing password
getNewpass := func() (string, error) { return p2, nil }
err = cstore.Update(n1, "jkkgkg", getNewpass)
err = cstore.Rotate(n1, "jkkgkg", getNewpass)
require.NotNil(t, err)
assertPassword(t, cstore, n1, p1, p2)

// then it changes the password when correct
err = cstore.Update(n1, p1, getNewpass)
err = cstore.Rotate(n1, p1, getNewpass)
require.NoError(t, err)
// p2 is now the proper one!
assertPassword(t, cstore, n1, p2, p1)
Expand Down
4 changes: 2 additions & 2 deletions tm2/pkg/crypto/keys/lazy_keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info
return NewDBKeybase(db).CreateMulti(name, pubkey)
}

func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
func (lkb lazyKeybase) Rotate(name, oldpass string, getNewpass func() (string, error)) error {
db, err := db.NewDB(lkb.name, dbBackend, lkb.dir)
if err != nil {
return err
}
defer db.Close()

return NewDBKeybase(db).Update(name, oldpass, getNewpass)
return NewDBKeybase(db).Rotate(name, oldpass, getNewpass)
}

func (lkb lazyKeybase) Import(name string, armor string) (err error) {
Expand Down
2 changes: 1 addition & 1 deletion tm2/pkg/crypto/keys/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type Keybase interface {
CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error)

// The following operations will *only* work on locally-stored keys
Update(name, oldpass string, getNewpass func() (string, error)) error
Rotate(name, oldpass string, getNewpass func() (string, error)) error
Import(name string, armor string) (err error)
ImportPrivKey(name, armor, decryptPassphrase, encryptPassphrase string) error
ImportPrivKeyUnsafe(name, armor, encryptPassphrase string) error
Expand Down

0 comments on commit 2ed45e3

Please sign in to comment.