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
182 changes: 178 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,15 @@ type KeyOutputList struct {
Keys []KeyOutput
}

var KeyGenCmd = &cmds.Command{
// KeyRenameOutput define the output type of keyRenameCmd
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 +160,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 +212,170 @@ 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 {
exist, err := ks.Has(newName)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

if exist {
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
27 changes: 27 additions & 0 deletions keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import (
)

type Keystore interface {
// Has return whether or not a key exist in the Keystore
Has(string) (bool, error)
// Put store a key in the Keystore
Put(string, ci.PrivKey) error
// Get retrieve a key from the Keystore
Get(string) (ci.PrivKey, error)
// Delete remove a key from the Keystore
Delete(string) error
// List return a list of key identifier
List() ([]string, error)
}

Expand Down Expand Up @@ -54,6 +60,24 @@ func NewFSKeystore(dir string) (*FSKeystore, error) {
return &FSKeystore{dir}, nil
}

// Has return whether or not a key exist in the Keystore
func (ks *FSKeystore) Has(name string) (bool, error) {
kp := filepath.Join(ks.dir, name)

_, err := os.Stat(kp)

if os.IsNotExist(err) {
return false, nil
}

if err != nil {
return false, err
}

return true, nil
}

// Put store a key in the Keystore
func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
Expand Down Expand Up @@ -87,6 +111,7 @@ func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
return nil
}

// Get retrieve a key from the Keystore
func (ks *FSKeystore) Get(name string) (ci.PrivKey, error) {
if err := validateName(name); err != nil {
return nil, err
Expand All @@ -105,6 +130,7 @@ func (ks *FSKeystore) Get(name string) (ci.PrivKey, error) {
return ci.UnmarshalPrivateKey(data)
}

// Delete remove a key from the Keystore
func (ks *FSKeystore) Delete(name string) error {
if err := validateName(name); err != nil {
return err
Expand All @@ -115,6 +141,7 @@ func (ks *FSKeystore) Delete(name string) error {
return os.Remove(kp)
}

// List return a list of key identifier
func (ks *FSKeystore) List() ([]string, error) {
dir, err := os.Open(ks.dir)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions keystore/keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ func TestKeystoreBasics(t *testing.T) {
t.Fatal(err)
}

exist, err := ks.Has("foo")
if !exist {
t.Fatal("should know it has a key named foo")
}
if err != nil {
t.Fatal(err)
}

exist, err = ks.Has("nonexistingkey")
if exist {
t.Fatal("should know it doesn't have a key named nonexistingkey")
}
if err != nil {
t.Fatal(err)
}

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

// Has return whether or not a key exist in the Keystore
func (mk *MemKeystore) Has(name string) (bool, error) {
_, ok := mk.keys[name]
return ok, nil
}

// Put store a key in the Keystore
func (mk *MemKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
Expand All @@ -24,6 +31,7 @@ func (mk *MemKeystore) Put(name string, k ci.PrivKey) error {
return nil
}

// Get retrieve a key from the Keystore
func (mk *MemKeystore) Get(name string) (ci.PrivKey, error) {
if err := validateName(name); err != nil {
return nil, err
Expand All @@ -37,6 +45,7 @@ func (mk *MemKeystore) Get(name string) (ci.PrivKey, error) {
return k, nil
}

// Delete remove a key from the Keystore
func (mk *MemKeystore) Delete(name string) error {
if err := validateName(name); err != nil {
return err
Expand All @@ -46,6 +55,7 @@ func (mk *MemKeystore) Delete(name string) error {
return nil
}

// List return a list of key identifier
func (mk *MemKeystore) List() ([]string, error) {
out := make([]string, 0, len(mk.keys))
for k, _ := range mk.keys {
Expand Down
17 changes: 17 additions & 0 deletions keystore/memkeystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ func TestMemKeyStoreBasics(t *testing.T) {
if err == nil {
t.Fatal("should not be able to overwrite key")
}

exist, err := ks.Has("foo")
if !exist {
t.Fatal("should know it has a key named foo")
}
if err != nil {
t.Fatal(err)
}

exist, err = ks.Has("nonexistingkey")
if exist {
t.Fatal("should know it doesn't have a key named nonexistingkey")
}
if err != nil {
t.Fatal(err)
}

if err := ks.Delete("bar"); err != nil {
t.Fatal(err)
}
Expand Down
31 changes: 31 additions & 0 deletions test/sharness/t0165-keystore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,37 @@ 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 rm can't remove self" '
test_must_fail ipfs key rm self 2>&1 | tee key_rm_out &&
grep -q "Error: cannot remove key with name" key_rm_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 rename 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