Address #1729: NFKD Normalization

This commit is contained in:
Zahary Karadjov 2020-10-02 18:46:05 +03:00 committed by zah
parent fec4b5014d
commit 78953fd9b5
3 changed files with 61 additions and 43 deletions

View File

@ -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)

View File

@ -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(

View File

@ -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