Browse Source

Expose new and list via cli

pull/1782/head
Ethan Frey 8 years ago
parent
commit
506ff7d85a
16 changed files with 173 additions and 80 deletions
  1. +3
    -0
      Makefile
  2. +7
    -14
      cmd/list.go
  3. +30
    -12
      cmd/new.go
  4. +27
    -6
      cmd/root.go
  5. +56
    -0
      cmd/utils.go
  6. +4
    -4
      cryptostore/enc_storage.go
  7. +8
    -8
      cryptostore/holder.go
  8. +4
    -3
      cryptostore/holder_test.go
  9. +1
    -1
      cryptostore/storage_test.go
  10. +2
    -1
      keys.toml
  11. +3
    -3
      storage.go
  12. +8
    -8
      storage/filestorage/main.go
  13. +1
    -1
      storage/filestorage/main_test.go
  14. +5
    -5
      storage/memstorage/main.go
  15. +1
    -1
      storage/memstorage/main_test.go
  16. +13
    -13
      transactions.go

+ 3
- 0
Makefile View File

@ -1,2 +1,5 @@
test:
go test ./...
install:
go install ./cmd/keys

+ 7
- 14
cmd/list.go View File

@ -18,7 +18,6 @@ import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// listCmd represents the list command
@ -28,22 +27,16 @@ var listCmd = &cobra.Command{
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`,
Run: func(cmd *cobra.Command, args []string) {
// TODO: Work your own magic here
fmt.Println("list called")
fmt.Println(viper.Get("format"))
infos, err := manager.List()
if err != nil {
fmt.Println(err.Error())
return
}
printInfos(infos)
},
}
func init() {
RootCmd.AddCommand(listCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// listCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
listCmd.Flags().StringP("format", "f", "text", "Format to display (text|json)")
}

+ 30
- 12
cmd/new.go View File

@ -22,28 +22,46 @@ import (
// newCmd represents the new command
var newCmd = &cobra.Command{
Use: "new",
Use: "new <name>",
Short: "Create a new public/private key pair",
Long: `Add a public/private key pair to the key store.
The password muts be entered in the terminal and not
passed as a command line argument for security.`,
Run: func(cmd *cobra.Command, args []string) {
// TODO: Work your own magic here
fmt.Println("new called")
},
Run: newPassword,
}
func init() {
RootCmd.AddCommand(newCmd)
}
// Here you will define your flags and configuration settings.
func newPassword(cmd *cobra.Command, args []string) {
if len(args) != 1 || len(args[0]) == 0 {
fmt.Print("You must provide a name for the key")
return
}
name := args[0]
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// newCmd.PersistentFlags().String("foo", "", "A help for foo")
// TODO: own function???
pass, err := getPassword("Enter a passphrase:")
if err != nil {
fmt.Println(err.Error())
return
}
pass2, err := getPassword("Repeat the passphrase:")
if err != nil {
fmt.Println(err.Error())
return
}
if pass != pass2 {
fmt.Println("Passphrases don't match")
return
}
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// newCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
info, err := manager.Create(name, pass)
if err != nil {
fmt.Println(err.Error())
return
}
printInfo(info)
}

+ 27
- 6
cmd/root.go View File

@ -17,16 +17,22 @@ package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
keys "github.com/tendermint/go-keys"
"github.com/tendermint/go-keys/cryptostore"
"github.com/tendermint/go-keys/storage/filestorage"
)
var (
rootDir string
format string
output string
keyDir string
manager keys.Manager
)
// RootCmd represents the base command when called without any subcommands
@ -53,7 +59,8 @@ func Execute() {
func init() {
cobra.OnInitialize(initEnv)
RootCmd.PersistentFlags().StringP("root", "r", os.ExpandEnv("$HOME/.tlc"), "root directory for config and data (default is TM_ROOT or $HOME/.tlc)")
RootCmd.PersistentFlags().StringP("format", "f", "text", "Output format (text|json)")
RootCmd.PersistentFlags().StringP("output", "o", "text", "Output format (text|json)")
RootCmd.PersistentFlags().StringP("keydir", "", "keys", "Directory to store private keys (subdir of root)")
}
// initEnv sets to use ENV variables if set.
@ -85,11 +92,25 @@ func bindFlags(cmd *cobra.Command, args []string) error {
// validateFlags asserts all RootCmd flags are valid
func validateFlags(cmd *cobra.Command) error {
format = viper.GetString("format")
switch format {
// validate output format
output = viper.GetString("output")
switch output {
case "text", "json":
return nil
default:
return errors.Errorf("Unsupported format: %s", format)
return errors.Errorf("Unsupported output format: %s", output)
}
// store the keys directory
keyDir = viper.GetString("keydir")
if !filepath.IsAbs(keyDir) {
keyDir = filepath.Join(rootDir, keyDir)
}
// and construct the key manager
manager = cryptostore.New(
cryptostore.GenEd25519, // TODO - cli switch???
cryptostore.SecretBox,
filestorage.New(keyDir),
)
return nil
}

+ 56
- 0
cmd/utils.go View File

@ -0,0 +1,56 @@
package cmd
import (
"fmt"
"github.com/bgentry/speakeasy"
"github.com/pkg/errors"
data "github.com/tendermint/go-data"
keys "github.com/tendermint/go-keys"
)
const PassLength = 10
func getPassword(prompt string) (string, error) {
pass, err := speakeasy.Ask(prompt)
if err != nil {
return "", err
}
if len(pass) < PassLength {
return "", errors.Errorf("Password must be at least %d characters", PassLength)
}
return pass, nil
}
func printInfo(info keys.Info) {
switch output {
case "text":
key, err := data.ToText(info.PubKey)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Printf("%s\t%s\n", info.Name, key)
case "json":
json, err := data.ToJSON(info)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}
func printInfos(infos keys.Infos) {
switch output {
case "text":
fmt.Println("All keys:")
for _, i := range infos {
printInfo(i)
}
case "json":
json, err := data.ToJSON(infos)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

+ 4
- 4
cryptostore/enc_storage.go View File

@ -21,7 +21,7 @@ func (es encryptedStorage) Put(name, pass string, key crypto.PrivKey) error {
return es.store.Put(name, secret, ki)
}
func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.KeyInfo, error) {
func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.Info, error) {
secret, info, err := es.store.Get(name)
if err != nil {
return nil, info, err
@ -30,7 +30,7 @@ func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.KeyInfo,
return key, info, err
}
func (es encryptedStorage) List() ([]keys.KeyInfo, error) {
func (es encryptedStorage) List() (keys.Infos, error) {
return es.store.List()
}
@ -39,8 +39,8 @@ func (es encryptedStorage) Delete(name string) error {
}
// info hardcodes the encoding of keys
func info(name string, key crypto.PrivKey) keys.KeyInfo {
return keys.KeyInfo{
func info(name string, key crypto.PrivKey) keys.Info {
return keys.Info{
Name: name,
PubKey: crypto.PubKeyS{key.PubKey()},
}


+ 8
- 8
cryptostore/holder.go View File

@ -24,28 +24,28 @@ func (s Manager) assertSigner() keys.Signer {
return s
}
// exists just to make sure we fulfill the KeyManager interface
func (s Manager) assertKeyManager() keys.KeyManager {
// exists just to make sure we fulfill the Manager interface
func (s Manager) assertKeyManager() keys.Manager {
return s
}
// Create adds a new key to the storage engine, returning error if
// another key already stored under this name
func (s Manager) Create(name, passphrase string) error {
func (s Manager) Create(name, passphrase string) (keys.Info, error) {
key := s.gen.Generate()
return s.es.Put(name, passphrase, key)
err := s.es.Put(name, passphrase, key)
return info(name, key), err
}
// List loads the keys from the storage and enforces alphabetical order
func (s Manager) List() (keys.KeyInfos, error) {
k, err := s.es.List()
res := keys.KeyInfos(k)
func (s Manager) List() (keys.Infos, error) {
res, err := s.es.List()
res.Sort()
return res, err
}
// Get returns the public information about one key
func (s Manager) Get(name string) (keys.KeyInfo, error) {
func (s Manager) Get(name string) (keys.Info, error) {
_, info, err := s.es.store.Get(name)
return info, err
}


+ 4
- 3
cryptostore/holder_test.go View File

@ -31,9 +31,10 @@ func TestKeyManagement(t *testing.T) {
// create some keys
_, err = cstore.Get(n1)
assert.NotNil(err)
err = cstore.Create(n1, p1)
i, err := cstore.Create(n1, p1)
require.Equal(n1, i.Name)
require.Nil(err)
err = cstore.Create(n2, p2)
_, err = cstore.Create(n2, p2)
require.Nil(err)
// we can get these keys
@ -159,7 +160,7 @@ func TestAdvancedKeyManagement(t *testing.T) {
p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$"
// make sure key works with initial password
err := cstore.Create(n1, p1)
_, err := cstore.Create(n1, p1)
require.Nil(err, "%+v", err)
assertPassword(assert, cstore, n1, p1, p2)


+ 1
- 1
cryptostore/storage_test.go View File

@ -15,7 +15,7 @@ func TestSortKeys(t *testing.T) {
// alphabetical order is n3, n1, n2
n1, n2, n3 := "john", "mike", "alice"
infos := keys.KeyInfos{
infos := keys.Infos{
info(n1, gen()),
info(n2, gen()),
info(n3, gen()),


+ 2
- 1
keys.toml View File

@ -1 +1,2 @@
format = "text"
output = "text"
keydir = ".mykeys"

+ 3
- 3
storage.go View File

@ -3,8 +3,8 @@ package keys
// Storage has many implementation, based on security and sharing requirements
// like disk-backed, mem-backed, vault, db, etc.
type Storage interface {
Put(name string, key []byte, info KeyInfo) error
Get(name string) ([]byte, KeyInfo, error)
List() ([]KeyInfo, error)
Put(name string, key []byte, info Info) error
Get(name string) ([]byte, Info, error)
List() (Infos, error)
Delete(name string) error
}

+ 8
- 8
storage/filestorage/main.go View File

@ -49,7 +49,7 @@ func (s FileStore) assertStorage() keys.Storage {
// Put creates two files, one with the public info as json, the other
// with the (encoded) private key as gpg ascii-armor style
func (s FileStore) Put(name string, key []byte, info keys.KeyInfo) error {
func (s FileStore) Put(name string, key []byte, info keys.Info) error {
pub, priv := s.nameToPaths(name)
// write public info
@ -62,10 +62,10 @@ func (s FileStore) Put(name string, key []byte, info keys.KeyInfo) error {
return write(priv, name, key)
}
// Get loads the keyinfo and (encoded) private key from the directory
// Get loads the info and (encoded) private key from the directory
// It uses `name` to generate the filename, and returns an error if the
// files don't exist or are in the incorrect format
func (s FileStore) Get(name string) ([]byte, keys.KeyInfo, error) {
func (s FileStore) Get(name string) ([]byte, keys.Info, error) {
pub, priv := s.nameToPaths(name)
info, err := readInfo(pub)
@ -78,8 +78,8 @@ func (s FileStore) Get(name string) ([]byte, keys.KeyInfo, error) {
}
// List parses the key directory for public info and returns a list of
// KeyInfo for all keys located in this directory.
func (s FileStore) List() ([]keys.KeyInfo, error) {
// Info for all keys located in this directory.
func (s FileStore) List() (keys.Infos, error) {
dir, err := os.Open(s.keyDir)
if err != nil {
return nil, errors.Wrap(err, "List Keys")
@ -91,7 +91,7 @@ func (s FileStore) List() ([]keys.KeyInfo, error) {
// filter names for .pub ending and load them one by one
// half the files is a good guess for pre-allocating the slice
infos := make([]keys.KeyInfo, 0, len(names)/2)
infos := make([]keys.Info, 0, len(names)/2)
for _, name := range names {
if strings.HasSuffix(name, PubExt) {
p := path.Join(s.keyDir, name)
@ -124,11 +124,11 @@ func (s FileStore) nameToPaths(name string) (pub, priv string) {
return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
}
func writeInfo(path string, info keys.KeyInfo) error {
func writeInfo(path string, info keys.Info) error {
return write(path, info.Name, info.PubKey.Bytes())
}
func readInfo(path string) (info keys.KeyInfo, err error) {
func readInfo(path string) (info keys.Info, err error) {
var data []byte
data, info.Name, err = read(path)
if err != nil {


+ 1
- 1
storage/filestorage/main_test.go View File

@ -23,7 +23,7 @@ func TestBasicCRUD(t *testing.T) {
name := "bar"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.KeyInfo{
info := keys.Info{
Name: name,
PubKey: crypto.PubKeyS{pubkey},
}


+ 5
- 5
storage/memstorage/main.go View File

@ -11,7 +11,7 @@ import (
)
type data struct {
info keys.KeyInfo
info keys.Info
key []byte
}
@ -29,7 +29,7 @@ func (s MemStore) assertStorage() keys.Storage {
// Put adds the given key, returns an error if it another key
// is already stored under this name
func (s MemStore) Put(name string, key []byte, info keys.KeyInfo) error {
func (s MemStore) Put(name string, key []byte, info keys.Info) error {
if _, ok := s[name]; ok {
return errors.Errorf("Key named '%s' already exists", name)
}
@ -38,7 +38,7 @@ func (s MemStore) Put(name string, key []byte, info keys.KeyInfo) error {
}
// Get returns the key stored under the name, or returns an error if not present
func (s MemStore) Get(name string) ([]byte, keys.KeyInfo, error) {
func (s MemStore) Get(name string) ([]byte, keys.Info, error) {
var err error
d, ok := s[name]
if !ok {
@ -48,8 +48,8 @@ func (s MemStore) Get(name string) ([]byte, keys.KeyInfo, error) {
}
// List returns the public info of all keys in the MemStore in unsorted order
func (s MemStore) List() ([]keys.KeyInfo, error) {
res := make([]keys.KeyInfo, len(s))
func (s MemStore) List() (keys.Infos, error) {
res := make([]keys.Info, len(s))
i := 0
for _, d := range s {
res[i] = d.info


+ 1
- 1
storage/memstorage/main_test.go View File

@ -15,7 +15,7 @@ func TestBasicCRUD(t *testing.T) {
name := "foo"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.KeyInfo{
info := keys.Info{
Name: name,
PubKey: crypto.PubKeyS{pubkey},
}


+ 13
- 13
transactions.go View File

@ -6,19 +6,19 @@ import (
crypto "github.com/tendermint/go-crypto"
)
// KeyInfo is the public information about a key
type KeyInfo struct {
// Info is the public information about a key
type Info struct {
Name string
PubKey crypto.PubKeyS
}
// KeyInfos is a wrapper to allows alphabetical sorting of the keys
type KeyInfos []KeyInfo
// Infos is a wrapper to allows alphabetical sorting of the keys
type Infos []Info
func (k KeyInfos) Len() int { return len(k) }
func (k KeyInfos) Less(i, j int) bool { return k[i].Name < k[j].Name }
func (k KeyInfos) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k KeyInfos) Sort() {
func (k Infos) Len() int { return len(k) }
func (k Infos) Less(i, j int) bool { return k[i].Name < k[j].Name }
func (k Infos) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k Infos) Sort() {
if k != nil {
sort.Sort(k)
}
@ -51,11 +51,11 @@ type Signer interface {
Sign(name, passphrase string, tx Signable) error
}
// KeyManager allows simple CRUD on a keystore, as an aid to signing
type KeyManager interface {
Create(name, passphrase string) error
List() (KeyInfos, error)
Get(name string) (KeyInfo, error)
// Manager allows simple CRUD on a keystore, as an aid to signing
type Manager interface {
Create(name, passphrase string) (Info, error)
List() (Infos, error)
Get(name string) (Info, error)
Update(name, oldpass, newpass string) error
Delete(name, passphrase string) error
}

Loading…
Cancel
Save