提交 805b5040 作者: Jeromy

basic keystore implementation

License: MIT
Signed-off-by: 's avatarJeromy <why@ipfs.io>
上级 c12f9777
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):
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")
......@@ -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
......
......@@ -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,
......
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)
}
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 (
"sync"
"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"
"github.com/ipfs/go-ipfs/repo/common"
config "github.com/ipfs/go-ipfs/repo/config"
......@@ -95,6 +96,7 @@ type FSRepo struct {
lockfile io.Closer
config *config.Config
ds repo.Datastore
keystore keystore.Keystore
}
var _ repo.Repo = (*FSRepo)(nil)
......@@ -163,6 +165,10 @@ func open(repoPath string) (repo.Repo, error) {
return nil, err
}
if err := r.openKeystore(); err != nil {
return nil, err
}
keepLocked = true
return r, nil
}
......@@ -303,6 +309,10 @@ func APIAddr(repoPath string) (ma.Multiaddr, error) {
return ma.NewMultiaddr(s)
}
func (r *FSRepo) Keystore() keystore.Keystore {
return r.keystore
}
// SetAPIAddr writes the API Addr to the /api file.
func (r *FSRepo) SetAPIAddr(addr ma.Multiaddr) error {
f, err := os.Create(filepath.Join(r.path, apiFile))
......@@ -329,6 +339,18 @@ func (r *FSRepo) openConfig() error {
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.
func (r *FSRepo) openDatastore() error {
switch r.config.Datastore.Type {
......
......@@ -3,6 +3,7 @@ package repo
import (
"errors"
keystore "github.com/ipfs/go-ipfs/keystore"
"github.com/ipfs/go-ipfs/repo/config"
ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
......@@ -14,6 +15,7 @@ var errTODO = errors.New("TODO: mock repo")
type Mock struct {
C config.Config
D Datastore
K keystore.Keystore
}
func (m *Mock) Config() (*config.Config, error) {
......@@ -40,3 +42,5 @@ func (m *Mock) GetStorageUsage() (uint64, error) { return 0, nil }
func (m *Mock) Close() 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 (
"errors"
"io"
keystore "github.com/ipfs/go-ipfs/keystore"
config "github.com/ipfs/go-ipfs/repo/config"
ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
......@@ -24,6 +25,8 @@ type Repo interface {
Datastore() Datastore
GetStorageUsage() (uint64, error)
Keystore() keystore.Keystore
// SetAPIAddr sets the API address in the repo.
SetAPIAddr(addr ma.Multiaddr) error
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论