Use Password Specific to Web UI Instead of Wallet Password (#7569)

* use password specific to web UI

* fix up a few more tests

* tests passing

* gaz

* fix fmt
This commit is contained in:
Raul Jordan
2020-10-19 21:26:31 -05:00
committed by GitHub
parent 1bc86d2658
commit bec91d348e
24 changed files with 193 additions and 449 deletions

View File

@@ -186,7 +186,7 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) {
backupPasswordFile: backupPasswordFile,
backupDir: backupDir,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
_, err = CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Imported,
@@ -194,7 +194,6 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(cliCtx.Context))
// We attempt to import accounts we wrote to the keys directory
// into our newly created wallet.

View File

@@ -59,7 +59,6 @@ func TestDeleteAccounts_Noninteractive(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(cliCtx.Context))
// We attempt to import accounts.
require.NoError(t, ImportAccountsCli(cliCtx))

View File

@@ -72,7 +72,7 @@ func TestExitAccountsCli_Ok(t *testing.T) {
// Flag required for ExitAccounts to work.
voluntaryExitPublicKeys: keystore.Pubkey,
})
w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
_, err = CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Imported,
@@ -80,7 +80,6 @@ func TestExitAccountsCli_Ok(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(cliCtx.Context))
require.NoError(t, ImportAccountsCli(cliCtx))
validatingPublicKeys, keymanager, err := prepareWallet(cliCtx)
@@ -128,5 +127,5 @@ func TestPrepareWallet_EmptyWalletReturnsError(t *testing.T) {
})
require.NoError(t, err)
_, _, err = prepareWallet(cliCtx)
assert.ErrorContains(t, "please recreate your wallet", err)
assert.ErrorContains(t, "wallet is empty", err)
}

View File

@@ -90,10 +90,6 @@ func ImportAccountsCli(cliCtx *cli.Context) error {
if err = createImportedKeymanagerWallet(cliCtx.Context, w); err != nil {
return nil, errors.Wrap(err, "could not create keymanager")
}
// We store the hashed password to disk.
if err := w.SaveHashedPassword(cliCtx.Context); err != nil {
return nil, err
}
log.WithField("wallet-path", cfg.WalletCfg.WalletDir).Info(
"Successfully created new wallet",
)

View File

@@ -1,7 +1,6 @@
package accounts
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
@@ -54,7 +53,6 @@ func TestImport_Noninteractive(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(context.Background()))
keymanager, err := imported.NewKeymanager(
cliCtx.Context,
&imported.SetupConfig{
@@ -116,7 +114,6 @@ func TestImport_Noninteractive_RandomName(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(context.Background()))
keymanager, err := imported.NewKeymanager(
cliCtx.Context,
&imported.SetupConfig{
@@ -179,7 +176,6 @@ func TestImport_Noninteractive_Filepath(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(context.Background()))
keymanager, err := imported.NewKeymanager(
cliCtx.Context,
&imported.SetupConfig{
@@ -306,7 +302,6 @@ func Test_importPrivateKeyAsAccount(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, wallet.SaveHashedPassword(context.Background()))
keymanager, err := imported.NewKeymanager(
cliCtx.Context,
&imported.SetupConfig{

View File

@@ -14,7 +14,6 @@ type Wallet interface {
// Methods to retrieve wallet and accounts metadata.
AccountsDir() string
Password() string
SetPassword(newPass string)
// Read methods for important wallet and accounts-related files.
ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error)
ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error)

View File

@@ -50,11 +50,6 @@ func (m *Wallet) Password() string {
return m.WalletPassword
}
// SetPassword sets a new password for the wallet.
func (m *Wallet) SetPassword(newPass string) {
m.WalletPassword = newPass
}
// WriteFileAtPath --
func (m *Wallet) WriteFileAtPath(_ context.Context, pathName, fileName string, data []byte) error {
m.lock.Lock()

View File

@@ -20,7 +20,6 @@ go_library(
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
"@org_golang_x_crypto//bcrypt:go_default_library",
],
)

View File

@@ -22,7 +22,6 @@ import (
"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/bcrypt"
)
var log = logrus.WithField("prefix", "wallet")
@@ -30,8 +29,6 @@ var log = logrus.WithField("prefix", "wallet")
const (
// KeymanagerConfigFileName for the keymanager used by the wallet: imported, derived, or remote.
KeymanagerConfigFileName = "keymanageropts.json"
// HashedPasswordFileName for the wallet.
HashedPasswordFileName = "hash"
// DirectoryPermissions for directories created under the wallet path.
DirectoryPermissions = os.ModePerm
// NewWalletPasswordPromptText for wallet creation.
@@ -192,16 +189,6 @@ func OpenWalletOrElseCli(cliCtx *cli.Context, otherwise func(cliCtx *cli.Context
if err != nil {
return nil, err
}
if fileutil.FileExists(filepath.Join(walletDir, HashedPasswordFileName)) {
hashedPassword, err := fileutil.ReadFileAsBytes(filepath.Join(walletDir, HashedPasswordFileName))
if err != nil {
return nil, err
}
// Compare the wallet password here.
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(walletPassword)); err != nil {
return nil, errors.Wrap(err, "wrong password for wallet")
}
}
return OpenWallet(cliCtx.Context, &Config{
WalletDir: walletDir,
WalletPassword: walletPassword,
@@ -267,11 +254,6 @@ func (w *Wallet) Password() string {
return w.walletPassword
}
// SetPassword sets a new password for the wallet.
func (w *Wallet) SetPassword(newPass string) {
w.walletPassword = newPass
}
// InitializeKeymanager reads a keymanager config from disk at the wallet path,
// unmarshals it based on the wallet's keymanager kind, and returns its value.
func (w *Wallet) InitializeKeymanager(
@@ -296,18 +278,6 @@ func (w *Wallet) InitializeKeymanager(
if err != nil {
return nil, errors.Wrap(err, "could not initialize imported keymanager")
}
if !fileutil.FileExists(filepath.Join(w.walletDir, HashedPasswordFileName)) {
keys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, errors.New("please recreate your wallet with wallet create")
}
if err := w.SaveHashedPassword(ctx); err != nil {
return nil, errors.Wrap(err, "could not save hashed password to disk")
}
}
case keymanager.Derived:
opts, err := derived.UnmarshalOptionsFile(configFile)
if err != nil {
@@ -321,11 +291,6 @@ func (w *Wallet) InitializeKeymanager(
if err != nil {
return nil, errors.Wrap(err, "could not initialize derived keymanager")
}
if !fileutil.FileExists(filepath.Join(w.walletDir, HashedPasswordFileName)) {
if err := w.SaveHashedPassword(ctx); err != nil {
return nil, errors.Wrap(err, "could not save hashed password to disk")
}
}
case keymanager.Remote:
opts, err := remote.UnmarshalOptionsFile(configFile)
if err != nil {
@@ -471,20 +436,6 @@ func (w *Wallet) WriteEncryptedSeedToDisk(_ context.Context, encoded []byte) err
return nil
}
// SaveHashedPassword to disk for the wallet.
func (w *Wallet) SaveHashedPassword(_ context.Context) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(w.walletPassword), hashCost)
if err != nil {
return errors.Wrap(err, "could not generate hashed password")
}
hashFilePath := filepath.Join(w.walletDir, HashedPasswordFileName)
// Write the config file to disk.
if err := ioutil.WriteFile(hashFilePath, hashedPassword, params.BeaconIoConfig().ReadWritePermissions); err != nil {
return errors.Wrap(err, "could not write hashed password for wallet to disk")
}
return nil
}
func readKeymanagerKindFromWalletPath(walletPath string) (keymanager.Kind, error) {
walletItem, err := os.Open(walletPath)
if err != nil {

View File

@@ -51,10 +51,6 @@ func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) {
if err != nil {
return nil, errors.Wrap(err, "could not create wallet with keymanager")
}
// We store the hashed password to disk.
if err := w.SaveHashedPassword(cliCtx.Context); err != nil {
return nil, errors.Wrap(err, "could not save hashed password to database")
}
return w, nil
}

View File

@@ -58,7 +58,7 @@ func RecoverWalletCli(cliCtx *cli.Context) error {
if err != nil {
return errors.Wrap(err, "could not get number of accounts to recover")
}
w, _, err := RecoverWallet(cliCtx.Context, &RecoverWalletConfig{
_, _, err = RecoverWallet(cliCtx.Context, &RecoverWalletConfig{
WalletDir: walletDir,
WalletPassword: walletPassword,
Mnemonic: mnemonic,
@@ -67,10 +67,6 @@ func RecoverWalletCli(cliCtx *cli.Context) error {
if err != nil {
return err
}
// We store the hashed password to disk.
if err := w.SaveHashedPassword(cliCtx.Context); err != nil {
return err
}
log.Infof(
"Successfully recovered HD wallet and saved configuration to disk. " +
"Make a new validator account with ./prysm.sh validator accounts create",

View File

@@ -90,6 +90,9 @@ func (v *validator) WaitForWalletInitialization(ctx context.Context) error {
if !v.useWeb {
return nil
}
if v.keyManager != nil {
return nil
}
walletChan := make(chan *wallet.Wallet)
sub := v.walletInitializedFeed.Subscribe(walletChan)
defer sub.Unsubscribe()

View File

@@ -432,12 +432,6 @@ func (dr *Keymanager) DepositDataForAccount(accountIndex uint64) ([]byte, error)
return tx.Data(), nil
}
// RefreshWalletPassword encrypts the seed config with the wallet password and
// writes it to disk, such as when the wallet password was modified by the user.
func (dr *Keymanager) RefreshWalletPassword(ctx context.Context) error {
return dr.rewriteSeedConfig(ctx)
}
func (dr *Keymanager) rewriteSeedConfig(ctx context.Context) error {
encryptor := keystorev4.New()
encryptedFields, err := encryptor.Encrypt(dr.seed, dr.wallet.Password())

View File

@@ -270,47 +270,3 @@ func TestDerivedKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
_, err := dr.Sign(context.Background(), req)
assert.ErrorContains(t, "no signing key found", err)
}
func TestDerivedKeymanager_RefreshWalletPassword(t *testing.T) {
password := "secretPassw0rd$1999"
wallet := &mock.Wallet{
Files: make(map[string]map[string][]byte),
AccountPasswords: make(map[string]string),
WalletPassword: password,
}
dr := &Keymanager{
wallet: wallet,
opts: DefaultKeymanagerOpts(),
}
seedCfg, err := initializeWalletSeedFile(wallet.Password(), true /* skip mnemonic confirm */)
require.NoError(t, err)
dr.seedCfg = seedCfg
decryptor := keystorev4.New()
seed, err := decryptor.Decrypt(dr.seedCfg.Crypto, wallet.Password())
require.NoError(t, err)
dr.seed = seed
require.NoError(t, dr.initializeKeysCachesFromSeed())
// First, generate some accounts.
numAccounts := 2
ctx := context.Background()
for i := 0; i < numAccounts; i++ {
_, _, err := dr.CreateAccount(ctx)
require.NoError(t, err)
}
// We attempt to decrypt with the wallet password and expect no error.
_, err = decryptor.Decrypt(dr.seedCfg.Crypto, dr.wallet.Password())
require.NoError(t, err)
// We change the wallet password.
wallet.WalletPassword = "NewPassw0rdz9**#"
// Attempting to decrypt with this new wallet password should fail.
_, err = decryptor.Decrypt(dr.seedCfg.Crypto, dr.wallet.Password())
require.ErrorContains(t, "invalid checksum", err)
// Call the refresh wallet password method, then attempting to decrypt should work.
require.NoError(t, dr.RefreshWalletPassword(ctx))
_, err = decryptor.Decrypt(dr.seedCfg.Crypto, dr.wallet.Password())
require.NoError(t, err)
}

View File

@@ -311,12 +311,6 @@ func (dr *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (b
return secretKey.Sign(req.SigningRoot), nil
}
// RefreshWalletPassword re-encrypts the accounts store and stores
// it to disk using a wallet's password which was recently changed.
func (dr *Keymanager) RefreshWalletPassword(ctx context.Context) error {
return dr.rewriteAccountsKeystore(ctx)
}
func (dr *Keymanager) rewriteAccountsKeystore(ctx context.Context) error {
newStore, err := dr.createAccountsKeystore(ctx, dr.accountsStore.PrivateKeys, dr.accountsStore.PublicKeys)
if err != nil {

View File

@@ -218,56 +218,3 @@ func TestImportedKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
_, err := dr.Sign(context.Background(), req)
assert.ErrorContains(t, "no signing key found in keys cache", err)
}
func TestImportedKeymanager_RefreshWalletPassword(t *testing.T) {
password := "secretPassw0rd$1999"
wallet := &mock.Wallet{
Files: make(map[string]map[string][]byte),
AccountPasswords: make(map[string]string),
WalletPassword: password,
}
dr := &Keymanager{
wallet: wallet,
accountsStore: &AccountStore{},
}
ctx := context.Background()
numAccounts := 5
keystores := make([]*keymanager.Keystore, numAccounts)
for i := 0; i < numAccounts; i++ {
keystores[i] = createRandomKeystore(t, password)
}
require.NoError(t, dr.ImportKeystores(ctx, keystores, password))
var encodedKeystore []byte
for k, v := range wallet.Files[AccountsPath] {
if strings.Contains(k, "keystore") {
encodedKeystore = v
}
}
keystoreFile := &keymanager.Keystore{}
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
// We attempt to decrypt with the wallet password and expect no error.
decryptor := keystorev4.New()
_, err := decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
require.NoError(t, err)
// We change the wallet password.
wallet.WalletPassword = "NewPassw0rdz9**#"
// Attempting to decrypt with this new wallet password should fail.
_, err = decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
require.ErrorContains(t, "invalid checksum", err)
// Call the refresh wallet password method, then attempting to decrypt should work.
require.NoError(t, dr.RefreshWalletPassword(ctx))
for k, v := range wallet.Files[AccountsPath] {
if strings.Contains(k, "keystore") {
encodedKeystore = v
}
}
keystoreFile = &keymanager.Keystore{}
require.NoError(t, json.Unmarshal(encodedKeystore, keystoreFile))
_, err = decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
require.NoError(t, err)
}

View File

@@ -263,6 +263,32 @@ func moveDb(cliCtx *cli.Context, accountsDir string) string {
}
func (s *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error {
var keyManager keymanager.IKeymanager
var err error
// Read the wallet from the specified path.
w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
return nil, nil
})
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
if w != nil {
s.wallet = w
log.WithFields(logrus.Fields{
"wallet": w.AccountsDir(),
"keymanager-kind": w.KeymanagerKind().String(),
}).Info("Opened validator wallet")
keyManager, err = w.InitializeKeymanager(
cliCtx.Context, false, /* skipMnemonicConfirm */
)
if err != nil {
return errors.Wrap(err, "could not read keymanager for wallet")
}
if err := w.LockWalletConfigFile(cliCtx.Context); err != nil {
log.Fatalf("Could not get a lock on wallet file. Please check if you have another validator instance running and using the same wallet: %v", err)
}
}
clearFlag := cliCtx.Bool(cmd.ClearDB.Name)
forceClearFlag := cliCtx.Bool(cmd.ForceClearDB.Name)
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
@@ -297,7 +323,7 @@ func (s *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error {
return err
}
}
if err := s.registerClientService(nil); err != nil {
if err := s.registerClientService(keyManager); err != nil {
return err
}
if err := s.registerRPCService(cliCtx); err != nil {

View File

@@ -47,7 +47,7 @@ func TestNode_Builds(t *testing.T) {
set.String("verbosity", "debug", "log verbosity")
require.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, passwordFile))
context := cli.NewContext(&app, set, nil)
w, err := accounts.CreateWalletWithKeymanager(context.Context, &accounts.CreateWalletConfig{
_, err := accounts.CreateWalletWithKeymanager(context.Context, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: dir,
KeymanagerKind: keymanager.Imported,
@@ -55,7 +55,6 @@ func TestNode_Builds(t *testing.T) {
},
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(context.Context))
valClient, err := NewValidatorClient(context)
require.NoError(t, err, "Failed to create ValidatorClient")

View File

@@ -213,7 +213,6 @@ func TestServer_DeleteAccounts_FailedPreconditions_WrongKeymanagerKind(t *testin
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{

View File

@@ -9,13 +9,13 @@ import (
"time"
"github.com/dgrijalva/jwt-go"
ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/promptutil"
"github.com/prysmaticlabs/prysm/shared/timeutils"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -26,6 +26,11 @@ var (
hashCost = 8
)
const (
// HashedRPCPassword for the validator RPC access.
HashedRPCPassword = "rpc-password-hash"
)
// Signup to authenticate access to the validator RPC API using bcrypt and
// a sufficiently strong password check.
func (s *Server) Signup(ctx context.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) {
@@ -35,25 +40,26 @@ func (s *Server) Signup(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespo
}
// First, we check if the validator already has a password. In this case,
// the user should be logged in as normal.
if fileutil.FileExists(filepath.Join(walletDir, wallet.HashedPasswordFileName)) {
if fileutil.FileExists(filepath.Join(walletDir, HashedRPCPassword)) {
return s.Login(ctx, req)
}
// We check the strength of the password to ensure it is high-entropy,
// has the required character count, and contains only unicode characters.
if err := promptutil.ValidatePasswordInput(req.Password); err != nil {
return nil, status.Error(codes.InvalidArgument, "Could not validate wallet password input")
return nil, status.Error(codes.InvalidArgument, "Could not validate RPC password input")
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), hashCost)
hasDir, err := fileutil.HasDir(walletDir)
if err != nil {
return nil, errors.Wrap(err, "could not generate hashed password")
return nil, status.Error(codes.FailedPrecondition, "Could not check if wallet directory exists")
}
hashFilePath := filepath.Join(walletDir, wallet.HashedPasswordFileName)
// Write the config file to disk.
if err := os.MkdirAll(walletDir, os.ModePerm); err != nil {
return nil, status.Error(codes.Internal, err.Error())
if !hasDir {
if err := os.MkdirAll(walletDir, params.BeaconIoConfig().ReadWritePermissions); err != nil {
return nil, status.Errorf(codes.Internal, "could not write directory %s to disk: %v", walletDir, err)
}
}
if err := ioutil.WriteFile(hashFilePath, hashedPassword, params.BeaconIoConfig().ReadWritePermissions); err != nil {
return nil, status.Errorf(codes.Internal, "could not write hashed password for wallet to disk: %v", err)
// Write the password hash to disk.
if err := s.SaveHashedPassword(req.Password); err != nil {
return nil, status.Errorf(codes.Internal, "could not write hashed password to disk: %v", err)
}
return s.sendAuthResponse()
}
@@ -67,9 +73,9 @@ func (s *Server) Login(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespon
// We check the strength of the password to ensure it is high-entropy,
// has the required character count, and contains only unicode characters.
if err := promptutil.ValidatePasswordInput(req.Password); err != nil {
return nil, status.Error(codes.InvalidArgument, "Could not validate wallet password input")
return nil, status.Error(codes.InvalidArgument, "Could not validate RPC password input")
}
hashedPasswordPath := filepath.Join(walletDir, wallet.HashedPasswordFileName)
hashedPasswordPath := filepath.Join(walletDir, HashedRPCPassword)
if !fileutil.FileExists(hashedPasswordPath) {
return nil, status.Error(codes.Internal, "Could not find hashed password on disk")
}
@@ -79,16 +85,7 @@ func (s *Server) Login(ctx context.Context, req *pb.AuthRequest) (*pb.AuthRespon
}
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.Password)); err != nil {
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
}
if err := s.initializeWallet(ctx, &wallet.Config{
WalletDir: walletDir,
WalletPassword: req.Password,
}); err != nil {
if strings.Contains(err.Error(), "invalid checksum") {
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
}
return nil, status.Errorf(codes.Internal, "Could not initialize wallet: %v", err)
return nil, status.Error(codes.Unauthenticated, "Incorrect validator RPC password")
}
return s.sendAuthResponse()
}
@@ -106,6 +103,35 @@ func (s *Server) sendAuthResponse() (*pb.AuthResponse, error) {
}, nil
}
// ChangePassword allows changing the RPC password via the API as an authenticated method.
func (s *Server) ChangePassword(ctx context.Context, req *pb.ChangePasswordRequest) (*ptypes.Empty, error) {
if req.CurrentPassword == "" {
return nil, status.Error(codes.InvalidArgument, "Current password cannot be empty")
}
hashedPasswordPath := filepath.Join(s.walletDir, HashedRPCPassword)
if !fileutil.FileExists(hashedPasswordPath) {
return nil, status.Error(codes.FailedPrecondition, "Could not compare password from disk")
}
hashedPassword, err := fileutil.ReadFileAsBytes(hashedPasswordPath)
if err != nil {
return nil, status.Error(codes.FailedPrecondition, "Could not retrieve hashed password from disk")
}
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.CurrentPassword)); err != nil {
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
}
if req.Password != req.PasswordConfirmation {
return nil, status.Error(codes.InvalidArgument, "Password does not match confirmation")
}
if err := promptutil.ValidatePasswordInput(req.Password); err != nil {
return nil, status.Error(codes.InvalidArgument, "Could not validate password input")
}
// Write the new password hash to disk.
if err := s.SaveHashedPassword(req.Password); err != nil {
return nil, status.Errorf(codes.Internal, "could not write hashed password to disk: %v", err)
}
return &ptypes.Empty{}, nil
}
// Creates a JWT token string using the JWT key with an expiration timestamp.
func (s *Server) createTokenString() (string, uint64, error) {
// Create a new token object, specifying signing method and the claims
@@ -122,53 +148,12 @@ func (s *Server) createTokenString() (string, uint64, error) {
return tokenString, uint64(expirationTime.Unix()), nil
}
// Initialize a wallet and send it over a global feed.
func (s *Server) initializeWallet(ctx context.Context, cfg *wallet.Config) error {
// We first ensure the user has a wallet.
exists, err := wallet.Exists(cfg.WalletDir)
// SaveHashedPassword to disk for the validator RPC.
func (s *Server) SaveHashedPassword(password string) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), hashCost)
if err != nil {
return errors.Wrap(err, wallet.CheckExistsErrMsg)
return errors.Wrap(err, "could not generate hashed password")
}
if !exists {
return wallet.ErrNoWalletFound
}
valid, err := wallet.IsValid(cfg.WalletDir)
if errors.Is(err, wallet.ErrNoWalletFound) {
return wallet.ErrNoWalletFound
}
if err != nil {
return errors.Wrap(err, wallet.CheckValidityErrMsg)
}
if !valid {
return errors.New(wallet.InvalidWalletErrMsg)
}
// We fire an event with the opened wallet over
// a global feed signifying wallet initialization.
w, err := wallet.OpenWallet(ctx, &wallet.Config{
WalletDir: cfg.WalletDir,
WalletPassword: cfg.WalletPassword,
})
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
s.walletInitialized = true
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
if err != nil {
return errors.Wrap(err, "could not initialize keymanager")
}
s.keymanager = km
s.wallet = w
s.walletDir = cfg.WalletDir
// Only send over feed if we have validating keys.
validatingPublicKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not check for validating public keys")
}
if len(validatingPublicKeys) > 0 {
s.walletInitializedFeed.Send(w)
}
return nil
hashFilePath := filepath.Join(s.walletDir, HashedRPCPassword)
return ioutil.WriteFile(hashFilePath, hashedPassword, params.BeaconIoConfig().ReadWritePermissions)
}

View File

@@ -49,7 +49,7 @@ func TestServer_SignupAndLogin_RoundTrip(t *testing.T) {
_, err := ss.Signup(ctx, &pb.AuthRequest{
Password: weakPass,
})
require.ErrorContains(t, "Could not validate wallet password input", err)
require.ErrorContains(t, "Could not validate RPC password input", err)
// We assert we are able to signup with a strong password.
_, err = ss.Signup(ctx, &pb.AuthRequest{
@@ -58,7 +58,7 @@ func TestServer_SignupAndLogin_RoundTrip(t *testing.T) {
require.NoError(t, err)
// Assert we stored the hashed password.
passwordHashExists := fileutil.FileExists(filepath.Join(defaultWalletPath, wallet.HashedPasswordFileName))
passwordHashExists := fileutil.FileExists(filepath.Join(defaultWalletPath, HashedRPCPassword))
assert.Equal(t, true, passwordHashExists)
// We attempt to create the wallet.
@@ -78,3 +78,47 @@ func TestServer_SignupAndLogin_RoundTrip(t *testing.T) {
})
require.NoError(t, err)
}
func TestServer_ChangePassword_Preconditions(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
ss := &Server{
walletDir: defaultWalletPath,
}
require.NoError(t, ss.SaveHashedPassword(strongPass))
_, err := ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: "",
})
assert.ErrorContains(t, "Could not validate password input", err)
_, err = ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: "abc",
PasswordConfirmation: "def",
})
assert.ErrorContains(t, "does not match", err)
}
func TestServer_ChangePassword_OK(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ss := &Server{
walletDir: defaultWalletPath,
}
password := "Passw0rdz%%%%pass"
newPassword := "NewPassw0rdz%%%%pass"
ctx := context.Background()
require.NoError(t, ss.SaveHashedPassword(password))
_, err := ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: password,
Password: newPassword,
PasswordConfirmation: newPassword,
})
require.NoError(t, err)
_, err = ss.Login(ctx, &pb.AuthRequest{
Password: newPassword,
})
require.NoError(t, err)
}

View File

@@ -4,23 +4,18 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"io/ioutil"
"path/filepath"
"strings"
ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/promptutil"
"github.com/prysmaticlabs/prysm/shared/rand"
"github.com/prysmaticlabs/prysm/validator/accounts"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/validator/keymanager"
"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -83,7 +78,7 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
}
switch req.Keymanager {
case pb.KeymanagerKind_IMPORTED:
w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
_, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Imported,
@@ -94,9 +89,6 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
if err != nil {
return nil, err
}
if err := w.SaveHashedPassword(ctx); err != nil {
return nil, err
}
if err := s.initializeWallet(ctx, &wallet.Config{
WalletDir: walletDir,
KeymanagerKind: keymanager.Imported,
@@ -237,77 +229,6 @@ func (s *Server) GenerateMnemonic(_ context.Context, _ *ptypes.Empty) (*pb.Gener
}, nil
}
// ChangePassword allows changing a wallet password via the API as
// an authenticated method.
func (s *Server) ChangePassword(ctx context.Context, req *pb.ChangePasswordRequest) (*ptypes.Empty, error) {
exists, err := wallet.Exists(s.walletDir)
if err != nil {
return nil, status.Errorf(codes.Internal, checkExistsErrMsg)
}
if !exists {
return nil, status.Errorf(codes.FailedPrecondition, noWalletMsg)
}
valid, err := wallet.IsValid(s.walletDir)
if errors.Is(err, wallet.ErrNoWalletFound) {
return nil, status.Errorf(codes.FailedPrecondition, noWalletMsg)
}
if err != nil {
return nil, status.Errorf(codes.Internal, checkValidityErrMsg)
}
if !valid {
return nil, status.Errorf(codes.FailedPrecondition, invalidWalletMsg)
}
if req.CurrentPassword == "" {
return nil, status.Error(codes.InvalidArgument, "Current wallet password cannot be empty")
}
hashedPasswordPath := filepath.Join(s.walletDir, wallet.HashedPasswordFileName)
if !fileutil.FileExists(hashedPasswordPath) {
return nil, status.Error(codes.FailedPrecondition, "Could not compare password from disk")
}
hashedPassword, err := fileutil.ReadFileAsBytes(hashedPasswordPath)
if err != nil {
return nil, status.Error(codes.FailedPrecondition, "Could not retrieve hashed password from disk")
}
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.CurrentPassword)); err != nil {
return nil, status.Error(codes.Unauthenticated, "Incorrect wallet password")
}
if req.Password != req.PasswordConfirmation {
return nil, status.Error(codes.InvalidArgument, "Password does not match confirmation")
}
if err := promptutil.ValidatePasswordInput(req.Password); err != nil {
return nil, status.Error(codes.InvalidArgument, "Could not validate wallet password input")
}
switch s.wallet.KeymanagerKind() {
case keymanager.Imported:
km, ok := s.keymanager.(*imported.Keymanager)
if !ok {
return nil, status.Error(codes.FailedPrecondition, "Not a valid imported keymanager")
}
s.wallet.SetPassword(req.Password)
if err := s.wallet.SaveHashedPassword(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "Could not save hashed password: %v", err)
}
if err := km.RefreshWalletPassword(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "Could not refresh wallet password: %v", err)
}
case keymanager.Derived:
km, ok := s.keymanager.(*derived.Keymanager)
if !ok {
return nil, status.Error(codes.FailedPrecondition, "Not a valid derived keymanager")
}
s.wallet.SetPassword(req.Password)
if err := s.wallet.SaveHashedPassword(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "Could not save hashed password: %v", err)
}
if err := km.RefreshWalletPassword(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "Could not refresh wallet password: %v", err)
}
case keymanager.Remote:
return nil, status.Error(codes.Internal, "Cannot change password for remote keymanager")
}
return &ptypes.Empty{}, nil
}
// ImportKeystores allows importing new keystores via RPC into the wallet
// which will be decrypted using the specified password .
func (s *Server) ImportKeystores(
@@ -355,3 +276,54 @@ func (s *Server) ImportKeystores(
ImportedPublicKeys: importedPubKeys,
}, nil
}
// Initialize a wallet and send it over a global feed.
func (s *Server) initializeWallet(ctx context.Context, cfg *wallet.Config) error {
// We first ensure the user has a wallet.
exists, err := wallet.Exists(cfg.WalletDir)
if err != nil {
return errors.Wrap(err, wallet.CheckExistsErrMsg)
}
if !exists {
return wallet.ErrNoWalletFound
}
valid, err := wallet.IsValid(cfg.WalletDir)
if errors.Is(err, wallet.ErrNoWalletFound) {
return wallet.ErrNoWalletFound
}
if err != nil {
return errors.Wrap(err, wallet.CheckValidityErrMsg)
}
if !valid {
return errors.New(wallet.InvalidWalletErrMsg)
}
// We fire an event with the opened wallet over
// a global feed signifying wallet initialization.
w, err := wallet.OpenWallet(ctx, &wallet.Config{
WalletDir: cfg.WalletDir,
WalletPassword: cfg.WalletPassword,
})
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
s.walletInitialized = true
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
if err != nil {
return errors.Wrap(err, "could not initialize keymanager")
}
s.keymanager = km
s.wallet = w
s.walletDir = cfg.WalletDir
// Only send over feed if we have validating keys.
validatingPublicKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not check for validating public keys")
}
if len(validatingPublicKeys) > 0 {
s.walletInitializedFeed.Send(w)
}
return nil
}

View File

@@ -35,7 +35,6 @@ func createImportedWalletWithAccounts(t testing.TB, numAccounts int) (*Server, [
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
@@ -192,7 +191,6 @@ func TestServer_WalletConfig(t *testing.T) {
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
s.wallet = w
@@ -212,92 +210,6 @@ func TestServer_WalletConfig(t *testing.T) {
})
}
func TestServer_ChangePassword_Preconditions(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
ss := &Server{
walletDir: defaultWalletPath,
}
_, err := ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: "",
})
assert.ErrorContains(t, noWalletMsg, err)
// We attempt to create the wallet.
w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: keymanager.Derived,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss.wallet = w
ss.walletInitialized = true
ss.keymanager = km
_, err = ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: "",
})
assert.ErrorContains(t, "Could not validate wallet password", err)
_, err = ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: "abc",
PasswordConfirmation: "def",
})
assert.ErrorContains(t, "does not match", err)
}
func TestServer_ChangePassword_ImportedKeymanager(t *testing.T) {
ss, _ := createImportedWalletWithAccounts(t, 1)
newPassword := "NewPassw0rdz%%%%pass"
_, err := ss.ChangePassword(context.Background(), &pb.ChangePasswordRequest{
CurrentPassword: ss.wallet.Password(),
Password: newPassword,
PasswordConfirmation: newPassword,
})
require.NoError(t, err)
assert.Equal(t, ss.wallet.Password(), newPassword)
}
func TestServer_ChangePassword_DerivedKeymanager(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
ctx := context.Background()
strongPass := "29384283xasjasd32%%&*@*#*"
// We attempt to create the wallet.
w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: keymanager.Derived,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
walletDir: defaultWalletPath,
}
ss.wallet = w
ss.walletInitialized = true
ss.keymanager = km
newPassword := "NewPassw0rdz%%%%pass"
_, err = ss.ChangePassword(ctx, &pb.ChangePasswordRequest{
CurrentPassword: strongPass,
Password: newPassword,
PasswordConfirmation: newPassword,
})
require.NoError(t, err)
assert.Equal(t, w.Password(), newPassword)
}
func TestServer_HasWallet(t *testing.T) {
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
@@ -378,7 +290,6 @@ func TestServer_ImportKeystores_FailedPreconditions(t *testing.T) {
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{
@@ -415,7 +326,6 @@ func TestServer_ImportKeystores_OK(t *testing.T) {
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
require.NoError(t, w.SaveHashedPassword(ctx))
km, err := w.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
require.NoError(t, err)
ss := &Server{

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/prysmaticlabs/prysm/shared"
"github.com/prysmaticlabs/prysm/shared/browser"
"github.com/sirupsen/logrus"
)
@@ -36,21 +35,13 @@ func NewServer(addr string) *Server {
// Start the web server.
func (s *Server) Start() {
go func() {
log.WithField("address", s.http.Addr).Info("Starting Prysm web UI")
log.WithField("address", "http://"+s.http.Addr).Info(
"Starting Prysm web UI on address, open in browser to access",
)
if err := s.http.ListenAndServe(); err != nil {
log.WithError(err).Error("Failed to start validator web server")
log.WithError(err).Error("Failed to run validator web server")
}
}()
time.Sleep(time.Second * 1)
cmd, err := browser.Command("http://" + s.http.Addr)
if err != nil {
log.WithError(err).Errorf("Could not open Prysm web UI in browser")
return
}
if err := cmd.Run(); err != nil {
log.WithError(err).Errorf("Could not open Prysm web UI in browser")
return
}
}
// Stop the web server gracefully with 1s timeout.