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

Implement ipfs key {rm, rename} #3892

Merged
merged 10 commits into from
May 18, 2017
173 changes: 169 additions & 4 deletions core/commands/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ var KeyCmd = &cmds.Command{
`,
},
Subcommands: map[string]*cmds.Command{
"gen": KeyGenCmd,
"list": KeyListCmd,
"gen": keyGenCmd,
"list": keyListCmd,
"rename": keyRenameCmd,
"rm": keyRmCmd,
},
}

Expand All @@ -47,7 +49,14 @@ type KeyOutputList struct {
Keys []KeyOutput
}

var KeyGenCmd = &cmds.Command{
type KeyRenameOutput struct {
Was string
Now string
Id string
Overwrite bool
}

var keyGenCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new keypair",
},
Expand Down Expand Up @@ -150,7 +159,7 @@ var KeyGenCmd = &cmds.Command{
Type: KeyOutput{},
}

var KeyListCmd = &cmds.Command{
var keyListCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List all local keypairs",
},
Expand Down Expand Up @@ -202,6 +211,162 @@ var KeyListCmd = &cmds.Command{
Type: KeyOutputList{},
}

var keyRenameCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Rename a keypair",
},
Arguments: []cmds.Argument{
cmds.StringArg("name", true, false, "name of key to rename"),
cmds.StringArg("newName", true, false, "new name of the key"),
},
Options: []cmds.Option{
cmds.BoolOption("force", "f", "Allow to overwrite an existing key."),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

ks := n.Repo.Keystore()

name := req.Arguments()[0]
newName := req.Arguments()[1]

if name == "self" {
res.SetError(fmt.Errorf("cannot rename key with name 'self'"), cmds.ErrNormal)
return
}

if newName == "self" {
res.SetError(fmt.Errorf("cannot overwrite key with name 'self'"), cmds.ErrNormal)
return
}

oldKey, err := ks.Get(name)
if err != nil {
res.SetError(fmt.Errorf("no key named %s was found", name), cmds.ErrNormal)
return
}

pubKey := oldKey.GetPublic()

pid, err := peer.IDFromPublicKey(pubKey)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

overwrite := false
force, _, _ := res.Request().Option("f").Bool()
if force && ks.Has(newName) {
overwrite = true
err := ks.Delete(newName)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
}

err = ks.Put(newName, oldKey)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

err = ks.Delete(name)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

res.SetOutput(&KeyRenameOutput{
Was: name,
Now: newName,
Id: pid.Pretty(),
Overwrite: overwrite,
})
},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
k, ok := res.Output().(*KeyRenameOutput)
if !ok {
return nil, fmt.Errorf("expected a KeyRenameOutput as command result")
}

buf := new(bytes.Buffer)

if k.Overwrite {
fmt.Fprintf(buf, "Key %s renamed to %s with overwriting\n", k.Id, k.Now)
} else {
fmt.Fprintf(buf, "Key %s renamed to %s\n", k.Id, k.Now)
}
return buf, nil
},
},
Type: KeyRenameOutput{},
}

var keyRmCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Remove a keypair",
},
Arguments: []cmds.Argument{
cmds.StringArg("name", true, true, "names of keys to remove").EnableStdin(),
},
Options: []cmds.Option{
cmds.BoolOption("l", "Show extra information about keys."),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

names := req.Arguments()

list := make([]KeyOutput, 0, len(names))
for _, name := range names {
if name == "self" {
res.SetError(fmt.Errorf("cannot remove key with name 'self'"), cmds.ErrNormal)
return
}

removed, err := n.Repo.Keystore().Get(name)
if err != nil {
res.SetError(fmt.Errorf("no key named %s was found", name), cmds.ErrNormal)
return
}

pubKey := removed.GetPublic()

pid, err := peer.IDFromPublicKey(pubKey)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

list = append(list, KeyOutput{Name: name, Id: pid.Pretty()})
}

for _, name := range names {
err = n.Repo.Keystore().Delete(name)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
}

res.SetOutput(&KeyOutputList{list})
},
Marshalers: cmds.MarshalerMap{
cmds.Text: keyOutputListMarshaler,
},
Type: KeyOutputList{},
}

func keyOutputListMarshaler(res cmds.Response) (io.Reader, error) {
withId, _, _ := res.Request().Option("l").Bool()

Expand Down
9 changes: 9 additions & 0 deletions keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

type Keystore interface {
Has(string) bool
Put(string, ci.PrivKey) error
Get(string) (ci.PrivKey, error)
Delete(string) error
Expand Down Expand Up @@ -54,6 +55,14 @@ func NewFSKeystore(dir string) (*FSKeystore, error) {
return &FSKeystore{dir}, nil
}

func (ks *FSKeystore) Has(name string) bool {
kp := filepath.Join(ks.dir, name)

_, err := os.Stat(kp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has should probably also return an error. If we run into some permissions issue, this will report that the key doesnt exist. use os.IsErrNotExist to check for file existence, otherwise the error is a real error


return err == nil
}

func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
Expand Down
8 changes: 8 additions & 0 deletions keystore/keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ func TestKeystoreBasics(t *testing.T) {
t.Fatal(err)
}

if !ks.Has("foo") {
t.Fatal("should know it has a key named foo")
}

if ks.Has("nonexistingkey") {
t.Fatal("should know it doesn't have a key named nonexistingkey")
}

if err := ks.Delete("bar"); err != nil {
t.Fatal(err)
}
Expand Down
5 changes: 5 additions & 0 deletions keystore/memkeystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ func NewMemKeystore() *MemKeystore {
return &MemKeystore{make(map[string]ci.PrivKey)}
}

func (mk *MemKeystore) Has(name string) bool {
_, ok := mk.keys[name]
return ok
}

func (mk *MemKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
Expand Down
9 changes: 9 additions & 0 deletions keystore/memkeystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ func TestMemKeyStoreBasics(t *testing.T) {
if err == nil {
t.Fatal("should not be able to overwrite key")
}

if !ks.Has("foo") {
t.Fatal("should know it has a key named foo")
}

if ks.Has("nonexistingkey") {
t.Fatal("should know it doesn't have a key named nonexistingkey")
}

if err := ks.Delete("bar"); err != nil {
t.Fatal(err)
}
Expand Down
26 changes: 26 additions & 0 deletions test/sharness/t0165-keystore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@ test_key_cmd() {
PeerID="$(ipfs config Identity.PeerID)"
ipfs key list -l | grep "$PeerID self"
'

test_expect_success "key rm remove a key" '
ipfs key rm foobarsa
echo bazed > list_exp &&
echo self >> list_exp
ipfs key list | sort > list_out &&
test_cmp list_exp list_out
'

test_expect_success "key rename rename a key" '
ipfs key rename bazed fooed
echo fooed > list_exp &&
echo self >> list_exp
ipfs key list | sort > list_out &&
test_cmp list_exp list_out
'

test_expect_success "key rename can't remove self" '
test_must_fail ipfs key rename self bar 2>&1 | tee key_rename_out &&
grep -q "Error: cannot rename key with name" key_rename_out
'

test_expect_success "key rename can't overwrite self, even with force" '
test_must_fail ipfs key rename -f fooed self 2>&1 | tee key_rename_out &&
grep -q "Error: cannot overwrite key with name" key_rename_out
'
}

test_key_cmd
Expand Down