Skip to content

Commit

Permalink
basic keystore implementation
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Jeromy <why@ipfs.io>
  • Loading branch information
whyrusleeping committed Dec 6, 2016
1 parent c12f977 commit 805b504
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 1 deletion.
158 changes: 158 additions & 0 deletions core/commands/keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package commands

import (
"crypto/rand"
"fmt"
"io"
"sort"
"strings"

cmds "github.com/ipfs/go-ipfs/commands"

peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
)

var KeyCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create and manipulate keypairs",
},
Subcommands: map[string]*cmds.Command{
"gen": KeyGenCmd,
"list": KeyListCmd,
},
}

type KeyOutput struct {
Name string
Id string
}

var KeyGenCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new keypair",
},
Options: []cmds.Option{
cmds.StringOption("type", "t", "type of the key to create"),
cmds.IntOption("size", "s", "size of the key to generate"),
},
Arguments: []cmds.Argument{
cmds.StringArg("name", true, false, "name of key to create"),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

typ, f, err := req.Option("type").String()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

if !f {
res.SetError(fmt.Errorf("please specify a key type with --type"), cmds.ErrNormal)
return
}

size, sizefound, err := req.Option("size").Int()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

name := req.Arguments()[0]
if name == "self" {
res.SetError(fmt.Errorf("cannot create key with name 'self'"), cmds.ErrNormal)
return
}

var sk ci.PrivKey
var pk ci.PubKey

switch typ {
case "rsa":
if !sizefound {
res.SetError(fmt.Errorf("please specify a key size with --size"), cmds.ErrNormal)
return
}

priv, pub, err := ci.GenerateKeyPairWithReader(ci.RSA, size, rand.Reader)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

sk = priv
pk = pub
case "ed25519":
priv, pub, err := ci.GenerateEd25519Key(rand.Reader)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

sk = priv
pk = pub
default:
res.SetError(fmt.Errorf("unrecognized key type: %s", typ), cmds.ErrNormal)
return
}

err = n.Repo.Keystore().Put(name, sk)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

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

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

return strings.NewReader(k.Id), nil
},
},
Type: KeyOutput{},
}

var KeyListCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List all local keypairs",
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

keys, err := n.Repo.Keystore().List()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

sort.Strings(keys)
res.SetOutput(&stringList{keys})
},
Marshalers: cmds.MarshalerMap{
cmds.Text: stringListMarshaler,
},
Type: stringList{},
}
16 changes: 15 additions & 1 deletion core/commands/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Publish an <ipfs-path> to another public key (not implemented):
This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are
"ns", "us" (or "µs"), "ms", "s", "m", "h".`).Default("24h"),
cmds.StringOption("ttl", "Time duration this record should be cached for (caution: experimental)."),
cmds.StringOption("key", "k", "name of key to use").Default("self"),
},
Run: func(req cmds.Request, res cmds.Response) {
log.Debug("begin publish")
Expand Down Expand Up @@ -109,7 +110,20 @@ Publish an <ipfs-path> to another public key (not implemented):
ctx = context.WithValue(ctx, "ipns-publish-ttl", d)
}

output, err := publish(ctx, n, n.PrivateKey, path.Path(pstr), popts)
var k crypto.PrivKey
kname, _, _ := req.Option("key").String()
if kname == "self" {
k = n.PrivateKey
} else {
ksk, err := n.Repo.Keystore().Get(kname)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
k = ksk
}

output, err := publish(ctx, n, k, path.Path(pstr), popts)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
Expand Down
1 change: 1 addition & 0 deletions core/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ var rootSubcommands = map[string]*cmds.Command{
"files": files.FilesCmd,
"get": GetCmd,
"id": IDCmd,
"key": KeyCmd,
"log": LogCmd,
"ls": LsCmd,
"mount": MountCmd,
Expand Down
123 changes: 123 additions & 0 deletions keystore/keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package keystore

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
)

type Keystore interface {
Put(string, ci.PrivKey) error
Get(string) (ci.PrivKey, error)
Delete(string) error
List() ([]string, error)
}

var ErrNoSuchKey = fmt.Errorf("no key by the given name was found")
var ErrKeyExists = fmt.Errorf("key by that name already exists, refusing to overwrite")

type FSKeystore struct {
dir string
}

func validateName(name string) error {
if name == "" {
return fmt.Errorf("key names must be at least one character")
}

if strings.Contains(name, "/") {
return fmt.Errorf("key names may not contain slashes")
}

if strings.HasPrefix(name, ".") {
return fmt.Errorf("key names may not begin with a period")
}

return nil
}

func NewFSKeystore(dir string) (*FSKeystore, error) {
_, err := os.Stat(dir)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
if err := os.Mkdir(dir, 0700); err != nil {
return nil, err
}
}

return &FSKeystore{dir}, nil
}

func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
}

b, err := k.Bytes()
if err != nil {
return err
}

kp := filepath.Join(ks.dir, name)

_, err = os.Stat(kp)
if err == nil {
return ErrKeyExists
}

fi, err := os.Create(kp)
if err != nil {
return err
}
defer fi.Close()

_, err = fi.Write(b)
if err != nil {
return err
}

return nil
}

func (ks *FSKeystore) Get(name string) (ci.PrivKey, error) {
if err := validateName(name); err != nil {
return nil, err
}

kp := filepath.Join(ks.dir, name)

data, err := ioutil.ReadFile(kp)
if err != nil {
if os.IsNotExist(err) {
return nil, ErrNoSuchKey
}
return nil, err
}

return ci.UnmarshalPrivateKey(data)
}

func (ks *FSKeystore) Delete(name string) error {
if err := validateName(name); err != nil {
return err
}

kp := filepath.Join(ks.dir, name)

return os.Remove(kp)
}

func (ks *FSKeystore) List() ([]string, error) {
dir, err := os.Open(ks.dir)
if err != nil {
return nil, err
}

return dir.Readdirnames(0)
}
55 changes: 55 additions & 0 deletions keystore/memkeystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package keystore

import ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"

type MemKeystore struct {
keys map[string]ci.PrivKey
}

func NewMemKeystore() *MemKeystore {
return &MemKeystore{make(map[string]ci.PrivKey)}
}

func (mk *MemKeystore) Put(name string, k ci.PrivKey) error {
if err := validateName(name); err != nil {
return err
}

_, ok := mk.keys[name]
if ok {
return ErrKeyExists
}

mk.keys[name] = k
return nil
}

func (mk *MemKeystore) Get(name string) (ci.PrivKey, error) {
if err := validateName(name); err != nil {
return nil, err
}

k, ok := mk.keys[name]
if !ok {
return nil, ErrNoSuchKey
}

return k, nil
}

func (mk *MemKeystore) Delete(name string) error {
if err := validateName(name); err != nil {
return err
}

delete(mk.keys, name)
return nil
}

func (mk *MemKeystore) List() ([]string, error) {
out := make([]string, 0, len(mk.keys))
for k, _ := range mk.keys {
out = append(out, k)
}
return out, nil
}
Loading

0 comments on commit 805b504

Please sign in to comment.