@cryptotaxi247 / kubo / commits / 805b50404

basic keystore implementation

License: MIT Signed-off-by: Jeromy <why@ipfs.io>

Jeromy committed Nov 30, 2016 at 10:18 UTC 805b504043ccf832056138cc32e38406e9a3d2e0
8 files changed +381 -1
core/commands/keystore.go new
+158
@@ -0,0 +1,158 @@
1 +package commands
2 +
3 +import (
4 + "crypto/rand"
5 + "fmt"
6 + "io"
7 + "sort"
8 + "strings"
9 +
10 + cmds "github.com/ipfs/go-ipfs/commands"
11 +
12 + peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
13 + ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
14 +)
15 +
16 +var KeyCmd = &cmds.Command{
17 + Helptext: cmds.HelpText{
18 + Tagline: "Create and manipulate keypairs",
19 + },
20 + Subcommands: map[string]*cmds.Command{
21 + "gen": KeyGenCmd,
22 + "list": KeyListCmd,
23 + },
24 +}
25 +
26 +type KeyOutput struct {
27 + Name string
28 + Id string
29 +}
30 +
31 +var KeyGenCmd = &cmds.Command{
32 + Helptext: cmds.HelpText{
33 + Tagline: "Create a new keypair",
34 + },
35 + Options: []cmds.Option{
36 + cmds.StringOption("type", "t", "type of the key to create"),
37 + cmds.IntOption("size", "s", "size of the key to generate"),
38 + },
39 + Arguments: []cmds.Argument{
40 + cmds.StringArg("name", true, false, "name of key to create"),
41 + },
42 + Run: func(req cmds.Request, res cmds.Response) {
43 + n, err := req.InvocContext().GetNode()
44 + if err != nil {
45 + res.SetError(err, cmds.ErrNormal)
46 + return
47 + }
48 +
49 + typ, f, err := req.Option("type").String()
50 + if err != nil {
51 + res.SetError(err, cmds.ErrNormal)
52 + return
53 + }
54 +
55 + if !f {
56 + res.SetError(fmt.Errorf("please specify a key type with --type"), cmds.ErrNormal)
57 + return
58 + }
59 +
60 + size, sizefound, err := req.Option("size").Int()
61 + if err != nil {
62 + res.SetError(err, cmds.ErrNormal)
63 + return
64 + }
65 +
66 + name := req.Arguments()[0]
67 + if name == "self" {
68 + res.SetError(fmt.Errorf("cannot create key with name 'self'"), cmds.ErrNormal)
69 + return
70 + }
71 +
72 + var sk ci.PrivKey
73 + var pk ci.PubKey
74 +
75 + switch typ {
76 + case "rsa":
77 + if !sizefound {
78 + res.SetError(fmt.Errorf("please specify a key size with --size"), cmds.ErrNormal)
79 + return
80 + }
81 +
82 + priv, pub, err := ci.GenerateKeyPairWithReader(ci.RSA, size, rand.Reader)
83 + if err != nil {
84 + res.SetError(err, cmds.ErrNormal)
85 + return
86 + }
87 +
88 + sk = priv
89 + pk = pub
90 + case "ed25519":
91 + priv, pub, err := ci.GenerateEd25519Key(rand.Reader)
92 + if err != nil {
93 + res.SetError(err, cmds.ErrNormal)
94 + return
95 + }
96 +
97 + sk = priv
98 + pk = pub
99 + default:
100 + res.SetError(fmt.Errorf("unrecognized key type: %s", typ), cmds.ErrNormal)
101 + return
102 + }
103 +
104 + err = n.Repo.Keystore().Put(name, sk)
105 + if err != nil {
106 + res.SetError(err, cmds.ErrNormal)
107 + return
108 + }
109 +
110 + pid, err := peer.IDFromPublicKey(pk)
111 + if err != nil {
112 + res.SetError(err, cmds.ErrNormal)
113 + return
114 + }
115 +
116 + res.SetOutput(&KeyOutput{
117 + Name: name,
118 + Id: pid.Pretty(),
119 + })
120 + },
121 + Marshalers: cmds.MarshalerMap{
122 + cmds.Text: func(res cmds.Response) (io.Reader, error) {
123 + k, ok := res.Output().(*KeyOutput)
124 + if !ok {
125 + return nil, fmt.Errorf("expected a KeyOutput as command result")
126 + }
127 +
128 + return strings.NewReader(k.Id), nil
129 + },
130 + },
131 + Type: KeyOutput{},
132 +}
133 +
134 +var KeyListCmd = &cmds.Command{
135 + Helptext: cmds.HelpText{
136 + Tagline: "List all local keypairs",
137 + },
138 + Run: func(req cmds.Request, res cmds.Response) {
139 + n, err := req.InvocContext().GetNode()
140 + if err != nil {
141 + res.SetError(err, cmds.ErrNormal)
142 + return
143 + }
144 +
145 + keys, err := n.Repo.Keystore().List()
146 + if err != nil {
147 + res.SetError(err, cmds.ErrNormal)
148 + return
149 + }
150 +
151 + sort.Strings(keys)
152 + res.SetOutput(&stringList{keys})
153 + },
154 + Marshalers: cmds.MarshalerMap{
155 + cmds.Text: stringListMarshaler,
156 + },
157 + Type: stringList{},
158 +}
core/commands/publish.go
+15 -1
@@ -56,6 +56,7 @@ Publish an <ipfs-path> to another public key (not implemented):
56 This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are
57 "ns", "us" (or "µs"), "ms", "s", "m", "h".`).Default("24h"),
58 cmds.StringOption("ttl", "Time duration this record should be cached for (caution: experimental)."),
59 + cmds.StringOption("key", "k", "name of key to use").Default("self"),
60 },
61 Run: func(req cmds.Request, res cmds.Response) {
62 log.Debug("begin publish")
@@ -109,7 +110,20 @@ Publish an <ipfs-path> to another public key (not implemented):
110 ctx = context.WithValue(ctx, "ipns-publish-ttl", d)
111 }
112
112 - output, err := publish(ctx, n, n.PrivateKey, path.Path(pstr), popts)
113 + var k crypto.PrivKey
114 + kname, _, _ := req.Option("key").String()
115 + if kname == "self" {
116 + k = n.PrivateKey
117 + } else {
118 + ksk, err := n.Repo.Keystore().Get(kname)
119 + if err != nil {
120 + res.SetError(err, cmds.ErrNormal)
121 + return
122 + }
123 + k = ksk
124 + }
125 +
126 + output, err := publish(ctx, n, k, path.Path(pstr), popts)
127 if err != nil {
128 res.SetError(err, cmds.ErrNormal)
129 return
core/commands/root.go
+1
@@ -103,6 +103,7 @@ var rootSubcommands = map[string]*cmds.Command{
103 "files": files.FilesCmd,
104 "get": GetCmd,
105 "id": IDCmd,
106 + "key": KeyCmd,
107 "log": LogCmd,
108 "ls": LsCmd,
109 "mount": MountCmd,
keystore/keystore.go new
+123
@@ -0,0 +1,123 @@
1 +package keystore
2 +
3 +import (
4 + "fmt"
5 + "io/ioutil"
6 + "os"
7 + "path/filepath"
8 + "strings"
9 +
10 + ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
11 +)
12 +
13 +type Keystore interface {
14 + Put(string, ci.PrivKey) error
15 + Get(string) (ci.PrivKey, error)
16 + Delete(string) error
17 + List() ([]string, error)
18 +}
19 +
20 +var ErrNoSuchKey = fmt.Errorf("no key by the given name was found")
21 +var ErrKeyExists = fmt.Errorf("key by that name already exists, refusing to overwrite")
22 +
23 +type FSKeystore struct {
24 + dir string
25 +}
26 +
27 +func validateName(name string) error {
28 + if name == "" {
29 + return fmt.Errorf("key names must be at least one character")
30 + }
31 +
32 + if strings.Contains(name, "/") {
33 + return fmt.Errorf("key names may not contain slashes")
34 + }
35 +
36 + if strings.HasPrefix(name, ".") {
37 + return fmt.Errorf("key names may not begin with a period")
38 + }
39 +
40 + return nil
41 +}
42 +
43 +func NewFSKeystore(dir string) (*FSKeystore, error) {
44 + _, err := os.Stat(dir)
45 + if err != nil {
46 + if !os.IsNotExist(err) {
47 + return nil, err
48 + }
49 + if err := os.Mkdir(dir, 0700); err != nil {
50 + return nil, err
51 + }
52 + }
53 +
54 + return &FSKeystore{dir}, nil
55 +}
56 +
57 +func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
58 + if err := validateName(name); err != nil {
59 + return err
60 + }
61 +
62 + b, err := k.Bytes()
63 + if err != nil {
64 + return err
65 + }
66 +
67 + kp := filepath.Join(ks.dir, name)
68 +
69 + _, err = os.Stat(kp)
70 + if err == nil {
71 + return ErrKeyExists
72 + }
73 +
74 + fi, err := os.Create(kp)
75 + if err != nil {
76 + return err
77 + }
78 + defer fi.Close()
79 +
80 + _, err = fi.Write(b)
81 + if err != nil {
82 + return err
83 + }
84 +
85 + return nil
86 +}
87 +
88 +func (ks *FSKeystore) Get(name string) (ci.PrivKey, error) {
89 + if err := validateName(name); err != nil {
90 + return nil, err
91 + }
92 +
93 + kp := filepath.Join(ks.dir, name)
94 +
95 + data, err := ioutil.ReadFile(kp)
96 + if err != nil {
97 + if os.IsNotExist(err) {
98 + return nil, ErrNoSuchKey
99 + }
100 + return nil, err
101 + }
102 +
103 + return ci.UnmarshalPrivateKey(data)
104 +}
105 +
106 +func (ks *FSKeystore) Delete(name string) error {
107 + if err := validateName(name); err != nil {
108 + return err
109 + }
110 +
111 + kp := filepath.Join(ks.dir, name)
112 +
113 + return os.Remove(kp)
114 +}
115 +
116 +func (ks *FSKeystore) List() ([]string, error) {
117 + dir, err := os.Open(ks.dir)
118 + if err != nil {
119 + return nil, err
120 + }
121 +
122 + return dir.Readdirnames(0)
123 +}
keystore/memkeystore.go new
+55
@@ -0,0 +1,55 @@
1 +package keystore
2 +
3 +import ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
4 +
5 +type MemKeystore struct {
6 + keys map[string]ci.PrivKey
7 +}
8 +
9 +func NewMemKeystore() *MemKeystore {
10 + return &MemKeystore{make(map[string]ci.PrivKey)}
11 +}
12 +
13 +func (mk *MemKeystore) Put(name string, k ci.PrivKey) error {
14 + if err := validateName(name); err != nil {
15 + return err
16 + }
17 +
18 + _, ok := mk.keys[name]
19 + if ok {
20 + return ErrKeyExists
21 + }
22 +
23 + mk.keys[name] = k
24 + return nil
25 +}
26 +
27 +func (mk *MemKeystore) Get(name string) (ci.PrivKey, error) {
28 + if err := validateName(name); err != nil {
29 + return nil, err
30 + }
31 +
32 + k, ok := mk.keys[name]
33 + if !ok {
34 + return nil, ErrNoSuchKey
35 + }
36 +
37 + return k, nil
38 +}
39 +
40 +func (mk *MemKeystore) Delete(name string) error {
41 + if err := validateName(name); err != nil {
42 + return err
43 + }
44 +
45 + delete(mk.keys, name)
46 + return nil
47 +}
48 +
49 +func (mk *MemKeystore) List() ([]string, error) {
50 + out := make([]string, 0, len(mk.keys))
51 + for k, _ := range mk.keys {
52 + out = append(out, k)
53 + }
54 + return out, nil
55 +}
repo/fsrepo/fsrepo.go
+22
@@ -11,6 +11,7 @@ import (
11 "sync"
12
13 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/mitchellh/go-homedir"
14 + keystore "github.com/ipfs/go-ipfs/keystore"
15 repo "github.com/ipfs/go-ipfs/repo"
16 "github.com/ipfs/go-ipfs/repo/common"
17 config "github.com/ipfs/go-ipfs/repo/config"
@@ -95,6 +96,7 @@ type FSRepo struct {
96 lockfile io.Closer
97 config *config.Config
98 ds repo.Datastore
99 + keystore keystore.Keystore
100 }
101
102 var _ repo.Repo = (*FSRepo)(nil)
@@ -163,6 +165,10 @@ func open(repoPath string) (repo.Repo, error) {
165 return nil, err
166 }
167
168 + if err := r.openKeystore(); err != nil {
169 + return nil, err
170 + }
171 +
172 keepLocked = true
173 return r, nil
174 }
@@ -303,6 +309,10 @@ func APIAddr(repoPath string) (ma.Multiaddr, error) {
309 return ma.NewMultiaddr(s)
310 }
311
312 +func (r *FSRepo) Keystore() keystore.Keystore {
313 + return r.keystore
314 +}
315 +
316 // SetAPIAddr writes the API Addr to the /api file.
317 func (r *FSRepo) SetAPIAddr(addr ma.Multiaddr) error {
318 f, err := os.Create(filepath.Join(r.path, apiFile))
@@ -329,6 +339,18 @@ func (r *FSRepo) openConfig() error {
339 return nil
340 }
341
342 +func (r *FSRepo) openKeystore() error {
343 + ksp := filepath.Join(r.path, "keystore")
344 + ks, err := keystore.NewFSKeystore(ksp)
345 + if err != nil {
346 + return err
347 + }
348 +
349 + r.keystore = ks
350 +
351 + return nil
352 +}
353 +
354 // openDatastore returns an error if the config file is not present.
355 func (r *FSRepo) openDatastore() error {
356 switch r.config.Datastore.Type {
repo/mock.go
+4
@@ -3,6 +3,7 @@ package repo
3 import (
4 "errors"
5
6 + keystore "github.com/ipfs/go-ipfs/keystore"
7 "github.com/ipfs/go-ipfs/repo/config"
8
9 ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
@@ -14,6 +15,7 @@ var errTODO = errors.New("TODO: mock repo")
15 type Mock struct {
16 C config.Config
17 D Datastore
18 + K keystore.Keystore
19 }
20
21 func (m *Mock) Config() (*config.Config, error) {
@@ -40,3 +42,5 @@ func (m *Mock) GetStorageUsage() (uint64, error) { return 0, nil }
42 func (m *Mock) Close() error { return errTODO }
43
44 func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO }
45 +
46 +func (m *Mock) Keystore() keystore.Keystore { return nil }
repo/repo.go
+3
@@ -4,6 +4,7 @@ import (
4 "errors"
5 "io"
6
7 + keystore "github.com/ipfs/go-ipfs/keystore"
8 config "github.com/ipfs/go-ipfs/repo/config"
9
10 ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
@@ -24,6 +25,8 @@ type Repo interface {
25 Datastore() Datastore
26 GetStorageUsage() (uint64, error)
27
28 + Keystore() keystore.Keystore
29 +
30 // SetAPIAddr sets the API address in the repo.
31 SetAPIAddr(addr ma.Multiaddr) error
32