-
-
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
Key import and export cli commands #7546
Changes from all commits
a065640
c7657cb
3bdc907
a70d04b
356ca3a
7f15be2
fab3a35
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 |
---|---|---|
@@ -1,13 +1,21 @@ | ||
package commands | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"text/tabwriter" | ||
|
||
cmds "github.com/ipfs/go-ipfs-cmds" | ||
cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" | ||
"github.com/ipfs/go-ipfs/core/commands/e" | ||
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo" | ||
options "github.com/ipfs/interface-go-ipfs-core/options" | ||
"github.com/libp2p/go-libp2p-core/crypto" | ||
peer "github.com/libp2p/go-libp2p-core/peer" | ||
mbase "github.com/multiformats/go-multibase" | ||
) | ||
|
@@ -31,6 +39,8 @@ publish'. | |
}, | ||
Subcommands: map[string]*cmds.Command{ | ||
"gen": keyGenCmd, | ||
"export": keyExportCmd, | ||
"import": keyImportCmd, | ||
"list": keyListCmd, | ||
"rename": keyRenameCmd, | ||
"rm": keyRmCmd, | ||
|
@@ -143,6 +153,164 @@ func formatID(id peer.ID, formatLabel string) string { | |
} | ||
} | ||
|
||
var keyExportCmd = &cmds.Command{ | ||
Helptext: cmds.HelpText{ | ||
Tagline: "Export a keypair", | ||
ShortDescription: ` | ||
Exports a named libp2p key to disk. | ||
|
||
By default, the output will be stored at './<key-name>.key', but an alternate | ||
path can be specified with '--output=<path>' or '-o=<path>'. | ||
`, | ||
}, | ||
Arguments: []cmds.Argument{ | ||
cmds.StringArg("name", true, false, "name of key to export").EnableStdin(), | ||
}, | ||
Options: []cmds.Option{ | ||
cmds.StringOption(outputOptionName, "o", "The path where the output should be stored."), | ||
}, | ||
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { | ||
name := req.Arguments[0] | ||
|
||
if name == "self" { | ||
return fmt.Errorf("cannot export key with name 'self'") | ||
} | ||
|
||
cfgRoot, err := cmdenv.GetConfigRoot(env) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
r, err := fsrepo.Open(cfgRoot) | ||
if err != nil { | ||
return err | ||
} | ||
defer r.Close() | ||
|
||
sk, err := r.Keystore().Get(name) | ||
if err != nil { | ||
return fmt.Errorf("key with name '%s' doesn't exist", name) | ||
} | ||
|
||
encoded, err := crypto.MarshalPrivateKey(sk) | ||
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. @rendaw I switched us from using base58btc encoded strings to just sending the raw bytes over the wire and working with files. While in the future it's pretty easy for us to add new base encodings to import/export (e.g. --type=raw/multibase) it's actually pretty painful for us to allow for imports to be strings OR files. I went with files over strings here as it seems likely to me that people will want to ultimately store these blobs in files. Additionally, the keystore currently utilizes files so people are likely already used to backing up keys this way. |
||
if err != nil { | ||
return err | ||
} | ||
|
||
return res.Emit(bytes.NewReader(encoded)) | ||
}, | ||
PostRun: cmds.PostRunMap{ | ||
cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error { | ||
req := res.Request() | ||
|
||
v, err := res.Next() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
outReader, ok := v.(io.Reader) | ||
if !ok { | ||
return e.New(e.TypeErr(outReader, v)) | ||
} | ||
|
||
outPath, _ := req.Options[outputOptionName].(string) | ||
if outPath == "" { | ||
trimmed := strings.TrimRight(fmt.Sprintf("%s.key", req.Arguments[0]), "/") | ||
_, outPath = filepath.Split(trimmed) | ||
outPath = filepath.Clean(outPath) | ||
} | ||
|
||
// create file | ||
file, err := os.Create(outPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
_, err = io.Copy(file, outReader) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}, | ||
}, | ||
} | ||
|
||
var keyImportCmd = &cmds.Command{ | ||
Helptext: cmds.HelpText{ | ||
Tagline: "Import a key and prints imported key id", | ||
}, | ||
Options: []cmds.Option{ | ||
cmds.StringOption(keyFormatOptionName, "f", "output format: b58mh or b36cid").WithDefault("b58mh"), | ||
}, | ||
Arguments: []cmds.Argument{ | ||
cmds.StringArg("name", true, false, "name to associate with key in keychain"), | ||
cmds.FileArg("key", true, false, "key provided by generate or export"), | ||
}, | ||
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { | ||
name := req.Arguments[0] | ||
|
||
if name == "self" { | ||
return fmt.Errorf("cannot import key with name 'self'") | ||
} | ||
|
||
file, err := cmdenv.GetFileArg(req.Files.Entries()) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
data, err := ioutil.ReadAll(file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
sk, err := crypto.UnmarshalPrivateKey(data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cfgRoot, err := cmdenv.GetConfigRoot(env) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
r, err := fsrepo.Open(cfgRoot) | ||
if err != nil { | ||
return err | ||
} | ||
defer r.Close() | ||
|
||
_, err = r.Keystore().Get(name) | ||
if err == nil { | ||
return fmt.Errorf("key with name '%s' already exists", name) | ||
} | ||
|
||
err = r.Keystore().Put(name, sk) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
pid, err := peer.IDFromPrivateKey(sk) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return cmds.EmitOnce(res, &KeyOutput{ | ||
Name: name, | ||
Id: formatID(pid, req.Options[keyFormatOptionName].(string)), | ||
}) | ||
}, | ||
Encoders: cmds.EncoderMap{ | ||
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ko *KeyOutput) error { | ||
_, err := w.Write([]byte(ko.Id + "\n")) | ||
return err | ||
}), | ||
}, | ||
Type: KeyOutput{}, | ||
} | ||
|
||
var keyListCmd = &cmds.Command{ | ||
Helptext: cmds.HelpText{ | ||
Tagline: "List all local keypairs", | ||
|
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.
Hm. Is there any reason not to allow this?
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.
I guess not, but we have to the keys live in different places and it's a little messier. Let's add it in a followup PR