Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Commit

Permalink
add encrypted key file support: use @ prefix plus file path (#441)
Browse files Browse the repository at this point in the history
* add encrypted key file support: use @ prefix plus file path

* change workding

* support gen-addr directly to encrypted file and addr-of key file
  • Loading branch information
Stumble authored Oct 31, 2019
1 parent 6df0ba2 commit c2f00cc
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 23 deletions.
40 changes: 26 additions & 14 deletions client/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package client

import (
"encoding/hex"
"fmt"
"os"

cosmoscli "github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
Expand All @@ -11,11 +13,21 @@ import (
rpcclient "github.com/tendermint/tendermint/rpc/client"

"github.com/lino-network/lino/client/core"
"github.com/lino-network/lino/client/encrypt"
)

var ValidateCmd = cosmoscli.ValidateCmd

func ParsePrivKey(key string) (crypto.PrivKey, error) {
// @ tag means that priv-key is encrypted in the file.
if key[0] == '@' {
bytes, err := encrypt.DecryptByStdin(key[1:])
if err != nil {
exitWith("Failed to decrypt file: %s", err)
}
key = string(bytes)
}

var privKey crypto.PrivKey
privKeyBytes, err := hex.DecodeString(key)
if err != nil {
Expand All @@ -37,7 +49,7 @@ func NewCoreContextFromViper() core.CoreContext {

seq := viper.GetInt64(FlagSequence)
if seq < 0 {
panic("Missing --" + FlagSequence)
exitWith("Missing --" + FlagSequence)
}

ctx := core.CoreContext{
Expand All @@ -52,21 +64,16 @@ func NewCoreContextFromViper() core.CoreContext {
}
ctx = ctx.WithFees(viper.GetString(FlagFees))

hasKey := false
for _, keyFlag := range []string{FlagPrivKey} {
key := viper.GetString(keyFlag)
if key != "" {
pk, err := ParsePrivKey(key)
if err != nil {
panic(err)
}
hasKey = true
ctx = ctx.WithPrivKey(pk)
}
privKey := viper.GetString(FlagPrivKey)
if len(privKey) == 0 {
exitWith("Missing --" + FlagPrivKey)
}
if !hasKey {
panic("Missing --" + FlagPrivKey)

pk, err := ParsePrivKey(privKey)
if err != nil {
exitWith("Invalid PrivKey: %s", err)
}
ctx = ctx.WithPrivKey(pk)
return ctx
}

Expand All @@ -90,3 +97,8 @@ func NewCoreBroadcastContextFromViper() core.CoreContext {
}

type CommandTxCallback func(cmd *cobra.Command, args []string) error

func exitWith(s string, args ...interface{}) {
fmt.Printf(s+"\n", args...)
os.Exit(1)
}
87 changes: 87 additions & 0 deletions client/encrypt/encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package encrypt

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"

"golang.org/x/crypto/ssh/terminal"
)

func hashPassword(pw string) []byte {
hash := sha256.Sum256([]byte(pw))
return hash[:]
}

func Encrypt(data []byte, password string) ([]byte, error) {
block, err := aes.NewCipher(hashPassword(password))
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
cipher := gcm.Seal(nonce, nonce, data, nil)
return cipher, nil
}

func Decrypt(data []byte, password string) ([]byte, error) {
block, err := aes.NewCipher(hashPassword(password))
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}

func EncryptToFile(filename string, data []byte, password string) error {
cipher, err := Encrypt(data, password)
if err != nil {
return err
}

f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()

_, err = f.Write(cipher)
if err != nil {
return err
}
return f.Sync()
}

func DecryptFromFile(filename string, password string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return Decrypt(data, password)
}

func DecryptByStdin(filename string) ([]byte, error) {
fmt.Printf("Password of %s: ", filename)
pw, err := terminal.ReadPassword(0)
if err != nil {
return nil, err
}
fmt.Printf("\n")
return DecryptFromFile(filename, string(pw))
}
2 changes: 1 addition & 1 deletion client/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().Int64(FlagSequence, -1, "Sequence number to sign the tx")
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagPrivKey, "", "Private key to sign the transaction")
c.Flags().String(FlagPrivKey, "", "Hex-encoded private key or encrypted file path(starting with @) to sign the transaction")
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 1linocoin")
c.Flags().Bool(FlagOffline, false, "Print Tx to stdout only")
Expand Down
87 changes: 82 additions & 5 deletions client/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/crypto/secp256k1"
"golang.org/x/crypto/ssh/terminal"

"github.com/lino-network/lino/client/encrypt"
)

func GetNowCmd(cdc *amino.Codec) *cobra.Command {
Expand All @@ -25,18 +28,92 @@ func GetNowCmd(cdc *amino.Codec) *cobra.Command {
}
}

func GetGenAddrCmd(cdc *amino.Codec) *cobra.Command {
func GetGenAddrCmd() *cobra.Command {
return &cobra.Command{
Use: "gen-addr",
Short: "gen-addr prints a lino bech32 address and the associated private key hex",
Args: cobra.NoArgs,
Use: "gen-addr [keyfile]",
Short: "gen-addr [keyfile] prints a lino bech32 address. The private key will be printed if keyfile not specified",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
priv := secp256k1.GenPrivKey()
pub := priv.PubKey()
addr := sdk.AccAddress(pub.Address())
fmt.Printf("addr: %s\n", addr)
fmt.Printf("priv-key: %s\n", strings.ToUpper(hex.EncodeToString(priv.Bytes())))
if len(args) > 0 {
keyfile := args[0]
return encryptSave(keyfile, []byte(hex.EncodeToString(priv.Bytes())))
} else {
fmt.Printf("priv-key: %s\n", strings.ToUpper(hex.EncodeToString(priv.Bytes())))
}
return nil
},
}
}

func GetAddrOfCmd() *cobra.Command {
return &cobra.Command{
Use: "addr-of <@keyfile>",
Short: "addr-of <@keyfile> prints the lino bech32 address, @ included, e.g. @foo.key",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
file := args[0]
priv, err := ParsePrivKey(file)
if err != nil {
return err
}
addr := sdk.AccAddress(priv.PubKey().Address())
fmt.Printf("addr: %s\n", addr)
return nil
},
}
}

func GetEncryptPrivKey() *cobra.Command {
return &cobra.Command{
Use: "encrypt-key <file>",
Short: "encrypt-key <file>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Hex-encoded Private key: ")
privKey, err := terminal.ReadPassword(0)
if err != nil {
return err
}
fmt.Println("")

// validate key
_, err = ParsePrivKey(string(privKey))
if err != nil {
return fmt.Errorf("invalid privKey: %+v", err)
}

return encryptSave(args[0], privKey)
},
}
}

func encryptSave(filepath string, privKey []byte) error {
fmt.Printf("Password: ")
pw1, err := terminal.ReadPassword(0)
if err != nil {
return err
}
fmt.Println("")

fmt.Printf("Password again: ")
pw2, err := terminal.ReadPassword(0)
if err != nil {
return err
}
fmt.Printf("\n\n")

if string(pw1) != string(pw2) {
return fmt.Errorf("password mismatch")
}

err = encrypt.EncryptToFile(filepath, privKey, string(pw1))
if err != nil {
return err
}
fmt.Printf("encerypted key have been wrote to %s.\n", filepath)
return nil
}
5 changes: 4 additions & 1 deletion cmd/linocli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ func main() {
txCmd(cdc),
client.LineBreak,
linoclient.GetNowCmd(cdc),
linoclient.GetGenAddrCmd(cdc),
linoclient.GetGenAddrCmd(),
linoclient.GetAddrOfCmd(),
linoclient.GetEncryptPrivKey(),
client.LineBreak,
)

executor := cli.PrepareMainCmd(rootCmd, "NS", DefaultCLIHome)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/tendermint v0.32.6
github.com/tendermint/tm-db v0.2.0
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65 // indirect
google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d // indirect
)
4 changes: 2 additions & 2 deletions x/account/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func getCmdRegister(cdc *codec.Codec) *cobra.Command {
func getCmdBind(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "bind <type:referrer>",
Short: "bind <type:referrer> --addr <addr> --addr-priv-key <hex> --addr-seq <seq> --name <name> --reg-fee <amount>",
Long: "bind <type:referrer> --addr <addr> --addr-priv-key <hex> --addr-seq <seq> --name <name> --reg-fee <amount> will bind <addr> with <name> as username. The signing key will be the same as transaction key.",
Short: "bind <type:referrer> --addr <addr> --addr-priv-key <key> --addr-seq <seq> --name <name> --reg-fee <amount>",
Long: "bind <type:referrer> --addr <addr> --addr-priv-key <key> --addr-seq <seq> --name <name> --reg-fee <amount> will bind <addr> with <name> as username. The signing key will be the same as transaction key.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := client.NewCoreContextFromViper().WithTxEncoder(linotypes.TxEncoder(cdc))
Expand Down

0 comments on commit c2f00cc

Please sign in to comment.