Skip to content

Commit

Permalink
Merge pull request #7546 from rendaw/master
Browse files Browse the repository at this point in the history
Key import and export cli commands
  • Loading branch information
aschmahmann committed Aug 3, 2020
2 parents 5b28704 + fab3a35 commit dfb8141
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 14 deletions.
2 changes: 2 additions & 0 deletions core/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func TestCommands(t *testing.T) {
"/id",
"/key",
"/key/gen",
"/key/export",
"/key/import",
"/key/list",
"/key/rename",
"/key/rm",
Expand Down
168 changes: 168 additions & 0 deletions core/commands/keystore.go
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"
)
Expand All @@ -31,6 +39,8 @@ publish'.
},
Subcommands: map[string]*cmds.Command{
"gen": keyGenCmd,
"export": keyExportCmd,
"import": keyImportCmd,
"list": keyListCmd,
"rename": keyRenameCmd,
"rm": keyRmCmd,
Expand Down Expand Up @@ -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)
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",
Expand Down
102 changes: 88 additions & 14 deletions test/sharness/t0165-keystore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ PEERID=$(ipfs key gen -f=b58mh --type=rsa --size=2048 key_rsa) &&
test_check_rsa2048_b58mh_peerid $PEERID
'

test_expect_success "test RSA key sk export format" '
ipfs key export key_rsa &&
test_check_rsa2048_sk key_rsa.key &&
rm key_rsa.key
'

test_expect_success "test RSA key B36CID multihash format" '
PEERID=$(ipfs key list -f=b36cid -l | grep key_rsa | head -n 1 | cut -d " " -f1) &&
test_check_rsa2048_b36cid_peerid $PEERID &&
Expand All @@ -28,6 +34,12 @@ PEERID=$(ipfs key gen -f=b36cid --type=ed25519 key_ed25519) &&
test_check_ed25519_b36cid_peerid $PEERID
'

test_expect_success "test ED25519 key sk export format" '
ipfs key export key_ed25519 &&
test_check_ed25519_sk key_ed25519.key &&
rm key_ed25519.key
'

test_expect_success "test ED25519 key B36CID multihash format" '
PEERID=$(ipfs key list -f=b36cid -l | grep key_ed25519 | head -n 1 | cut -d " " -f1) &&
test_check_ed25519_b36cid_peerid $PEERID &&
Expand All @@ -37,19 +49,63 @@ ipfs key rm key_ed25519


test_expect_success "create a new rsa key" '
rsahash=$(ipfs key gen -f=b58mh foobarsa --type=rsa --size=2048)
rsahash=$(ipfs key gen -f=b58mh generated_rsa_key --type=rsa --size=2048)
echo $rsahash > rsa_key_id
'

test_expect_success "create a new ed25519 key" '
edhash=$(ipfs key gen -f=b58mh bazed --type=ed25519)
edhash=$(ipfs key gen -f=b58mh generated_ed25519_key --type=ed25519)
echo $edhash > ed25519_key_id
'

test_expect_success "export and import rsa key" '
ipfs key export generated_rsa_key &&
ipfs key rm generated_rsa_key &&
ipfs key import generated_rsa_key generated_rsa_key.key > roundtrip_rsa_key_id &&
test_cmp rsa_key_id roundtrip_rsa_key_id
'

test_expect_success "export and import ed25519 key" '
ipfs key export generated_ed25519_key &&
ipfs key rm generated_ed25519_key &&
ipfs key import generated_ed25519_key generated_ed25519_key.key > roundtrip_ed25519_key_id &&
test_cmp ed25519_key_id roundtrip_ed25519_key_id
'

test_expect_success "both keys show up in list output" '
echo bazed > list_exp &&
echo foobarsa >> list_exp &&
test_expect_success "test export file option" '
ipfs key export generated_rsa_key -o=named_rsa_export_file &&
test_cmp generated_rsa_key.key named_rsa_export_file &&
ipfs key export generated_ed25519_key -o=named_ed25519_export_file &&
test_cmp generated_ed25519_key.key named_ed25519_export_file
'

test_expect_success "key export can't export self" '
test_must_fail ipfs key export self 2>&1 | tee key_exp_out &&
grep -q "Error: cannot export key with name" key_exp_out &&
test_must_fail ipfs key export self -o=selfexport 2>&1 | tee key_exp_out &&
grep -q "Error: cannot export key with name" key_exp_out
'

test_expect_success "key import can't import self" '
ipfs key gen overwrite_self_import &&
ipfs key export overwrite_self_import &&
test_must_fail ipfs key import self overwrite_self_import.key 2>&1 | tee key_imp_out &&
grep -q "Error: cannot import key with name" key_imp_out &&
ipfs key rm overwrite_self_import &&
rm overwrite_self_import.key
'

test_expect_success "add a default key" '
ipfs key gen quxel
'

test_expect_success "all keys show up in list output" '
echo generated_ed25519_key > list_exp &&
echo generated_rsa_key >> list_exp &&
echo quxel >> list_exp &&
echo self >> list_exp
ipfs key list -f=b58mh | sort > list_out &&
test_cmp list_exp list_out
ipfs key list -f=b58mh > list_out &&
test_sort_cmp list_exp list_out
'

test_expect_success "key hashes show up in long list output" '
Expand All @@ -63,11 +119,12 @@ ipfs key rm key_ed25519
'

test_expect_success "key rm remove a key" '
ipfs key rm foobarsa
echo bazed > list_exp &&
ipfs key rm generated_rsa_key
echo generated_ed25519_key > list_exp &&
echo quxel >> list_exp &&
echo self >> list_exp
ipfs key list -f=b58mh | sort > list_out &&
test_cmp list_exp list_out
ipfs key list -f=b58mh > list_out &&
test_sort_cmp list_exp list_out
'

test_expect_success "key rm can't remove self" '
Expand All @@ -76,11 +133,12 @@ ipfs key rm key_ed25519
'

test_expect_success "key rename rename a key" '
ipfs key rename bazed fooed
ipfs key rename generated_ed25519_key fooed
echo fooed > list_exp &&
echo quxel >> list_exp &&
echo self >> list_exp
ipfs key list -f=b58mh | sort > list_out &&
test_cmp list_exp list_out
ipfs key list -f=b58mh > list_out &&
test_sort_cmp list_exp list_out
'

test_expect_success "key rename rename key output succeeds" '
Expand All @@ -101,6 +159,22 @@ ipfs key rm key_ed25519
'
}

test_check_rsa2048_sk() {
sklen=$(ls -l $1 | awk '{print $5}') &&
test "$sklen" -lt "1600" && test "$sklen" -gt "1000" || {
echo "Bad RSA2048 sk '$1' with len '$sklen'"
return 1
}
}

test_check_ed25519_sk() {
sklen=$(ls -l $1 | awk '{print $5}') &&
test "$sklen" -lt "100" && test "$sklen" -gt "30" || {
echo "Bad ED25519 sk '$1' with len '$sklen'"
return 1
}
}

test_key_cmd

test_done

0 comments on commit dfb8141

Please sign in to comment.