-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
basic keystore implementation #3472
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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{}, | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought you wanted to use multicodec so we can migrate much easier? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, it won't make migrating easier at all. (it will make them hard since i'll have to decode multicodecs in the migrations code :P ) |
||
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) | ||
} |
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be safe I would use
os.IsExist
and change it toErrKeyExists
then, else passthrough the error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you use
os.IsExist
here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, IsNotExist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah! okay, i see what you want