Skip to content

Commit

Permalink
Merge PR #5104: Remove keyring lazy implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Alessio Treglia authored and alexanderbez committed Sep 30, 2019
1 parent 3e6562c commit 46cd611
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 398 deletions.
143 changes: 129 additions & 14 deletions crypto/keys/keybase_keyring.go → crypto/keys/keyring.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package keys

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strings"

"github.com/99designs/keyring"
"github.com/pkg/errors"

"github.com/tendermint/crypto/bcrypt"
"github.com/tendermint/tendermint/crypto"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"

"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
Expand All @@ -21,26 +30,32 @@ var _ Keybase = keyringKeybase{}

// keyringKeybase implements the Keybase interface by using the Keyring library
// for account key persistence.
//
// TODO: There is no need for keyringKeybase to implement the Keybase interface
// or even have any of its members be public as it currently cannot be used by
// the outside world. Any client uses this implementation through the
// lazyKeybaseKeyring implementation. Consider either:
//
// 1. Remove lazyKeybaseKeyring and make keyringKeybase useable by the outside
// world.
// 2. Mark all keyringKeybase methods private and remove the compile time
// Keybase interface assertion.
type keyringKeybase struct {
db keyring.Keyring
base baseKeybase
}

func newKeyringKeybase(db keyring.Keyring) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
var maxPassphraseEntryAttempts = 3

// NewKeyring creates a new instance of a keyring.
func NewKeyring(name string, dir string, userInput io.Reader) Keybase {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, userInput, false))
if err != nil {
panic(err)
}

return newKeyringKeybase(db)
}

// NewTestKeyring creates a new instance of a keyring for
// testing purposes that does not prompt users for password.
func NewTestKeyring(name string, dir string) Keybase {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, nil, true))
if err != nil {
panic(err)
}

return newKeyringKeybase(db)
}

// CreateMnemonic generates a new key and persists it to storage, encrypted
Expand Down Expand Up @@ -437,3 +452,103 @@ func (kb keyringKeybase) writeInfo(name string, info Info) {
panic(err)
}
}

//nolint: funlen
func lkbToKeyringConfig(name, dir string, buf io.Reader, test bool) keyring.Config {
if test {
return keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: name,
FileDir: dir,
FilePasswordFunc: fakePrompt,
}
}

realPrompt := func(prompt string) (string, error) {
keyhashStored := false
keyhashFilePath := filepath.Join(dir, "keyhash")

var keyhash []byte

_, err := os.Stat(keyhashFilePath)
switch {
case err == nil:
keyhash, err = ioutil.ReadFile(keyhashFilePath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
}

keyhashStored = true

case os.IsNotExist(err):
keyhashStored = false

default:
return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
}

failureCounter := 0
for {
failureCounter++
if failureCounter > maxPassphraseEntryAttempts {
return "", fmt.Errorf("too many failed passphrase attempts")
}

buf := bufio.NewReader(buf)
pass, err := input.GetPassword("Enter keyring passphrase:", buf)
if err != nil {
continue
}

if keyhashStored {
if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
fmt.Fprintln(os.Stderr, "incorrect passphrase")
continue
}
return pass, nil
}

reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}

if pass != reEnteredPass {
fmt.Fprintln(os.Stderr, "passphrase do not match")
continue
}

saltBytes := crypto.CRandBytes(16)
passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}

if err := ioutil.WriteFile(dir+"/keyhash", passwordHash, 0555); err != nil {
return "", err
}

return pass, nil
}
}

return keyring.Config{
ServiceName: name,
FileDir: dir,
FilePasswordFunc: realPrompt,
}
}

func fakePrompt(prompt string) (string, error) {
fmt.Fprintln(os.Stderr, "Fake prompt for passphase. Testing only")
return "test", nil
}

func newKeyringKeybase(db keyring.Keyring) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

func TestNewTestKeybaseKeyring(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := NewTestKeyring("keybasename", dir)
lazykb, ok := kb.(lazyKeybaseKeyring)
require.True(t, ok)
require.Equal(t, lazykb.name, "keybasename")
}

func TestLazyKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
Expand Down
Loading

0 comments on commit 46cd611

Please sign in to comment.