mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-17 08:56:45 +00:00
Merge different places of keyboard input to keyboardCreatePassword() and keyboardGetPassword().
Fix Windows problem with english words array. Add checksum for english words array verification.
This commit is contained in:
parent
d2f8cf9386
commit
9441e912cb
@ -1,5 +1,5 @@
|
|||||||
import
|
import
|
||||||
std/[os, strutils, terminal, wordwrap],
|
std/[os, strutils, terminal, wordwrap, unicode],
|
||||||
chronicles, chronos, web3, stint, json_serialization,
|
chronicles, chronos, web3, stint, json_serialization,
|
||||||
serialization, blscurve, eth/common/eth_types, eth/keys, confutils, bearssl,
|
serialization, blscurve, eth/common/eth_types, eth/keys, confutils, bearssl,
|
||||||
spec/[datatypes, digest, crypto, keystore],
|
spec/[datatypes, digest, crypto, keystore],
|
||||||
@ -116,6 +116,64 @@ proc checkFilePermissions*(filePath: string): bool =
|
|||||||
else:
|
else:
|
||||||
true
|
true
|
||||||
|
|
||||||
|
proc keyboardCreatePassword(prompt: string, confirm: string): KsResult[string] =
|
||||||
|
while true:
|
||||||
|
let password =
|
||||||
|
try:
|
||||||
|
readPasswordFromStdin(prompt)
|
||||||
|
except IOError:
|
||||||
|
error "Could not read password from stdin"
|
||||||
|
return err("Could not read password from stdin")
|
||||||
|
|
||||||
|
# We treat `password` as UTF-8 encoded string.
|
||||||
|
if validateUtf8(password) == -1:
|
||||||
|
if runeLen(password) < minPasswordLen:
|
||||||
|
echo80 "The entered password should be at least " & $minPasswordLen &
|
||||||
|
" characters."
|
||||||
|
continue
|
||||||
|
elif password in mostCommonPasswords:
|
||||||
|
echo80 "The entered password is too commonly used and it would be " &
|
||||||
|
"easy to brute-force with automated tools."
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
echo80 "Entered password is not valid UTF-8 string"
|
||||||
|
continue
|
||||||
|
|
||||||
|
let confirmedPassword =
|
||||||
|
try:
|
||||||
|
readPasswordFromStdin(confirm)
|
||||||
|
except IOError:
|
||||||
|
error "Could not read password from stdin"
|
||||||
|
return err("Could not read password from stdin")
|
||||||
|
|
||||||
|
if password != confirmedPassword:
|
||||||
|
echo "Passwords don't match, please try again"
|
||||||
|
continue
|
||||||
|
|
||||||
|
return ok(password)
|
||||||
|
|
||||||
|
proc keyboardGetPassword[T](prompt: string, attempts: int,
|
||||||
|
pred: proc(p: string): KsResult[T] {.closure.}): KsResult[T] =
|
||||||
|
var
|
||||||
|
remainingAttempts = attempts
|
||||||
|
counter = 1
|
||||||
|
|
||||||
|
while remainingAttempts > 0:
|
||||||
|
let passphrase =
|
||||||
|
try:
|
||||||
|
readPasswordFromStdin(prompt)
|
||||||
|
except IOError as exc:
|
||||||
|
error "Could not read password from stdin"
|
||||||
|
return
|
||||||
|
os.sleep(1000 * counter)
|
||||||
|
let res = pred(passphrase)
|
||||||
|
if res.isOk():
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
inc(counter)
|
||||||
|
dec(remainingAttempts)
|
||||||
|
err("Failed to decrypt keystore")
|
||||||
|
|
||||||
proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
||||||
nonInteractive: bool): Option[ValidatorPrivKey] =
|
nonInteractive: bool): Option[ValidatorPrivKey] =
|
||||||
let
|
let
|
||||||
@ -155,21 +213,19 @@ proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
|||||||
keyName, validatorsDir, secretsDir = secretsDir
|
keyName, validatorsDir, secretsDir = secretsDir
|
||||||
return
|
return
|
||||||
|
|
||||||
var remainingAttempts = 3
|
let prompt = "Please enter passphrase for key \"" &
|
||||||
var prompt = "Please enter passphrase for key \"" & validatorsDir/keyName & "\"\n"
|
(validatorsDir / keyName) & "\": "
|
||||||
while remainingAttempts > 0:
|
let res = keyboardGetPassword[ValidatorPrivKey](prompt, 3,
|
||||||
let passphrase = KeystorePass:
|
proc (password: string): KsResult[ValidatorPrivKey] =
|
||||||
try: readPasswordFromStdin(prompt)
|
let decrypted = decryptKeystore(keystore, KeystorePass password)
|
||||||
except IOError:
|
if decrypted.isErr():
|
||||||
error "STDIN not readable. Cannot obtain Keystore password"
|
error "Keystore decryption failed. Please try again", keystorePath
|
||||||
return
|
decrypted
|
||||||
|
)
|
||||||
let decrypted = decryptKeystore(keystore, passphrase)
|
if res.isOk():
|
||||||
if decrypted.isOk:
|
some(res.get())
|
||||||
return decrypted.get.some
|
else:
|
||||||
else:
|
return
|
||||||
prompt = "Keystore decryption failed. Please try again"
|
|
||||||
dec remainingAttempts
|
|
||||||
|
|
||||||
iterator validatorKeysFromDirs*(validatorsDir, secretsDir: string): ValidatorPrivKey =
|
iterator validatorKeysFromDirs*(validatorsDir, secretsDir: string): ValidatorPrivKey =
|
||||||
try:
|
try:
|
||||||
@ -244,67 +300,34 @@ proc loadNetKeystore*(keyStorePath: string,
|
|||||||
error "Network keystore decryption failed", key_store = keyStorePath
|
error "Network keystore decryption failed", key_store = keyStorePath
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
var remainingAttempts = 3
|
let prompt = "Please enter passphrase to unlock networking key: "
|
||||||
var counter = 0
|
let res = keyboardGetPassword[lcrypto.PrivateKey](prompt, 3,
|
||||||
var prompt = "Please enter passphrase to unlock networking key: "
|
proc (password: string): KsResult[lcrypto.PrivateKey] =
|
||||||
while remainingAttempts > 0:
|
let decrypted = decryptNetKeystore(keystore, KeystorePass password)
|
||||||
let passphrase = KeystorePass:
|
if decrypted.isErr():
|
||||||
try:
|
error "Keystore decryption failed. Please try again", keystorePath
|
||||||
readPasswordFromStdin(prompt)
|
decrypted
|
||||||
except IOError:
|
)
|
||||||
error "Could not read password from stdin"
|
if res.isOk():
|
||||||
return
|
some(res.get())
|
||||||
|
else:
|
||||||
let decrypted = decryptNetKeystore(keystore, passphrase)
|
return
|
||||||
if decrypted.isOk:
|
|
||||||
return some(decrypted.get())
|
|
||||||
else:
|
|
||||||
dec remainingAttempts
|
|
||||||
inc counter
|
|
||||||
os.sleep(1000 * counter)
|
|
||||||
error "Network keystore decryption failed", key_store = keyStorePath
|
|
||||||
|
|
||||||
proc saveNetKeystore*(rng: var BrHmacDrbgContext, keyStorePath: string,
|
proc saveNetKeystore*(rng: var BrHmacDrbgContext, keyStorePath: string,
|
||||||
netKey: lcrypto.PrivateKey, insecurePwd: Option[string]
|
netKey: lcrypto.PrivateKey, insecurePwd: Option[string]
|
||||||
): Result[void, KeystoreGenerationError] =
|
): Result[void, KeystoreGenerationError] =
|
||||||
var password, confirmedPassword: TaintedString
|
let password =
|
||||||
if insecurePwd.isSome():
|
if insecurePwd.isSome():
|
||||||
warn "Using insecure password to lock networking key",
|
warn "Using insecure password to lock networking key",
|
||||||
key_path = keyStorePath
|
key_path = keyStorePath
|
||||||
password = insecurePwd.get()
|
insecurePwd.get()
|
||||||
else:
|
else:
|
||||||
while true:
|
|
||||||
let prompt = "Please enter NEW password to lock network key storage: "
|
let prompt = "Please enter NEW password to lock network key storage: "
|
||||||
|
let confirm = "Please confirm, network key storage password: "
|
||||||
password =
|
let res = keyboardCreatePassword(prompt, confirm)
|
||||||
try:
|
if res.isErr():
|
||||||
readPasswordFromStdin(prompt)
|
return err(FailedToCreateKeystoreFile)
|
||||||
except IOError:
|
res.get()
|
||||||
error "Could not read password from stdin"
|
|
||||||
return err(FailedToCreateKeystoreFile)
|
|
||||||
|
|
||||||
if len(password) < minPasswordLen:
|
|
||||||
echo "The entered password should be at least ", minPasswordLen,
|
|
||||||
" characters"
|
|
||||||
continue
|
|
||||||
elif password in mostCommonPasswords:
|
|
||||||
echo80 "The entered password is too commonly used and it would be " &
|
|
||||||
"easy to brute-force with automated tools."
|
|
||||||
continue
|
|
||||||
|
|
||||||
confirmedPassword =
|
|
||||||
try:
|
|
||||||
readPasswordFromStdin("Please confirm, network key storage " &
|
|
||||||
"password: ")
|
|
||||||
except IOError:
|
|
||||||
error "Could not read password from stdin"
|
|
||||||
return err(FailedToCreateKeystoreFile)
|
|
||||||
|
|
||||||
if password != confirmedPassword:
|
|
||||||
echo "Passwords don't match, please try again"
|
|
||||||
continue
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
let keyStore = createNetKeystore(kdfScrypt, rng, netKey,
|
let keyStore = createNetKeystore(kdfScrypt, rng, netKey,
|
||||||
KeystorePass password)
|
KeystorePass password)
|
||||||
@ -526,87 +549,64 @@ proc pickPasswordAndSaveWallet(rng: var BrHmacDrbgContext,
|
|||||||
"installation and can be changed at any time."
|
"installation and can be changed at any time."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
while true:
|
let password =
|
||||||
var password, confirmedPassword: TaintedString
|
block:
|
||||||
try:
|
let prompt = "Please enter a password: "
|
||||||
var firstTry = true
|
let confirm = "Please repeat the password: "
|
||||||
|
let res = keyboardCreatePassword(prompt, confirm)
|
||||||
|
if res.isErr():
|
||||||
|
return err($res.error)
|
||||||
|
res.get()
|
||||||
|
|
||||||
template prompt: string =
|
var name: WalletName
|
||||||
if firstTry:
|
let outWalletName = config.outWalletName
|
||||||
"Please enter a password: "
|
if outWalletName.isSome:
|
||||||
else:
|
name = outWalletName.get
|
||||||
"Please enter a new password: "
|
else:
|
||||||
|
echo ""
|
||||||
|
echo80 "For your convenience, the wallet can be identified with a name " &
|
||||||
|
"of your choice. Please enter a wallet name below or press ENTER " &
|
||||||
|
"to continue with a machine-generated name."
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
if not readPasswordInput(prompt, password):
|
var enteredName = ask "Wallet name"
|
||||||
return err "failure to read a password from stdin"
|
if enteredName.len > 0:
|
||||||
|
name =
|
||||||
if password.len < minPasswordLen:
|
|
||||||
try:
|
try:
|
||||||
echo "The entered password should be at least $1 characters." %
|
WalletName.parseCmdArg(enteredName)
|
||||||
[$minPasswordLen]
|
except CatchableError as err:
|
||||||
except ValueError:
|
echo err.msg & ". Please try again."
|
||||||
raiseAssert "The format string above is correct"
|
continue
|
||||||
elif password in mostCommonPasswords:
|
break
|
||||||
echo80 "The entered password is too commonly used and it would be easy " &
|
|
||||||
"to brute-force with automated tools."
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
firstTry = false
|
let nextAccount =
|
||||||
|
if config.cmd == wallets and config.walletsCmd == WalletsCmd.restore:
|
||||||
if not readPasswordInput("Please repeat the password:", confirmedPassword):
|
|
||||||
return err "failure to read a password from stdin"
|
|
||||||
|
|
||||||
if password != confirmedPassword:
|
|
||||||
echo "Passwords don't match, please try again"
|
|
||||||
continue
|
|
||||||
|
|
||||||
var name: WalletName
|
|
||||||
let outWalletName = config.outWalletName
|
|
||||||
if outWalletName.isSome:
|
|
||||||
name = outWalletName.get
|
|
||||||
else:
|
|
||||||
echo ""
|
|
||||||
echo80 "For your convenience, the wallet can be identified with a name " &
|
|
||||||
"of your choice. Please enter a wallet name below or press ENTER " &
|
|
||||||
"to continue with a machine-generated name."
|
|
||||||
|
|
||||||
while true:
|
|
||||||
var enteredName = ask "Wallet name"
|
|
||||||
if enteredName.len > 0:
|
|
||||||
name = try: WalletName.parseCmdArg(enteredName)
|
|
||||||
except CatchableError as err:
|
|
||||||
echo err.msg & ". Please try again."
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
let nextAccount = if config.cmd == wallets and
|
|
||||||
config.walletsCmd == WalletsCmd.restore:
|
|
||||||
config.restoredDepositsCount
|
config.restoredDepositsCount
|
||||||
else:
|
else:
|
||||||
none Natural
|
none Natural
|
||||||
|
|
||||||
let wallet = createWallet(kdfPbkdf2, rng, mnemonic,
|
let wallet = createWallet(kdfPbkdf2, rng, mnemonic,
|
||||||
name = name,
|
name = name,
|
||||||
nextAccount = nextAccount,
|
nextAccount = nextAccount,
|
||||||
password = KeystorePass password)
|
password = KeystorePass password)
|
||||||
|
|
||||||
let outWalletFileFlag = config.outWalletFile
|
let outWalletFileFlag = config.outWalletFile
|
||||||
let outWalletFile = if outWalletFileFlag.isSome:
|
let outWalletFile =
|
||||||
|
if outWalletFileFlag.isSome:
|
||||||
string outWalletFileFlag.get
|
string outWalletFileFlag.get
|
||||||
else:
|
else:
|
||||||
config.walletsDir / addFileExt(string wallet.uuid, "json")
|
config.walletsDir / addFileExt(string wallet.uuid, "json")
|
||||||
|
|
||||||
let status = saveWallet(wallet, outWalletFile)
|
let status = saveWallet(wallet, outWalletFile)
|
||||||
if status.isErr:
|
if status.isErr:
|
||||||
return err("failure to create wallet file due to " & status.error)
|
|
||||||
|
|
||||||
notice "Wallet file written", path = outWalletFile
|
|
||||||
return ok WalletPathPair(wallet: wallet, path: outWalletFile)
|
|
||||||
finally:
|
|
||||||
burnMem(password)
|
burnMem(password)
|
||||||
burnMem(confirmedPassword)
|
burnMem(confirmedPassword)
|
||||||
|
return err("failure to create wallet file due to " & status.error)
|
||||||
|
|
||||||
|
info "Wallet file written", path = outWalletFile
|
||||||
|
burnMem(password)
|
||||||
|
burnMem(confirmedPassword)
|
||||||
|
return ok WalletPathPair(wallet: wallet, path: outWalletFile)
|
||||||
|
|
||||||
proc createWalletInteractively*(
|
proc createWalletInteractively*(
|
||||||
rng: var BrHmacDrbgContext,
|
rng: var BrHmacDrbgContext,
|
||||||
@ -665,7 +665,7 @@ proc restoreWalletInteractively*(rng: var BrHmacDrbgContext,
|
|||||||
|
|
||||||
echo "To restore your wallet, please enter your backed-up seed phrase."
|
echo "To restore your wallet, please enter your backed-up seed phrase."
|
||||||
while true:
|
while true:
|
||||||
if not readPasswordInput("Seedphrase:", enteredMnemonic):
|
if not readPasswordInput("Seedphrase: ", enteredMnemonic):
|
||||||
fatal "failure to read password from stdin"
|
fatal "failure to read password from stdin"
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
@ -685,24 +685,26 @@ proc loadWallet*(fileName: string): Result[Wallet, string] =
|
|||||||
err exc.msg
|
err exc.msg
|
||||||
|
|
||||||
proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
||||||
|
let prompt = "Please enter the password for unlocking the wallet: "
|
||||||
echo "Please enter the password for unlocking the wallet"
|
echo "Please enter the password for unlocking the wallet"
|
||||||
|
|
||||||
for i in 1..3:
|
let res = keyboardGetPassword[Mnemonic](prompt, 3,
|
||||||
var password: TaintedString
|
proc (password: string): KsResult[Mnemonic] =
|
||||||
try:
|
|
||||||
if not readPasswordInput("Password: ", password):
|
|
||||||
return err "failure to read password from stdin"
|
|
||||||
|
|
||||||
var secret = decryptCryptoField(wallet.crypto, KeystorePass password)
|
var secret = decryptCryptoField(wallet.crypto, KeystorePass password)
|
||||||
if secret.len > 0:
|
if len(secret) > 0:
|
||||||
defer: burnMem(secret)
|
let mnemonic = Mnemonic(string.fromBytes(secret))
|
||||||
return ok Mnemonic(string.fromBytes(secret))
|
burnMem(secret)
|
||||||
|
ok(mnemonic)
|
||||||
else:
|
else:
|
||||||
echo "Unlocking of the wallet failed. Please try again."
|
let failed = "Unlocking of the wallet failed. Please try again"
|
||||||
finally:
|
echo failed
|
||||||
burnMem(password)
|
err(failed)
|
||||||
|
)
|
||||||
|
|
||||||
return err "failure to unlock wallet"
|
if res.isOk():
|
||||||
|
ok(res.get())
|
||||||
|
else:
|
||||||
|
err "Unlocking of the wallet failed."
|
||||||
|
|
||||||
proc findWallet*(config: BeaconNodeConf,
|
proc findWallet*(config: BeaconNodeConf,
|
||||||
name: WalletName): Result[WalletPathPair, string] =
|
name: WalletName): Result[WalletPathPair, string] =
|
||||||
|
@ -202,7 +202,7 @@ macro wordListArray*(filename: static string,
|
|||||||
minWordLen: static int = 0,
|
minWordLen: static int = 0,
|
||||||
maxWordLen: static int = high(int)): untyped =
|
maxWordLen: static int = high(int)): untyped =
|
||||||
result = newTree(nnkBracket)
|
result = newTree(nnkBracket)
|
||||||
var words = slurp(filename).split()
|
var words = slurp(filename).splitLines()
|
||||||
for word in words:
|
for word in words:
|
||||||
if word.len >= minWordLen and word.len <= maxWordLen:
|
if word.len >= minWordLen and word.len <= maxWordLen:
|
||||||
result.add newCall("cstring", newLit(word))
|
result.add newCall("cstring", newLit(word))
|
||||||
@ -213,9 +213,21 @@ const
|
|||||||
englishWords = wordListArray("english_word_list.txt",
|
englishWords = wordListArray("english_word_list.txt",
|
||||||
maxWords = wordListLen,
|
maxWords = wordListLen,
|
||||||
maxWordLen = maxWordLen)
|
maxWordLen = maxWordLen)
|
||||||
|
englishWordsDigest =
|
||||||
|
"AD90BF3BEB7B0EB7E5ACD74727DC0DA96E0A280A258354E7293FB7E211AC03DB".toDigest
|
||||||
|
|
||||||
|
proc checkEnglishWords(): bool =
|
||||||
|
if len(englishWords) != wordListLen:
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
var ctx: sha256
|
||||||
|
ctx.init()
|
||||||
|
for item in englishWords:
|
||||||
|
ctx.update($item)
|
||||||
|
ctx.finish() == englishWordsDigest
|
||||||
|
|
||||||
static:
|
static:
|
||||||
doAssert englishWords.len == wordListLen
|
doAssert(checkEnglishWords(), "English words array is corrupted!")
|
||||||
|
|
||||||
func append*(path: KeyPath, pathNode: Natural): KeyPath =
|
func append*(path: KeyPath, pathNode: Natural): KeyPath =
|
||||||
KeyPath(path.string & "/" & $pathNode)
|
KeyPath(path.string & "/" & $pathNode)
|
||||||
@ -225,7 +237,8 @@ func validateKeyPath*(path: TaintedString): Result[KeyPath, cstring] =
|
|||||||
var number: BiggestUint
|
var number: BiggestUint
|
||||||
try:
|
try:
|
||||||
for elem in path.string.split("/"):
|
for elem in path.string.split("/"):
|
||||||
# TODO: doesn't "m" have to be the first character and is it the only place where it is valid?
|
# TODO: doesn't "m" have to be the first character and is it the only
|
||||||
|
# place where it is valid?
|
||||||
if elem == "m":
|
if elem == "m":
|
||||||
continue
|
continue
|
||||||
# parseBiggestUInt can raise if overflow
|
# parseBiggestUInt can raise if overflow
|
||||||
@ -274,8 +287,6 @@ proc generateMnemonic*(
|
|||||||
entropyParam: openarray[byte] = @[]): Mnemonic =
|
entropyParam: openarray[byte] = @[]): Mnemonic =
|
||||||
## Generates a valid BIP-0039 mnenomic:
|
## Generates a valid BIP-0039 mnenomic:
|
||||||
## https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
## https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
doAssert words.len == wordListLen
|
|
||||||
|
|
||||||
var entropy: seq[byte]
|
var entropy: seq[byte]
|
||||||
if entropyParam.len == 0:
|
if entropyParam.len == 0:
|
||||||
setLen(entropy, 32)
|
setLen(entropy, 32)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user