Address #1729: NFKD Normalization
This commit is contained in:
parent
fec4b5014d
commit
78953fd9b5
|
@ -194,7 +194,7 @@ proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
|||
error "Password file has insecure permissions", key_path = keyStorePath
|
||||
return
|
||||
|
||||
let passphrase = KeystorePass:
|
||||
let passphrase = KeystorePass.init:
|
||||
try:
|
||||
readFile(passphrasePath)
|
||||
except IOError as err:
|
||||
|
@ -218,11 +218,12 @@ proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
|||
(validatorsDir / keyName) & "\": "
|
||||
let res = keyboardGetPassword[ValidatorPrivKey](prompt, 3,
|
||||
proc (password: string): KsResult[ValidatorPrivKey] =
|
||||
let decrypted = decryptKeystore(keystore, KeystorePass password)
|
||||
let decrypted = decryptKeystore(keystore, KeystorePass.init password)
|
||||
if decrypted.isErr():
|
||||
error "Keystore decryption failed. Please try again", keystorePath
|
||||
decrypted
|
||||
)
|
||||
|
||||
if res.isOk():
|
||||
some(res.get())
|
||||
else:
|
||||
|
@ -294,7 +295,7 @@ proc loadNetKeystore*(keyStorePath: string,
|
|||
|
||||
if insecurePwd.isSome():
|
||||
warn "Using insecure password to unlock networking key"
|
||||
let decrypted = decryptNetKeystore(keystore, KeystorePass insecurePwd.get())
|
||||
let decrypted = decryptNetKeystore(keystore, KeystorePass.init insecurePwd.get)
|
||||
if decrypted.isOk:
|
||||
return some(decrypted.get())
|
||||
else:
|
||||
|
@ -304,7 +305,7 @@ proc loadNetKeystore*(keyStorePath: string,
|
|||
let prompt = "Please enter passphrase to unlock networking key: "
|
||||
let res = keyboardGetPassword[lcrypto.PrivateKey](prompt, 3,
|
||||
proc (password: string): KsResult[lcrypto.PrivateKey] =
|
||||
let decrypted = decryptNetKeystore(keystore, KeystorePass password)
|
||||
let decrypted = decryptNetKeystore(keystore, KeystorePass.init password)
|
||||
if decrypted.isErr():
|
||||
error "Keystore decryption failed. Please try again", keystorePath
|
||||
decrypted
|
||||
|
@ -331,7 +332,7 @@ proc saveNetKeystore*(rng: var BrHmacDrbgContext, keyStorePath: string,
|
|||
res.get()
|
||||
|
||||
let keyStore = createNetKeystore(kdfScrypt, rng, netKey,
|
||||
KeystorePass password)
|
||||
KeystorePass.init password)
|
||||
var encodedStorage: string
|
||||
try:
|
||||
encodedStorage = Json.encode(keyStore)
|
||||
|
@ -355,7 +356,7 @@ proc saveKeystore(rng: var BrHmacDrbgContext,
|
|||
validatorDir = validatorsDir / keyName
|
||||
|
||||
if not existsDir(validatorDir):
|
||||
var password = KeystorePass ncrutils.toHex(getRandomBytes(rng, 32))
|
||||
var password = KeystorePass.init ncrutils.toHex(getRandomBytes(rng, 32))
|
||||
defer: burnMem(password)
|
||||
|
||||
let
|
||||
|
@ -378,7 +379,7 @@ proc saveKeystore(rng: var BrHmacDrbgContext,
|
|||
if sres.isErr():
|
||||
return err(FailedToCreateSecretsDir)
|
||||
|
||||
let swres = writeFile(secretsDir / keyName, string(password), 0o600)
|
||||
let swres = writeFile(secretsDir / keyName, password.str, 0o600)
|
||||
if swres.isErr():
|
||||
return err(FailedToCreateSecretFile)
|
||||
|
||||
|
@ -400,7 +401,7 @@ proc generateDeposits*(preset: RuntimePreset,
|
|||
|
||||
let withdrawalKeyPath = makeKeyPath(0, withdrawalKeyKind)
|
||||
# TODO: Explain why we are using an empty password
|
||||
var withdrawalKey = keyFromPath(mnemonic, KeystorePass"", withdrawalKeyPath)
|
||||
var withdrawalKey = keyFromPath(mnemonic, KeystorePass.init "", withdrawalKeyPath)
|
||||
defer: burnMem(withdrawalKey)
|
||||
let withdrawalPubKey = withdrawalKey.toPubKey
|
||||
|
||||
|
@ -496,7 +497,7 @@ proc importKeystoresFromDir*(rng: var BrHmacDrbgContext,
|
|||
var firstDecryptionAttempt = true
|
||||
|
||||
while true:
|
||||
var secret = decryptCryptoField(keystore.crypto, KeystorePass password)
|
||||
var secret = decryptCryptoField(keystore.crypto, KeystorePass.init password)
|
||||
|
||||
if secret.len == 0:
|
||||
if firstDecryptionAttempt:
|
||||
|
@ -590,7 +591,7 @@ proc pickPasswordAndSaveWallet(rng: var BrHmacDrbgContext,
|
|||
let wallet = createWallet(kdfPbkdf2, rng, mnemonic,
|
||||
name = name,
|
||||
nextAccount = nextAccount,
|
||||
password = KeystorePass password)
|
||||
password = KeystorePass.init password)
|
||||
|
||||
let outWalletFileFlag = config.outWalletFile
|
||||
let outWalletFile =
|
||||
|
@ -680,7 +681,7 @@ proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
|||
|
||||
let res = keyboardGetPassword[Mnemonic](prompt, 3,
|
||||
proc (password: string): KsResult[Mnemonic] =
|
||||
var secret = decryptCryptoField(wallet.crypto, KeystorePass password)
|
||||
var secret = decryptCryptoField(wallet.crypto, KeystorePass.init password)
|
||||
if len(secret) > 0:
|
||||
let mnemonic = Mnemonic(string.fromBytes(secret))
|
||||
burnMem(secret)
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
|
||||
import
|
||||
# Standard library
|
||||
std/[math, strutils, parseutils, strformat, typetraits, algorithm],
|
||||
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode],
|
||||
# Third-party libraries
|
||||
normalize,
|
||||
# Status libraries
|
||||
stew/[results, byteutils, bitseqs, bitops2], stew/shims/macros,
|
||||
bearssl, eth/keyfile/uuid, blscurve, faststreams/textio, json_serialization,
|
||||
stew/[results, bitseqs, bitops2], stew/shims/macros,
|
||||
bearssl, eth/keyfile/uuid, blscurve, json_serialization,
|
||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
||||
# Internal
|
||||
# Local modules
|
||||
libp2p/crypto/crypto as lcrypto,
|
||||
./datatypes, ./crypto, ./digest, ./signatures
|
||||
|
||||
|
@ -127,8 +129,9 @@ type
|
|||
WalletName* = distinct string
|
||||
Mnemonic* = distinct string
|
||||
KeyPath* = distinct string
|
||||
KeystorePass* = distinct string
|
||||
KeySeed* = distinct seq[byte]
|
||||
KeystorePass* = object
|
||||
str*: string
|
||||
|
||||
Credentials* = object
|
||||
mnemonic*: Mnemonic
|
||||
|
@ -136,7 +139,7 @@ type
|
|||
signingKey*: ValidatorPrivKey
|
||||
withdrawalKey*: ValidatorPrivKey
|
||||
|
||||
SensitiveData = Mnemonic|KeystorePass|KeySeed
|
||||
SensitiveStrings = Mnemonic|KeySeed
|
||||
SimpleHexEncodedTypes = ScryptSalt|ChecksumBytes|CipherBytes
|
||||
|
||||
const
|
||||
|
@ -181,11 +184,16 @@ template `==`*(lhs, rhs: WalletName): bool =
|
|||
template `$`*(x: WalletName): string =
|
||||
string(x)
|
||||
|
||||
template burnMem*(m: var (SensitiveData|TaintedString)) =
|
||||
template burnMem*(m: var (SensitiveStrings|TaintedString)) =
|
||||
# TODO: `burnMem` in nimcrypto could use distinctBase
|
||||
# to make its usage less error-prone.
|
||||
ncrutils.burnMem(string m)
|
||||
|
||||
template burnMem*(m: var KeystorePass) =
|
||||
# TODO: `burnMem` in nimcrypto could use distinctBase
|
||||
# to make its usage less error-prone.
|
||||
ncrutils.burnMem(m.str)
|
||||
|
||||
func longName*(wallet: Wallet): string =
|
||||
if wallet.name.string == wallet.uuid.string:
|
||||
wallet.name.string
|
||||
|
@ -273,9 +281,18 @@ func makeKeyPath*(validatorIdx: Natural,
|
|||
except ValueError:
|
||||
raiseAssert "All values above can be converted successfully to strings"
|
||||
|
||||
func isControlRune(r: Rune): bool =
|
||||
let r = int r
|
||||
(r >= 0 and r < 0x20) or (r >= 0x7F and r < 0xA0)
|
||||
|
||||
proc init*(T: type KeystorePass, input: string): T =
|
||||
for rune in toNFKD(input):
|
||||
if not isControlRune(rune):
|
||||
result.str.add rune
|
||||
|
||||
func getSeed*(mnemonic: Mnemonic, password: KeystorePass): KeySeed =
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed
|
||||
let salt = "mnemonic-" & password.string
|
||||
let salt = "mnemonic-" & password.str
|
||||
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
|
||||
|
||||
template add(m: var Mnemonic, s: cstring) =
|
||||
|
@ -332,7 +349,7 @@ proc validateMnemonic*(inputWords: TaintedString,
|
|||
## with sensitive data even in case of validator failure.
|
||||
## Make sure to burn the received data after usage.
|
||||
|
||||
let words = inputWords.string.strip.split(Whitespace)
|
||||
let words = strutils.strip(inputWords.string.toNFKD).split(Whitespace)
|
||||
if words.len < 12 or words.len > 24 or words.len mod 3 != 0:
|
||||
return false
|
||||
|
||||
|
@ -488,7 +505,7 @@ proc decryptCryptoField*(crypto: Crypto, password: KeystorePass): seq[byte] =
|
|||
let decKey = case crypto.kdf.function
|
||||
of kdfPbkdf2:
|
||||
template params: auto = crypto.kdf.pbkdf2Params
|
||||
sha256.pbkdf2(password.string, params.salt.bytes, params.c, params.dklen)
|
||||
sha256.pbkdf2(password.str, params.salt.bytes, params.c, params.dklen)
|
||||
of kdfScrypt:
|
||||
template params: auto = crypto.kdf.scryptParams
|
||||
if params.dklen != scryptParams.dklen or
|
||||
|
@ -497,7 +514,7 @@ proc decryptCryptoField*(crypto: Crypto, password: KeystorePass): seq[byte] =
|
|||
params.p != scryptParams.p:
|
||||
# TODO This should be reported in a better way
|
||||
return
|
||||
@(scrypt(password.string,
|
||||
@(scrypt(password.str,
|
||||
params.salt.bytes,
|
||||
scryptParams.n,
|
||||
scryptParams.r,
|
||||
|
@ -571,7 +588,7 @@ proc decryptNetKeystore*(nkeystore: JsonString,
|
|||
proc createCryptoField(kdfKind: KdfKind,
|
||||
rng: var BrHmacDrbgContext,
|
||||
secret: openarray[byte],
|
||||
password = KeystorePass "",
|
||||
password = KeystorePass.init "",
|
||||
salt: openarray[byte] = @[],
|
||||
iv: openarray[byte] = @[]): Crypto =
|
||||
type AES = aes128
|
||||
|
@ -592,7 +609,7 @@ proc createCryptoField(kdfKind: KdfKind,
|
|||
var decKey: seq[byte]
|
||||
let kdf = case kdfKind
|
||||
of kdfPbkdf2:
|
||||
decKey = sha256.pbkdf2(password.string,
|
||||
decKey = sha256.pbkdf2(password.str,
|
||||
kdfSalt,
|
||||
pbkdf2Params.c,
|
||||
pbkdf2Params.dklen)
|
||||
|
@ -600,7 +617,7 @@ proc createCryptoField(kdfKind: KdfKind,
|
|||
params.salt = Pbkdf2Salt kdfSalt
|
||||
Kdf(function: kdfPbkdf2, pbkdf2Params: params, message: "")
|
||||
of kdfScrypt:
|
||||
decKey = @(scrypt(password.string, kdfSalt,
|
||||
decKey = @(scrypt(password.str, kdfSalt,
|
||||
scryptParams.n, scryptParams.r, scryptParams.p, keyLen))
|
||||
var params = scryptParams
|
||||
params.salt = ScryptSalt kdfSalt
|
||||
|
@ -629,7 +646,7 @@ proc createCryptoField(kdfKind: KdfKind,
|
|||
proc createNetKeystore*(kdfKind: KdfKind,
|
||||
rng: var BrHmacDrbgContext,
|
||||
privKey: lcrypto.PrivateKey,
|
||||
password = KeystorePass "",
|
||||
password = KeystorePass.init "",
|
||||
description = "",
|
||||
salt: openarray[byte] = @[],
|
||||
iv: openarray[byte] = @[]): NetKeystore =
|
||||
|
@ -650,7 +667,7 @@ proc createNetKeystore*(kdfKind: KdfKind,
|
|||
proc createKeystore*(kdfKind: KdfKind,
|
||||
rng: var BrHmacDrbgContext,
|
||||
privKey: ValidatorPrivkey,
|
||||
password = KeystorePass "",
|
||||
password = KeystorePass.init "",
|
||||
path = KeyPath "",
|
||||
description = "",
|
||||
salt: openarray[byte] = @[],
|
||||
|
@ -675,7 +692,7 @@ proc createWallet*(kdfKind: KdfKind,
|
|||
name = WalletName "",
|
||||
salt: openarray[byte] = @[],
|
||||
iv: openarray[byte] = @[],
|
||||
password = KeystorePass "",
|
||||
password = KeystorePass.init "",
|
||||
nextAccount = none(Natural),
|
||||
pretty = true): Wallet =
|
||||
let
|
||||
|
@ -683,7 +700,7 @@ proc createWallet*(kdfKind: KdfKind,
|
|||
# Please note that we are passing an empty password here because
|
||||
# we want the wallet restoration procedure to depend only on the
|
||||
# mnemonic (the user is asked to treat the mnemonic as a password).
|
||||
seed = getSeed(mnemonic, KeystorePass"")
|
||||
seed = getSeed(mnemonic, KeystorePass.init "")
|
||||
crypto = createCryptoField(kdfKind, rng, distinctBase seed,
|
||||
password, salt, iv)
|
||||
Wallet(
|
||||
|
|
|
@ -174,7 +174,7 @@ suiteReport "KeyStorage testing suite":
|
|||
timedTest "[PBKDF2] Keystore decryption":
|
||||
let
|
||||
keystore = Json.decode(pbkdf2Vector, Keystore)
|
||||
decrypt = decryptKeystore(keystore, KeystorePass password)
|
||||
decrypt = decryptKeystore(keystore, KeystorePass.init password)
|
||||
|
||||
check decrypt.isOk
|
||||
check secret.isEqual(decrypt.get())
|
||||
|
@ -182,7 +182,7 @@ suiteReport "KeyStorage testing suite":
|
|||
timedTest "[SCRYPT] Keystore decryption":
|
||||
let
|
||||
keystore = Json.decode(scryptVector, Keystore)
|
||||
decrypt = decryptKeystore(keystore, KeystorePass password)
|
||||
decrypt = decryptKeystore(keystore, KeystorePass.init password)
|
||||
|
||||
check decrypt.isOk
|
||||
check secret.isEqual(decrypt.get())
|
||||
|
@ -190,7 +190,7 @@ suiteReport "KeyStorage testing suite":
|
|||
timedTest "[PBKDF2] Network Keystore decryption":
|
||||
let
|
||||
keystore = Json.decode(pbkdf2NetVector, NetKeystore)
|
||||
decrypt = decryptNetKeystore(keystore, KeystorePass password)
|
||||
decrypt = decryptNetKeystore(keystore, KeystorePass.init password)
|
||||
|
||||
check decrypt.isOk
|
||||
check nsecret == decrypt.get()
|
||||
|
@ -198,14 +198,14 @@ suiteReport "KeyStorage testing suite":
|
|||
timedTest "[SCRYPT] Network Keystore decryption":
|
||||
let
|
||||
keystore = Json.decode(scryptNetVector, NetKeystore)
|
||||
decrypt = decryptNetKeystore(keystore, KeystorePass password)
|
||||
decrypt = decryptNetKeystore(keystore, KeystorePass.init password)
|
||||
|
||||
check decrypt.isOk
|
||||
check nsecret == decrypt.get()
|
||||
|
||||
timedTest "[PBKDF2] Keystore encryption":
|
||||
let keystore = createKeystore(kdfPbkdf2, rng[], secret,
|
||||
KeystorePass password,
|
||||
KeystorePass.init password,
|
||||
salt=salt, iv=iv,
|
||||
description = "This is a test keystore that uses PBKDF2 to secure the secret.",
|
||||
path = validateKeyPath("m/12381/60/0/0").expect("Valid Keypath"))
|
||||
|
@ -219,7 +219,7 @@ suiteReport "KeyStorage testing suite":
|
|||
|
||||
timedTest "[PBKDF2] Network Keystore encryption":
|
||||
let nkeystore = createNetKeystore(kdfPbkdf2, rng[], nsecret,
|
||||
KeystorePass password,
|
||||
KeystorePass.init password,
|
||||
salt = salt, iv = iv,
|
||||
description =
|
||||
"PBKDF2 Network private key storage")
|
||||
|
@ -232,7 +232,7 @@ suiteReport "KeyStorage testing suite":
|
|||
|
||||
timedTest "[SCRYPT] Keystore encryption":
|
||||
let keystore = createKeystore(kdfScrypt, rng[], secret,
|
||||
KeystorePass password,
|
||||
KeystorePass.init password,
|
||||
salt=salt, iv=iv,
|
||||
description = "This is a test keystore that uses scrypt to secure the secret.",
|
||||
path = validateKeyPath("m/12381/60/3141592653/589793238").expect("Valid keypath"))
|
||||
|
@ -246,7 +246,7 @@ suiteReport "KeyStorage testing suite":
|
|||
|
||||
timedTest "[SCRYPT] Network Keystore encryption":
|
||||
let nkeystore = createNetKeystore(kdfScrypt, rng[], nsecret,
|
||||
KeystorePass password,
|
||||
KeystorePass.init password,
|
||||
salt = salt, iv = iv,
|
||||
description =
|
||||
"SCRYPT Network private key storage")
|
||||
|
@ -265,20 +265,20 @@ suiteReport "KeyStorage testing suite":
|
|||
echo createKeystore(kdfPbkdf2, rng[], secret, iv = [byte 1])
|
||||
|
||||
check decryptKeystore(JsonString pbkdf2Vector,
|
||||
KeystorePass "wrong pass").isErr
|
||||
KeystorePass.init "wrong pass").isErr
|
||||
|
||||
check decryptKeystore(JsonString pbkdf2Vector,
|
||||
KeystorePass "").isErr
|
||||
KeystorePass.init "").isErr
|
||||
|
||||
check decryptKeystore(JsonString "{\"a\": 0}",
|
||||
KeystorePass "").isErr
|
||||
KeystorePass.init "").isErr
|
||||
|
||||
check decryptKeystore(JsonString "",
|
||||
KeystorePass "").isErr
|
||||
KeystorePass.init "").isErr
|
||||
|
||||
template checkVariant(remove): untyped =
|
||||
check decryptKeystore(JsonString pbkdf2Vector.replace(remove, "1234"),
|
||||
KeystorePass password).isErr
|
||||
KeystorePass.init password).isErr
|
||||
|
||||
checkVariant "f876" # salt
|
||||
checkVariant "75ea" # checksum
|
||||
|
@ -288,4 +288,4 @@ suiteReport "KeyStorage testing suite":
|
|||
badKdf{"crypto", "kdf", "function"} = %"invalid"
|
||||
|
||||
check decryptKeystore(JsonString $badKdf,
|
||||
KeystorePass password).iserr
|
||||
KeystorePass.init password).iserr
|
||||
|
|
Loading…
Reference in New Issue