提交 60371a5b 作者: Jeromy Johnson 提交者: GitHub

Merge pull request #3472 from ipfs/feat/keystore

basic keystore implementation
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{},
}
...@@ -56,6 +56,7 @@ Publish an <ipfs-path> to another public key (not implemented): ...@@ -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 This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are
"ns", "us" (or "µs"), "ms", "s", "m", "h".`).Default("24h"), "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("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) { Run: func(req cmds.Request, res cmds.Response) {
log.Debug("begin publish") log.Debug("begin publish")
...@@ -109,7 +110,14 @@ Publish an <ipfs-path> to another public key (not implemented): ...@@ -109,7 +110,14 @@ Publish an <ipfs-path> to another public key (not implemented):
ctx = context.WithValue(ctx, "ipns-publish-ttl", d) ctx = context.WithValue(ctx, "ipns-publish-ttl", d)
} }
output, err := publish(ctx, n, n.PrivateKey, path.Path(pstr), popts) kname, _, _ := req.Option("key").String()
k, err := n.GetKey(kname)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
output, err := publish(ctx, n, k, path.Path(pstr), popts)
if err != nil { if err != nil {
res.SetError(err, cmds.ErrNormal) res.SetError(err, cmds.ErrNormal)
return return
......
...@@ -103,6 +103,7 @@ var rootSubcommands = map[string]*cmds.Command{ ...@@ -103,6 +103,7 @@ var rootSubcommands = map[string]*cmds.Command{
"files": files.FilesCmd, "files": files.FilesCmd,
"get": GetCmd, "get": GetCmd,
"id": IDCmd, "id": IDCmd,
"key": KeyCmd,
"log": LogCmd, "log": LogCmd,
"ls": LsCmd, "ls": LsCmd,
"mount": MountCmd, "mount": MountCmd,
......
...@@ -514,6 +514,14 @@ func (n *IpfsNode) loadID() error { ...@@ -514,6 +514,14 @@ func (n *IpfsNode) loadID() error {
return nil return nil
} }
func (n *IpfsNode) GetKey(name string) (ic.PrivKey, error) {
if name == "self" {
return n.PrivateKey, nil
} else {
return n.Repo.Keystore().Get(name)
}
}
func (n *IpfsNode) LoadPrivateKey() error { func (n *IpfsNode) LoadPrivateKey() error {
if n.Identity == "" || n.Peerstore == nil { if n.Identity == "" || n.Peerstore == nil {
return errors.New("loaded private key out of order.") return errors.New("loaded private key out of order.")
......
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
} else if !os.IsNotExist(err) {
return err
}
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)
}
package keystore
import (
"fmt"
"io/ioutil"
"math/rand"
"sort"
"testing"
ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
)
type rr struct{}
func (rr rr) Read(b []byte) (int, error) {
return rand.Read(b)
}
func privKeyOrFatal(t *testing.T) ci.PrivKey {
priv, _, err := ci.GenerateEd25519Key(rr{})
if err != nil {
t.Fatal(err)
}
return priv
}
func TestKeystoreBasics(t *testing.T) {
tdir, err := ioutil.TempDir("", "keystore-test")
if err != nil {
t.Fatal(err)
}
ks, err := NewFSKeystore(tdir)
if err != nil {
t.Fatal(err)
}
l, err := ks.List()
if err != nil {
t.Fatal(err)
}
if len(l) != 0 {
t.Fatal("expected no keys")
}
k1 := privKeyOrFatal(t)
k2 := privKeyOrFatal(t)
k3 := privKeyOrFatal(t)
k4 := privKeyOrFatal(t)
err = ks.Put("foo", k1)
if err != nil {
t.Fatal(err)
}
err = ks.Put("bar", k2)
if err != nil {
t.Fatal(err)
}
l, err = ks.List()
if err != nil {
t.Fatal(err)
}
sort.Strings(l)
if l[0] != "bar" || l[1] != "foo" {
t.Fatal("wrong entries listed")
}
if err := assertDirContents(tdir, []string{"foo", "bar"}); err != nil {
t.Fatal(err)
}
err = ks.Put("foo", k3)
if err == nil {
t.Fatal("should not be able to overwrite key")
}
if err := assertDirContents(tdir, []string{"foo", "bar"}); err != nil {
t.Fatal(err)
}
if err := ks.Delete("bar"); err != nil {
t.Fatal(err)
}
if err := assertDirContents(tdir, []string{"foo"}); err != nil {
t.Fatal(err)
}
if err := ks.Put("beep", k3); err != nil {
t.Fatal(err)
}
if err := ks.Put("boop", k4); err != nil {
t.Fatal(err)
}
if err := assertDirContents(tdir, []string{"foo", "beep", "boop"}); err != nil {
t.Fatal(err)
}
if err := assertGetKey(ks, "foo", k1); err != nil {
t.Fatal(err)
}
if err := assertGetKey(ks, "beep", k3); err != nil {
t.Fatal(err)
}
if err := assertGetKey(ks, "boop", k4); err != nil {
t.Fatal(err)
}
if err := ks.Put("..///foo/", k1); err == nil {
t.Fatal("shouldnt be able to put a poorly named key")
}
if err := ks.Put("", k1); err == nil {
t.Fatal("shouldnt be able to put a key with no name")
}
if err := ks.Put(".foo", k1); err == nil {
t.Fatal("shouldnt be able to put a key with a 'hidden' name")
}
}
func TestMakeKeystoreNoDir(t *testing.T) {
_, err := NewFSKeystore("/this/is/not/a/real/dir")
if err == nil {
t.Fatal("shouldnt be able to make a keystore in a nonexistant directory")
}
}
func assertGetKey(ks Keystore, name string, exp ci.PrivKey) error {
out_k, err := ks.Get(name)
if err != nil {
return err
}
if !out_k.Equals(exp) {
return fmt.Errorf("key we got out didnt match expectation")
}
return nil
}
func assertDirContents(dir string, exp []string) error {
finfos, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
if len(finfos) != len(exp) {
return fmt.Errorf("Expected %d directory entries", len(exp))
}
var names []string
for _, fi := range finfos {
names = append(names, fi.Name())
}
sort.Strings(names)
sort.Strings(exp)
if len(names) != len(exp) {
return fmt.Errorf("directory had wrong number of entries in it")
}
for i, v := range names {
if v != exp[i] {
return fmt.Errorf("had wrong entry in directory")
}
}
return nil
}
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
}
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"sync" "sync"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/mitchellh/go-homedir" "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/mitchellh/go-homedir"
keystore "github.com/ipfs/go-ipfs/keystore"
repo "github.com/ipfs/go-ipfs/repo" repo "github.com/ipfs/go-ipfs/repo"
"github.com/ipfs/go-ipfs/repo/common" "github.com/ipfs/go-ipfs/repo/common"
config "github.com/ipfs/go-ipfs/repo/config" config "github.com/ipfs/go-ipfs/repo/config"
...@@ -95,6 +96,7 @@ type FSRepo struct { ...@@ -95,6 +96,7 @@ type FSRepo struct {
lockfile io.Closer lockfile io.Closer
config *config.Config config *config.Config
ds repo.Datastore ds repo.Datastore
keystore keystore.Keystore
} }
var _ repo.Repo = (*FSRepo)(nil) var _ repo.Repo = (*FSRepo)(nil)
...@@ -163,6 +165,10 @@ func open(repoPath string) (repo.Repo, error) { ...@@ -163,6 +165,10 @@ func open(repoPath string) (repo.Repo, error) {
return nil, err return nil, err
} }
if err := r.openKeystore(); err != nil {
return nil, err
}
keepLocked = true keepLocked = true
return r, nil return r, nil
} }
...@@ -303,6 +309,10 @@ func APIAddr(repoPath string) (ma.Multiaddr, error) { ...@@ -303,6 +309,10 @@ func APIAddr(repoPath string) (ma.Multiaddr, error) {
return ma.NewMultiaddr(s) return ma.NewMultiaddr(s)
} }
func (r *FSRepo) Keystore() keystore.Keystore {
return r.keystore
}
// SetAPIAddr writes the API Addr to the /api file. // SetAPIAddr writes the API Addr to the /api file.
func (r *FSRepo) SetAPIAddr(addr ma.Multiaddr) error { func (r *FSRepo) SetAPIAddr(addr ma.Multiaddr) error {
f, err := os.Create(filepath.Join(r.path, apiFile)) f, err := os.Create(filepath.Join(r.path, apiFile))
...@@ -329,6 +339,18 @@ func (r *FSRepo) openConfig() error { ...@@ -329,6 +339,18 @@ func (r *FSRepo) openConfig() error {
return nil return nil
} }
func (r *FSRepo) openKeystore() error {
ksp := filepath.Join(r.path, "keystore")
ks, err := keystore.NewFSKeystore(ksp)
if err != nil {
return err
}
r.keystore = ks
return nil
}
// openDatastore returns an error if the config file is not present. // openDatastore returns an error if the config file is not present.
func (r *FSRepo) openDatastore() error { func (r *FSRepo) openDatastore() error {
switch r.config.Datastore.Type { switch r.config.Datastore.Type {
......
...@@ -3,6 +3,7 @@ package repo ...@@ -3,6 +3,7 @@ package repo
import ( import (
"errors" "errors"
keystore "github.com/ipfs/go-ipfs/keystore"
"github.com/ipfs/go-ipfs/repo/config" "github.com/ipfs/go-ipfs/repo/config"
ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr" ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
...@@ -14,6 +15,7 @@ var errTODO = errors.New("TODO: mock repo") ...@@ -14,6 +15,7 @@ var errTODO = errors.New("TODO: mock repo")
type Mock struct { type Mock struct {
C config.Config C config.Config
D Datastore D Datastore
K keystore.Keystore
} }
func (m *Mock) Config() (*config.Config, error) { func (m *Mock) Config() (*config.Config, error) {
...@@ -40,3 +42,5 @@ func (m *Mock) GetStorageUsage() (uint64, error) { return 0, nil } ...@@ -40,3 +42,5 @@ func (m *Mock) GetStorageUsage() (uint64, error) { return 0, nil }
func (m *Mock) Close() error { return errTODO } func (m *Mock) Close() error { return errTODO }
func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO } func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO }
func (m *Mock) Keystore() keystore.Keystore { return nil }
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"io" "io"
keystore "github.com/ipfs/go-ipfs/keystore"
config "github.com/ipfs/go-ipfs/repo/config" config "github.com/ipfs/go-ipfs/repo/config"
ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore" ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
...@@ -24,6 +25,8 @@ type Repo interface { ...@@ -24,6 +25,8 @@ type Repo interface {
Datastore() Datastore Datastore() Datastore
GetStorageUsage() (uint64, error) GetStorageUsage() (uint64, error)
Keystore() keystore.Keystore
// SetAPIAddr sets the API address in the repo. // SetAPIAddr sets the API address in the repo.
SetAPIAddr(addr ma.Multiaddr) error SetAPIAddr(addr ma.Multiaddr) error
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论