diff --git a/core/commands/keystore.go b/core/commands/keystore.go index 7ab6cd91495..522552815d9 100644 --- a/core/commands/keystore.go +++ b/core/commands/keystore.go @@ -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, }, } @@ -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", }, @@ -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", }, @@ -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() diff --git a/keystore/keystore.go b/keystore/keystore.go index b69e3e940b1..8424dbafa58 100644 --- a/keystore/keystore.go +++ b/keystore/keystore.go @@ -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) } @@ -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 @@ -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 @@ -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 @@ -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 { diff --git a/keystore/keystore_test.go b/keystore/keystore_test.go index 4840069bca0..53c30b0d7d1 100644 --- a/keystore/keystore_test.go +++ b/keystore/keystore_test.go @@ -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) } diff --git a/keystore/memkeystore.go b/keystore/memkeystore.go index ae45ecf2193..068c5e18917 100644 --- a/keystore/memkeystore.go +++ b/keystore/memkeystore.go @@ -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 @@ -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 @@ -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 @@ -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 { diff --git a/keystore/memkeystore_test.go b/keystore/memkeystore_test.go index 7f4362795d5..62533d54b92 100644 --- a/keystore/memkeystore_test.go +++ b/keystore/memkeystore_test.go @@ -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) } diff --git a/test/sharness/t0165-keystore.sh b/test/sharness/t0165-keystore.sh index ec9726a8a84..305ab1e38c1 100755 --- a/test/sharness/t0165-keystore.sh +++ b/test/sharness/t0165-keystore.sh @@ -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