Add delete and recover commands, and test them

This commit is contained in:
Ethan Frey 2017-06-20 20:34:13 +02:00
parent e9537b2da6
commit 1ab9ab9494
6 changed files with 197 additions and 5 deletions

49
cmd/delete.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright © 2017 Ethan Frey
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "DANGER: Delete a private key from your system",
RunE: runDeleteCmd,
}
func runDeleteCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
oldpass, err := getPassword("DANGER - enter password to permanently delete key:")
if err != nil {
return err
}
err = GetKeyManager().Delete(name, oldpass)
if err != nil {
return err
}
fmt.Println("Password deleted forever (uh oh!)")
return nil
}

View File

@ -15,14 +15,20 @@
package cmd
import (
"fmt"
"github.com/pkg/errors"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
flagType = "type"
flagType = "type"
flagNoBackup = "no-backup"
)
// newCmd represents the new command
@ -37,6 +43,7 @@ passed as a command line argument for security.`,
func init() {
newCmd.Flags().StringP(flagType, "t", "ed25519", "Type of key (ed25519|secp256k1)")
newCmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
}
func runNewCmd(cmd *cobra.Command, args []string) error {
@ -51,9 +58,37 @@ func runNewCmd(cmd *cobra.Command, args []string) error {
return err
}
info, _, err := GetKeyManager().Create(name, pass, algo)
info, seed, err := GetKeyManager().Create(name, pass, algo)
if err == nil {
printInfo(info)
printCreate(info, seed)
}
return err
}
type NewOutput struct {
Key keys.Info `json:"key"`
Seed string `json:"seed"`
}
func printCreate(info keys.Info, seed string) {
switch viper.Get(cli.OutputFlag) {
case "text":
printInfo(info)
// print seed unless requested not to.
if !viper.GetBool(flagNoBackup) {
fmt.Println("**Important** write this seed phrase in a safe place.")
fmt.Println("It is the only way to recover your account if you ever forget your password.\n")
fmt.Println(seed)
}
case "json":
out := NewOutput{Key: info}
if !viper.GetBool(flagNoBackup) {
out.Seed = seed
}
json, err := data.ToJSON(out)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

53
cmd/recover.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright © 2017 Ethan Frey
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// recoverCmd represents the recover command
var recoverCmd = &cobra.Command{
Use: "recover <name>",
Short: "Change the password for a private key",
RunE: runRecoverCmd,
}
func runRecoverCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
pass, err := getPassword("Enter the new passphrase:")
if err != nil {
return err
}
// not really a password... huh?
seed, err := getSeed("Enter your recovery seed phrase")
if err != nil {
return err
}
info, err := GetKeyManager().Recover(name, pass, seed)
if err != nil {
return err
}
printInfo(info)
return nil
}

View File

@ -41,6 +41,8 @@ func init() {
RootCmd.AddCommand(listCmd)
RootCmd.AddCommand(newCmd)
RootCmd.AddCommand(updateCmd)
RootCmd.AddCommand(deleteCmd)
RootCmd.AddCommand(recoverCmd)
}
func RegisterServer() {

View File

@ -76,6 +76,12 @@ func getPassword(prompt string) (pass string, err error) {
return pass, nil
}
func getSeed(prompt string) (seed string, err error) {
seed, err = stdinPassword()
seed = strings.TrimSpace(seed)
return
}
func getCheckPassword(prompt, prompt2 string) (string, error) {
// simple read on no-tty
if !inputIsTty() {

View File

@ -12,8 +12,9 @@ oneTimeSetUp() {
newKey(){
assertNotNull "keyname required" "$1"
KEYPASS=${2:-qwertyuiop}
KEY=$(echo $KEYPASS | ${EXE} new $1)
assertTrue "created $1" $?
KEY=$(echo $KEYPASS | ${EXE} new $1 -o json)
if ! assertTrue "created $1" $?; then return 1; fi
assertEquals "$1" $(echo $KEY | jq .key.name | tr -d \")
return $?
}
@ -59,6 +60,52 @@ test02updateKeys() {
assertTrue "takes new key after update" "updateKey $USER $PASS2 $PASS3"
}
test03recoverKeys() {
USER=sleepy
PASS1=S4H.9j.D9S7hso
USER2=easy
PASS2=1234567890
# make a user and check they exist
echo "create..."
KEY=$(echo $PASS1 | ${EXE} new $USER -o json)
if ! assertTrue "created $USER" $?; then return 1; fi
if [ -n "$DEBUG" ]; then echo $KEY; echo; fi
SEED=$(echo $KEY | jq .seed | tr -d \")
ADDR=$(echo $KEY | jq .key.address | tr -d \")
PUBKEY=$(echo $KEY | jq .key.pubkey | tr -d \")
assertTrue "${EXE} get $USER > /dev/null"
# let's delete this key
echo "delete..."
assertFalse "echo foo | ${EXE} delete $USER > /dev/null"
assertTrue "echo $PASS1 | ${EXE} delete $USER > /dev/null"
assertFalse "${EXE} get $USER > /dev/null"
# fails on short password
echo "recover..."
assertFalse "echo foo; echo $SEED | ${EXE} recover $USER2 -o json > /dev/null"
# fails on bad seed
assertFalse "echo $PASS2; echo \"silly white whale tower bongo\" | ${EXE} recover $USER2 -o json > /dev/null"
# now we got it
KEY2=$((echo $PASS2; echo $SEED) | ${EXE} recover $USER2 -o json)
if ! assertTrue "recovery failed: $KEY2" $?; then return 1; fi
if [ -n "$DEBUG" ]; then echo $KEY2; echo; fi
# make sure it looks the same
NAME2=$(echo $KEY2 | jq .name | tr -d \")
ADDR2=$(echo $KEY2 | jq .address | tr -d \")
PUBKEY2=$(echo $KEY2 | jq .pubkey | tr -d \")
assertEquals "wrong username" "$USER2" "$NAME2"
assertEquals "address doesn't match" "$ADDR" "$ADDR2"
assertEquals "pubkey doesn't match" "$PUBKEY" "$PUBKEY2"
# and we can find the info
assertTrue "${EXE} get $USER2 > /dev/null"
}
# load and run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/shunit2