mirror of
https://github.com/waku-org/nwaku.git
synced 2025-02-12 15:06:38 +00:00
deploy: ea8d72188e9ee74430fc1f311a7905c52cce4d02
This commit is contained in:
parent
e90639330d
commit
97ddd4d70e
@ -291,6 +291,11 @@ type
|
||||
defaultValue: ""
|
||||
name: "rln-relay-eth-contract-address" }: string
|
||||
|
||||
rlnRelayCredentialsPassword* {.
|
||||
desc: "Password for encrypting RLN credentials",
|
||||
defaultValue: ""
|
||||
name: "rln-relay-cred-password" }: string
|
||||
|
||||
# NOTE: Keys are different in nim-libp2p
|
||||
proc parseCmdArg*(T: type crypto.PrivateKey, p: TaintedString): T =
|
||||
try:
|
||||
|
@ -162,6 +162,11 @@ type
|
||||
defaultValue: ""
|
||||
name: "rln-relay-eth-contract-address" }: string
|
||||
|
||||
rlnRelayCredentialsPassword* {.
|
||||
desc: "Password for encrypting RLN credentials",
|
||||
defaultValue: ""
|
||||
name: "rln-relay-cred-password" }: string
|
||||
|
||||
staticnodes* {.
|
||||
desc: "Peer multiaddr to directly connect with. Argument may be repeated."
|
||||
name: "staticnode" }: seq[string]
|
||||
|
@ -45,7 +45,9 @@ import
|
||||
./v2/test_enr_utils,
|
||||
./v2/test_peer_exchange,
|
||||
./v2/test_waku_noise,
|
||||
./v2/test_waku_noise_sessions
|
||||
./v2/test_waku_noise_sessions,
|
||||
# Utils
|
||||
./v2/test_utils_keyfile
|
||||
|
||||
when defined(rln) or defined(rlnzerokit):
|
||||
import
|
||||
|
377
tests/v2/test_utils_keyfile.nim
Normal file
377
tests/v2/test_utils_keyfile.nim
Normal file
@ -0,0 +1,377 @@
|
||||
{.used.}
|
||||
|
||||
import
|
||||
std/[json, os],
|
||||
stew/byteutils,
|
||||
testutils/unittests, chronos,
|
||||
eth/keys
|
||||
import
|
||||
../../waku/v2/utils/keyfile
|
||||
|
||||
from ../../waku/v2/protocol/waku_noise/noise_utils import randomSeqByte
|
||||
|
||||
suite "KeyFile test suite":
|
||||
|
||||
let rng = newRng()
|
||||
|
||||
test "Create/Save/Load single keyfile":
|
||||
|
||||
# The password we use to encrypt our secret
|
||||
let password = "randompassword"
|
||||
|
||||
# The filepath were the keyfile will be stored
|
||||
let filepath = "./test.keyfile"
|
||||
defer: removeFile(filepath)
|
||||
|
||||
# The secret
|
||||
var secret = randomSeqByte(rng[], 300)
|
||||
|
||||
# We create a keyfile encrypting the secret with password
|
||||
let keyfile = createKeyFileJson(secret, password)
|
||||
|
||||
check:
|
||||
keyfile.isOk()
|
||||
# We save to disk the keyfile
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
# We load from the file all the decrypted keyfiles encrypted under password
|
||||
var decodedKeyfiles = loadKeyFiles(filepath, password)
|
||||
|
||||
check:
|
||||
decodedKeyfiles.isOk()
|
||||
# Since only one secret was stored in file, we expect only one keyfile being decrypted
|
||||
decodedKeyfiles.get().len == 1
|
||||
|
||||
# We check if the decrypted secret is the same as the original secret
|
||||
let decodedSecret = decodedKeyfiles.get()[0]
|
||||
|
||||
check:
|
||||
secret == decodedSecret.get()
|
||||
|
||||
test "Create/Save/Load multiple keyfiles in same file":
|
||||
|
||||
# We set different passwords for different keyfiles that will be stored in same file
|
||||
let password1 = string.fromBytes(randomSeqByte(rng[], 20))
|
||||
let password2 = ""
|
||||
let password3 = string.fromBytes(randomSeqByte(rng[], 20))
|
||||
var keyfile: KfResult[JsonNode]
|
||||
|
||||
let filepath = "./test.keyfile"
|
||||
defer: removeFile(filepath)
|
||||
|
||||
# We generate 6 different secrets and we encrypt them using 3 different passwords, and we store the obtained keystore
|
||||
|
||||
let secret1 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret1, password1)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
let secret2 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret2, password2)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
let secret3 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret3, password3)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
# We encrypt secret4 with password3
|
||||
let secret4 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret4, password3)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
# We encrypt secret5 with password1
|
||||
let secret5 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret5, password1)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
# We encrypt secret6 with password1
|
||||
let secret6 = randomSeqByte(rng[], 300)
|
||||
keyfile = createKeyFileJson(secret6, password1)
|
||||
check:
|
||||
keyfile.isOk()
|
||||
saveKeyFile(filepath, keyfile.get()).isOk()
|
||||
|
||||
# Now there are 6 keyfiles stored in filepath encrypted with 3 different passwords
|
||||
# We decrypt the keyfiles using the respective passwords and we check that the number of
|
||||
# successful decryptions corresponds to the number of secrets encrypted under that password
|
||||
|
||||
var decodedKeyfilesPassword1 = loadKeyFiles(filepath, password1)
|
||||
check:
|
||||
decodedKeyfilesPassword1.isOk()
|
||||
decodedKeyfilesPassword1.get().len == 3
|
||||
var decodedSecretsPassword1 = decodedKeyfilesPassword1.get()
|
||||
|
||||
var decodedKeyfilesPassword2 = loadKeyFiles(filepath, password2)
|
||||
check:
|
||||
decodedKeyfilesPassword2.isOk()
|
||||
decodedKeyfilesPassword2.get().len == 1
|
||||
var decodedSecretsPassword2 = decodedKeyfilesPassword2.get()
|
||||
|
||||
var decodedKeyfilesPassword3 = loadKeyFiles(filepath, password3)
|
||||
check:
|
||||
decodedKeyfilesPassword3.isOk()
|
||||
decodedKeyfilesPassword3.get().len == 2
|
||||
var decodedSecretsPassword3 = decodedKeyfilesPassword3.get()
|
||||
|
||||
# We check if the corresponding secrets are correct
|
||||
check:
|
||||
# Secrets encrypted with password 1
|
||||
secret1 == decodedSecretsPassword1[0].get()
|
||||
secret5 == decodedSecretsPassword1[1].get()
|
||||
secret6 == decodedSecretsPassword1[2].get()
|
||||
# Secrets encrypted with password 2
|
||||
secret2 == decodedSecretsPassword2[0].get()
|
||||
# Secrets encrypted with password 3
|
||||
secret3 == decodedSecretsPassword3[0].get()
|
||||
secret4 == decodedSecretsPassword3[1].get()
|
||||
|
||||
|
||||
# The following tests are originally from the nim-eth keyfile tests module https://github.com/status-im/nim-eth/blob/master/tests/keyfile/test_keyfile.nim
|
||||
# and are slightly adapted to test backwards compatibility with nim-eth implementation of our customized version of the utils/keyfile module
|
||||
# Note: the original nim-eth "Create/Save/Load test" is redefined and expanded above in "KeyFile test suite"
|
||||
suite "KeyFile test suite (adapted from nim-eth keyfile tests)":
|
||||
|
||||
let rng = newRng()
|
||||
|
||||
# Testvectors originally from https://github.com/status-im/nim-eth/blob/fef47331c37ee8abb8608037222658737ff498a6/tests/keyfile/test_keyfile.nim#L22-L168
|
||||
let TestVectors = [
|
||||
%*{
|
||||
"keyfile": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"},
|
||||
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
|
||||
"kdf" : "pbkdf2",
|
||||
"kdfparams" : {
|
||||
"c" : 262144,
|
||||
"dklen" : 32,
|
||||
"prf" : "hmac-sha256",
|
||||
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
|
||||
},
|
||||
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||
"version" : 3
|
||||
},
|
||||
"name": "test1",
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version": 3,
|
||||
"crypto": {
|
||||
"ciphertext": "ee75456c006b1e468133c5d2a916bacd3cf515ced4d9b021b5c59978007d1e87",
|
||||
"version": 1,
|
||||
"kdf": "pbkdf2",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"c": 262144,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": "504490577620f64f43d73f29479c2cf0"
|
||||
},
|
||||
"mac": "196815708465de9af7504144a1360d08874fc3c30bb0e648ce88fbc36830d35d",
|
||||
"cipherparams": {"iv": "514ccc8c4fb3e60e5538e0cf1e27c233"},
|
||||
"cipher": "aes-128-ctr"
|
||||
},
|
||||
"id": "98d193c7-5174-4c7c-5345-c1daf95477b5"
|
||||
},
|
||||
"name": "python_generated_test_with_odd_iv",
|
||||
"password": "foo",
|
||||
"priv": "0101010101010101010101010101010101010101010101010101010101010101"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version": 3,
|
||||
"crypto": {
|
||||
"ciphertext": "d69313b6470ac1942f75d72ebf8818a0d484ac78478a132ee081cd954d6bd7a9",
|
||||
"cipherparams": {"iv": "ffffffffffffffffffffffffffffffff"},
|
||||
"kdf": "pbkdf2",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"c": 262144,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": "c82ef14476014cbf438081a42709e2ed"
|
||||
},
|
||||
"mac": "cf6bfbcc77142a22c4a908784b4a16f1023a1d0e2aff404c20158fa4f1587177",
|
||||
"cipher": "aes-128-ctr",
|
||||
"version": 1
|
||||
},
|
||||
"id": "abb67040-8dbe-0dad-fc39-2b082ef0ee5f"
|
||||
},
|
||||
"name": "evilnonce",
|
||||
"password": "bar",
|
||||
"priv": "0202020202020202020202020202020202020202020202020202020202020202"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version" : 3,
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {
|
||||
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
|
||||
},
|
||||
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
|
||||
"kdf" : "scrypt",
|
||||
"kdfparams" : {
|
||||
"dklen" : 32,
|
||||
"n" : 262144,
|
||||
"r" : 1,
|
||||
"p" : 8,
|
||||
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
|
||||
},
|
||||
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6"
|
||||
},
|
||||
"name" : "test2",
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version": 3,
|
||||
"address": "460121576cc7df020759730751f92bd62fd78dd6",
|
||||
"crypto": {
|
||||
"ciphertext": "54ae683c6287fa3d58321f09d56e26d94e58a00d4f90bdd95782ae0e4aab618b",
|
||||
"cipherparams": {
|
||||
"iv": "681679cdb125bba9495d068b002816a4"
|
||||
},
|
||||
"cipher": "aes-128-ctr",
|
||||
"kdf": "scrypt",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"salt": "c3407f363fce02a66e3c4bf4a8f6b7da1c1f54266cef66381f0625c251c32785",
|
||||
"n": 8192,
|
||||
"r": 8,
|
||||
"p": 1
|
||||
},
|
||||
"mac": "dea6bdf22a2f522166ed82808c22a6311e84c355f4bbe100d4260483ff675a46"
|
||||
},
|
||||
"id": "0eb785e0-340a-4290-9c42-90a11973ee47"
|
||||
},
|
||||
"name": "mycrypto",
|
||||
"password": "foobartest121",
|
||||
"priv": "05a4d3eb46c742cb8850440145ce70cbc80b59f891cf5f50fd3e9c280b50c4e4"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"crypto": {
|
||||
"cipher": "aes-128-ctr",
|
||||
"cipherparams": {
|
||||
"iv": "7e7b02d2b4ef45d6c98cb885e75f48d5",
|
||||
},
|
||||
"ciphertext": "a7a5743a6c7eb3fa52396bd3fd94043b79075aac3ccbae8e62d3af94db00397c",
|
||||
"kdf": "scrypt",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"n": 8192,
|
||||
"p": 1,
|
||||
"r": 8,
|
||||
"salt": "247797c7a357b707a3bdbfaa55f4c553756bca09fec20ddc938e7636d21e4a20",
|
||||
},
|
||||
"mac": "5a3ba5bebfda2c384586eda5fcda9c8397d37c9b0cc347fea86525cf2ea3a468",
|
||||
},
|
||||
"address": "0b6f2de3dee015a95d3330dcb7baf8e08aa0112d",
|
||||
"id": "3c8efdd6-d538-47ec-b241-36783d3418b9",
|
||||
"version": 3
|
||||
},
|
||||
"password": "moomoocow",
|
||||
"priv": "21eac69b9a52f466bfe9047f0f21c9caf3a5cdaadf84e2750a9b3265d450d481",
|
||||
"name": "eth-keyfile-conftest"
|
||||
}
|
||||
]
|
||||
|
||||
test "Testing nim-eth test vectors":
|
||||
|
||||
var secret: KfResult[seq[byte]]
|
||||
var expectedSecret: seq[byte]
|
||||
|
||||
for i in 0..<TestVectors.len:
|
||||
|
||||
# Decryption with correct password
|
||||
expectedSecret = decodeHex(TestVectors[i].getOrDefault("priv").getStr())
|
||||
secret =
|
||||
decodeKeyFileJson(TestVectors[i].getOrDefault("keyfile"),
|
||||
TestVectors[i].getOrDefault("password").getStr())
|
||||
check:
|
||||
secret.isOk()
|
||||
secret.get() == expectedSecret
|
||||
|
||||
# Decryption with wrong password
|
||||
secret = decodeKeyFileJson(TestVectors[i].getOrDefault("keyfile"), "wrongpassword")
|
||||
|
||||
check:
|
||||
secret.isErr()
|
||||
secret.error == KeyFileError.IncorrectMac
|
||||
|
||||
test "Wrong mac in keyfile":
|
||||
|
||||
# This keyfile is the same as the first one in TestVectors,
|
||||
# but the last byte of mac is changed to 00.
|
||||
# While ciphertext is the correct encryption of priv under password,
|
||||
# mac verfication should fail and nothing will be decrypted
|
||||
let keyfileWrongMac = %*{
|
||||
"keyfile": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"},
|
||||
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
|
||||
"kdf" : "pbkdf2",
|
||||
"kdfparams" : {
|
||||
"c" : 262144,
|
||||
"dklen" : 32,
|
||||
"prf" : "hmac-sha256",
|
||||
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
|
||||
},
|
||||
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e900"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||
"version" : 3
|
||||
},
|
||||
"name": "test1",
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
}
|
||||
|
||||
# Decryption with correct password
|
||||
let expectedSecret = decodeHex(keyfileWrongMac.getOrDefault("priv").getStr())
|
||||
let secret =
|
||||
decodeKeyFileJson(keyfileWrongMac.getOrDefault("keyfile"),
|
||||
keyfileWrongMac.getOrDefault("password").getStr())
|
||||
check:
|
||||
secret.isErr()
|
||||
secret.error == KeyFileError.IncorrectMac
|
||||
|
||||
test "Scrypt keyfiles":
|
||||
let
|
||||
expectedSecret = randomSeqByte(rng[], 300)
|
||||
password = "miawmiawcat"
|
||||
|
||||
# By default, keyfiles' encryption key is derived from password using PBKDF2.
|
||||
# Here we test keyfiles encypted with a key derived from password using scrypt
|
||||
jsonKeyfile = createKeyFileJson(expectedSecret, password, 3, AES128CTR, SCRYPT)
|
||||
|
||||
check:
|
||||
jsonKeyfile.isOk()
|
||||
|
||||
let secret = decodeKeyFileJson(jsonKeyfile.get(), password)
|
||||
|
||||
check:
|
||||
secret.isOk()
|
||||
secret.get() == expectedSecret
|
||||
|
||||
test "Load non-existent keyfile test":
|
||||
|
||||
check:
|
||||
loadKeyFiles("nonexistant.keyfile", "password").error ==
|
||||
KeyFileError.KeyfileDoesNotExist
|
@ -2,7 +2,7 @@
|
||||
{.used.}
|
||||
|
||||
import
|
||||
std/options, sequtils, times, deques,
|
||||
std/[options, os, sequtils, times, deques],
|
||||
testutils/unittests, chronos, chronicles, stint,
|
||||
stew/byteutils, stew/shims/net as stewNet,
|
||||
libp2p/crypto/crypto,
|
||||
@ -980,7 +980,7 @@ suite "Waku rln relay":
|
||||
check:
|
||||
keypair.get().idCommitment == idCommitment
|
||||
|
||||
test "Read Persistent RLN credentials":
|
||||
test "Read/Write RLN credentials":
|
||||
# create an RLN instance
|
||||
var rlnInstance = createRLNInstance()
|
||||
check:
|
||||
@ -1003,16 +1003,25 @@ suite "Waku rln relay":
|
||||
|
||||
var rlnMembershipCredentials = RlnMembershipCredentials(membershipKeyPair: k, rlnIndex: index)
|
||||
|
||||
let path = "testPath.txt"
|
||||
let password = "%m0um0ucoW%"
|
||||
|
||||
let filepath = "./testRLNCredentials.txt"
|
||||
defer: removeFile(filepath)
|
||||
|
||||
# Write RLN credentials
|
||||
writeFile(path, pretty(%rlnMembershipCredentials))
|
||||
check:
|
||||
writeRlnCredentials(filepath, rlnMembershipCredentials, password).isOk()
|
||||
|
||||
var credentials = readPersistentRlnCredentials(path)
|
||||
let readCredentialsResult = readRlnCredentials(filepath, password)
|
||||
check:
|
||||
readCredentialsResult.isOk()
|
||||
|
||||
let credentials = readCredentialsResult.get()
|
||||
|
||||
check:
|
||||
credentials.membershipKeyPair == k
|
||||
credentials.rlnIndex == index
|
||||
credentials.isSome()
|
||||
credentials.get().membershipKeyPair == k
|
||||
credentials.get().rlnIndex == index
|
||||
|
||||
test "histogram static bucket generation":
|
||||
let buckets = generateBucketsForHistogram(10)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# libtool - Provide generalized library-building support services.
|
||||
# Generated automatically by config.status (libbacktrace) version-unused
|
||||
# Libtool was configured on host fv-az245-751:
|
||||
# Libtool was configured on host fv-az222-281:
|
||||
# NOTE: Changes made to this file will be lost: look at ltmain.sh.
|
||||
#
|
||||
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
|
||||
|
@ -16,6 +16,7 @@ import
|
||||
waku_rln_relay_types,
|
||||
waku_rln_relay_metrics,
|
||||
../../utils/time,
|
||||
../../utils/keyfile,
|
||||
../../node/waku_node,
|
||||
../../../../../apps/wakunode2/config, ## TODO: Decouple the protocol code from the app configuration
|
||||
../../../../../apps/chat2/config_chat2, ## TODO: Decouple the protocol code from the app configuration
|
||||
@ -1145,19 +1146,45 @@ proc mountRlnRelayDynamic*(node: WakuNode,
|
||||
node.wakuRlnRelay = rlnPeer
|
||||
return ok(true)
|
||||
|
||||
proc readPersistentRlnCredentials*(path: string) : RlnMembershipCredentials {.raises: [Defect, OSError, IOError, Exception].} =
|
||||
info "Rln credentials exist in file"
|
||||
proc writeRlnCredentials*(path: string, credentials: RlnMembershipCredentials, password: string): RlnRelayResult[void] =
|
||||
info "Storing RLN credentials"
|
||||
var jsonString: string
|
||||
jsonString.toUgly(%credentials)
|
||||
let keyfile = createKeyFileJson(toBytes(jsonString), password)
|
||||
if keyfile.isErr():
|
||||
return err("Error while creating keyfile for RLN credentials")
|
||||
if saveKeyFile(path, keyfile.get()).isErr():
|
||||
return err("Error while saving keyfile for RLN credentials")
|
||||
return ok()
|
||||
|
||||
# Attempts decryptions of all keyfiles with the provided password.
|
||||
# If one or more credentials are successfully decrypted, the max(min(index,number_decrypted),0)-th is returned.
|
||||
proc readRlnCredentials*(path: string, password: string, index: int = 0): RlnRelayResult[Option[RlnMembershipCredentials]] =
|
||||
info "Reading RLN credentials"
|
||||
# With regards to printing the keys, it is purely for debugging purposes so that the user becomes explicitly aware of the current keys in use when nwaku is started.
|
||||
# Note that this is only until the RLN contract being used is the one deployed on Goerli testnet.
|
||||
# These prints need to omitted once RLN contract is deployed on Ethereum mainnet and using valuable funds for staking.
|
||||
waku_rln_membership_credentials_import_duration_seconds.nanosecondTime:
|
||||
let entireRlnCredentialsFile = readFile(path)
|
||||
|
||||
let jsonObject = parseJson(entireRlnCredentialsFile)
|
||||
try:
|
||||
var decodedKeyfiles = loadKeyFiles(path, password)
|
||||
|
||||
if decodedKeyfiles.isOk():
|
||||
var decodedRlnCredentials = decodedKeyfiles.get()
|
||||
debug "Successfully decrypted keyfiles for the provided password", numberKeyfilesDecrypted=decodedRlnCredentials.len
|
||||
# We should return the index-th decrypted credential, but we ensure to not overflow
|
||||
let credentialIndex = max(min(index, decodedRlnCredentials.len - 1), 0)
|
||||
debug "Picking credential with (adjusted) index", inputIndex=index, adjustedIndex=credentialIndex
|
||||
let jsonObject = parseJson(string.fromBytes(decodedRlnCredentials[credentialIndex].get()))
|
||||
let deserializedRlnCredentials = to(jsonObject, RlnMembershipCredentials)
|
||||
debug "Deserialized RLN credentials", rlnCredentials=deserializedRlnCredentials
|
||||
return ok(some(deserializedRlnCredentials))
|
||||
else:
|
||||
debug "Unable to decrypt RLN credentials with provided password. ", error=decodedKeyfiles.error
|
||||
return ok(none(RlnMembershipCredentials))
|
||||
except:
|
||||
return err("Error while loading keyfile for RLN credentials at " & path)
|
||||
|
||||
debug "Deserialized Rln credentials", rlnCredentials=deserializedRlnCredentials
|
||||
return deserializedRlnCredentials
|
||||
|
||||
proc mount(node: WakuNode,
|
||||
conf: WakuNodeConf|Chat2Conf,
|
||||
@ -1217,12 +1244,22 @@ proc mount(node: WakuNode,
|
||||
# if the path does not contain any credential file, then a new set is generated and pesisted in the same path
|
||||
# if there is a credential file, then no new credentials are generated, instead the content of the file is read and used to mount rln-relay
|
||||
if conf.rlnRelayCredPath != "":
|
||||
|
||||
let rlnRelayCredPath = joinPath(conf.rlnRelayCredPath, RlnCredentialsFilename)
|
||||
debug "rln-relay credential path", rlnRelayCredPath
|
||||
|
||||
# check if there is an rln-relay credential file in the supplied path
|
||||
if fileExists(rlnRelayCredPath):
|
||||
|
||||
info "A RLN credential file exists in provided path", path=rlnRelayCredPath
|
||||
|
||||
# retrieve rln-relay credential
|
||||
credentials = some(readPersistentRlnCredentials(rlnRelayCredPath))
|
||||
let readCredentialsRes = readRlnCredentials(rlnRelayCredPath, conf.rlnRelayCredentialsPassword)
|
||||
|
||||
if readCredentialsRes.isErr():
|
||||
return err("RLN credentials cannot be read: " & readCredentialsRes.error())
|
||||
|
||||
credentials = readCredentialsRes.get()
|
||||
|
||||
else: # there is no credential file available in the supplied path
|
||||
# mount the rln-relay protocol leaving rln-relay credentials arguments unassigned
|
||||
@ -1256,8 +1293,8 @@ proc mount(node: WakuNode,
|
||||
# persist rln credential
|
||||
credentials = some(RlnMembershipCredentials(rlnIndex: node.wakuRlnRelay.membershipIndex,
|
||||
membershipKeyPair: node.wakuRlnRelay.membershipKeyPair))
|
||||
writeFile(rlnRelayCredPath, pretty(%credentials.get()))
|
||||
|
||||
if writeRlnCredentials(rlnRelayCredPath, credentials.get(), conf.rlnRelayCredentialsPassword).isErr():
|
||||
return err("error in storing rln credentials")
|
||||
|
||||
else:
|
||||
# do not persist or use a persisted rln-relay credential
|
||||
|
573
waku/v2/utils/keyfile.nim
Normal file
573
waku/v2/utils/keyfile.nim
Normal file
@ -0,0 +1,573 @@
|
||||
# This implementation is originally taken from nim-eth keyfile module https://github.com/status-im/nim-eth/blob/master/eth/keyfile and adapted to
|
||||
# - create keyfiles for arbitrary-long input byte data (rather than fixed-size private keys)
|
||||
# - allow storage of multiple keyfiles (encrypted with different passwords) in same file and iteration among successful decryptions
|
||||
# - enable/disable at compilation time the keyfile id and version fields
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
std/[os, strutils, json, sequtils],
|
||||
nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak, scrypt],
|
||||
stew/results,
|
||||
eth/keys,
|
||||
eth/keyfile/uuid
|
||||
|
||||
export results
|
||||
|
||||
const
|
||||
# Version 3 constants
|
||||
SaltSize = 16
|
||||
DKLen = 32
|
||||
MaxDKLen = 128
|
||||
ScryptR = 1
|
||||
ScryptP = 8
|
||||
Pbkdf2WorkFactor = 1_000_000
|
||||
ScryptWorkFactor = 262_144
|
||||
|
||||
type
|
||||
KeyFileError* = enum
|
||||
RandomError = "keyfile error: Random generator error"
|
||||
UuidError = "keyfile error: UUID generator error"
|
||||
BufferOverrun = "keyfile error: Supplied buffer is too small"
|
||||
IncorrectDKLen = "keyfile error: `dklen` parameter is 0 or more then MaxDKLen"
|
||||
MalformedError = "keyfile error: JSON has incorrect structure"
|
||||
NotImplemented = "keyfile error: Feature is not implemented"
|
||||
NotSupported = "keyfile error: Feature is not supported"
|
||||
EmptyMac = "keyfile error: `mac` parameter is zero length or not in hexadecimal form"
|
||||
EmptyCiphertext = "keyfile error: `ciphertext` parameter is zero length or not in hexadecimal format"
|
||||
EmptySalt = "keyfile error: `salt` parameter is zero length or not in hexadecimal format"
|
||||
EmptyIV = "keyfile error: `cipherparams.iv` parameter is zero length or not in hexadecimal format"
|
||||
IncorrectIV = "keyfile error: Size of IV vector is not equal to cipher block size"
|
||||
PrfNotSupported = "keyfile error: PRF algorithm for PBKDF2 is not supported"
|
||||
KdfNotSupported = "keyfile error: KDF algorithm is not supported"
|
||||
CipherNotSupported = "keyfile error: `cipher` parameter is not supported"
|
||||
IncorrectMac = "keyfile error: `mac` verification failed"
|
||||
ScryptBadParam = "keyfile error: bad scrypt's parameters"
|
||||
OsError = "keyfile error: OS specific error"
|
||||
IoError = "keyfile error: IO specific error"
|
||||
JsonError = "keyfile error: JSON encoder/decoder error"
|
||||
KeyfileDoesNotExist = "keyfile error: file does not exist"
|
||||
|
||||
KdfKind* = enum
|
||||
PBKDF2, ## PBKDF2
|
||||
SCRYPT ## SCRYPT
|
||||
|
||||
HashKind* = enum
|
||||
HashNoSupport, HashSHA2_224, HashSHA2_256, HashSHA2_384, HashSHA2_512,
|
||||
HashKECCAK224, HashKECCAK256, HashKECCAK384, HashKECCAK512,
|
||||
HashSHA3_224, HashSHA3_256, HashSHA3_384, HashSHA3_512
|
||||
|
||||
CryptKind* = enum
|
||||
CipherNoSupport, ## Cipher not supported
|
||||
AES128CTR ## AES-128-CTR
|
||||
|
||||
CipherParams = object
|
||||
iv: seq[byte]
|
||||
|
||||
Cipher = object
|
||||
kind: CryptKind
|
||||
params: CipherParams
|
||||
text: seq[byte]
|
||||
|
||||
Crypto = object
|
||||
kind: KdfKind
|
||||
cipher: Cipher
|
||||
kdfParams: JsonNode
|
||||
mac: seq[byte]
|
||||
|
||||
ScryptParams* = object
|
||||
dklen: int
|
||||
n, p, r: int
|
||||
salt: string
|
||||
|
||||
Pbkdf2Params* = object
|
||||
dklen: int
|
||||
c: int
|
||||
prf: HashKind
|
||||
salt: string
|
||||
|
||||
DKey = array[DKLen, byte]
|
||||
KfResult*[T] = Result[T, KeyFileError]
|
||||
|
||||
const
|
||||
SupportedHashes = [
|
||||
"sha224", "sha256", "sha384", "sha512",
|
||||
"keccak224", "keccak256", "keccak384", "keccak512",
|
||||
"sha3_224", "sha3_256", "sha3_384", "sha3_512"
|
||||
]
|
||||
|
||||
SupportedHashesKinds = [
|
||||
HashSHA2_224, HashSHA2_256, HashSHA2_384, HashSHA2_512,
|
||||
HashKECCAK224, HashKECCAK256, HashKECCAK384, HashKECCAK512,
|
||||
HashSHA3_224, HashSHA3_256, HashSHA3_384, HashSHA3_512
|
||||
]
|
||||
|
||||
# When true, the keyfile json will contain "version" and "id" fields, respectively. Default to false.
|
||||
VersionInKeyfile: bool = false
|
||||
IdInKeyfile: bool = false
|
||||
|
||||
proc mapErrTo[T, E](r: Result[T, E], v: static KeyFileError): KfResult[T] =
|
||||
r.mapErr(proc (e: E): KeyFileError = v)
|
||||
|
||||
proc `$`(k: KdfKind): string =
|
||||
case k
|
||||
of SCRYPT:
|
||||
return "scrypt"
|
||||
else:
|
||||
return "pbkdf2"
|
||||
|
||||
proc `$`(k: CryptKind): string =
|
||||
case k
|
||||
of AES128CTR:
|
||||
return "aes-128-ctr"
|
||||
else:
|
||||
return "aes-128-ctr"
|
||||
|
||||
# Parses the prf name to HashKind
|
||||
proc getPrfHash(prf: string): HashKind =
|
||||
let p = prf.toLowerAscii()
|
||||
if p.startsWith("hmac-"):
|
||||
var hash = p[5..^1]
|
||||
var res = SupportedHashes.find(hash)
|
||||
if res >= 0:
|
||||
return SupportedHashesKinds[res]
|
||||
return HashNoSupport
|
||||
|
||||
# Parses the cipher name to CryptoKind
|
||||
proc getCipher(c: string): CryptKind =
|
||||
var cl = c.toLowerAscii()
|
||||
if cl == "aes-128-ctr":
|
||||
return AES128CTR
|
||||
else:
|
||||
return CipherNoSupport
|
||||
|
||||
# Key derivation routine for PBKDF2
|
||||
proc deriveKey(password: string,
|
||||
salt: string,
|
||||
kdfkind: KdfKind,
|
||||
hashkind: HashKind,
|
||||
workfactor: int): KfResult[DKey] =
|
||||
if kdfkind == PBKDF2:
|
||||
var output: DKey
|
||||
var c = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
case hashkind
|
||||
of HashSHA2_224:
|
||||
var ctx: HMAC[sha224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA2_256:
|
||||
var ctx: HMAC[sha256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA2_384:
|
||||
var ctx: HMAC[sha384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA2_512:
|
||||
var ctx: HMAC[sha512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashKECCAK224:
|
||||
var ctx: HMAC[keccak224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashKECCAK256:
|
||||
var ctx: HMAC[keccak256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashKECCAK384:
|
||||
var ctx: HMAC[keccak384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashKECCAK512:
|
||||
var ctx: HMAC[keccak512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA3_224:
|
||||
var ctx: HMAC[sha3_224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA3_256:
|
||||
var ctx: HMAC[sha3_256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA3_384:
|
||||
var ctx: HMAC[sha3_384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
of HashSHA3_512:
|
||||
var ctx: HMAC[sha3_512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ctx.clear()
|
||||
ok(output)
|
||||
else:
|
||||
err(PrfNotSupported)
|
||||
else:
|
||||
err(NotImplemented)
|
||||
|
||||
# Scrypt wrapper
|
||||
func scrypt[T, M](password: openArray[T], salt: openArray[M],
|
||||
N, r, p: int, output: var openArray[byte]): int =
|
||||
let (xyvLen, bLen) = scryptCalc(N, r, p)
|
||||
var xyv = newSeq[uint32](xyvLen)
|
||||
var b = newSeq[byte](bLen)
|
||||
scrypt(password, salt, N, r, p, xyv, b, output)
|
||||
|
||||
# Key derivation routine for Scrypt
|
||||
proc deriveKey(password: string, salt: string,
|
||||
workFactor, r, p: int): KfResult[DKey] =
|
||||
|
||||
let wf = if workFactor == 0: ScryptWorkFactor else: workFactor
|
||||
var output: DKey
|
||||
if scrypt(password, salt, wf, r, p, output) == 0:
|
||||
return err(ScryptBadParam)
|
||||
|
||||
return ok(output)
|
||||
|
||||
# Encryption routine
|
||||
proc encryptData(plaintext: openArray[byte],
|
||||
cryptkind: CryptKind,
|
||||
key: openArray[byte],
|
||||
iv: openArray[byte]): KfResult[seq[byte]] =
|
||||
if cryptkind == AES128CTR:
|
||||
var ciphertext = newSeqWith(plaintext.len, 0.byte)
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.encrypt(plaintext, ciphertext)
|
||||
ctx.clear()
|
||||
ok(ciphertext)
|
||||
else:
|
||||
err(NotImplemented)
|
||||
|
||||
# Decryption routine
|
||||
proc decryptData(ciphertext: openArray[byte],
|
||||
cryptkind: CryptKind,
|
||||
key: openArray[byte],
|
||||
iv: openArray[byte]): KfResult[seq[byte]] =
|
||||
if cryptkind == AES128CTR:
|
||||
if len(iv) != aes128.sizeBlock:
|
||||
return err(IncorrectIV)
|
||||
var plaintext = newSeqWith(ciphertext.len, 0.byte)
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.decrypt(ciphertext, plaintext)
|
||||
ctx.clear()
|
||||
ok(plaintext)
|
||||
else:
|
||||
err(NotImplemented)
|
||||
|
||||
# Encodes KDF parameters in JSON
|
||||
proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int): KfResult[JsonNode] =
|
||||
if kdfkind == SCRYPT:
|
||||
let wf = if workfactor == 0: ScryptWorkFactor else: workfactor
|
||||
ok(%*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"n": wf,
|
||||
"r": ScryptR,
|
||||
"p": ScryptP,
|
||||
"salt": salt
|
||||
}
|
||||
)
|
||||
elif kdfkind == PBKDF2:
|
||||
let wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
ok(%*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"c": wf,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": salt
|
||||
}
|
||||
)
|
||||
else:
|
||||
err(NotImplemented)
|
||||
|
||||
# Decodes hex strings to byte sequences
|
||||
proc decodeHex*(m: string): seq[byte] =
|
||||
if len(m) > 0:
|
||||
try:
|
||||
return utils.fromHex(m)
|
||||
except CatchableError:
|
||||
return newSeq[byte]()
|
||||
else:
|
||||
return newSeq[byte]()
|
||||
|
||||
# Parses the salt from hex string to byte string
|
||||
proc decodeSalt(m: string): string =
|
||||
var sarr: seq[byte]
|
||||
if len(m) > 0:
|
||||
try:
|
||||
sarr = utils.fromHex(m)
|
||||
var output = newString(len(sarr))
|
||||
copyMem(addr output[0], addr sarr[0], len(sarr))
|
||||
return output
|
||||
except CatchableError:
|
||||
return ""
|
||||
else:
|
||||
return ""
|
||||
|
||||
# Compares the message authentication code
|
||||
proc compareMac(m1: openArray[byte], m2: openArray[byte]): bool =
|
||||
if len(m1) == len(m2) and len(m1) > 0:
|
||||
return equalMem(unsafeAddr m1[0], unsafeAddr m2[0], len(m1))
|
||||
else:
|
||||
return false
|
||||
|
||||
# Creates a keyfile for secret encrypted with password according to the other parameters
|
||||
# Returns keyfile in JSON according to Web3 Secure storage format (here, differently than standard, version and id are optional)
|
||||
proc createKeyFileJson*(secret: openArray[byte],
|
||||
password: string,
|
||||
version: int = 3,
|
||||
cryptkind: CryptKind = AES128CTR,
|
||||
kdfkind: KdfKind = PBKDF2,
|
||||
workfactor: int = 0): KfResult[JsonNode] =
|
||||
## Create JSON object with keyfile structure.
|
||||
##
|
||||
## ``secret`` - secret data, which will be stored
|
||||
## ``password`` - encryption password
|
||||
## ``outjson`` - result JSON object
|
||||
## ``version`` - version of keyfile format (default is 3)
|
||||
## ``cryptkind`` - algorithm for encryption
|
||||
## (default is AES128-CTR)
|
||||
## ``kdfkind`` - algorithm for key deriviation function (default is PBKDF2)
|
||||
## ``workfactor`` - Key deriviation function work factor, 0 is to use
|
||||
## default workfactor.
|
||||
var iv: array[aes128.sizeBlock, byte]
|
||||
var salt: array[SaltSize, byte]
|
||||
var saltstr = newString(SaltSize)
|
||||
if randomBytes(iv) != aes128.sizeBlock:
|
||||
return err(RandomError)
|
||||
if randomBytes(salt) != SaltSize:
|
||||
return err(RandomError)
|
||||
copyMem(addr saltstr[0], addr salt[0], SaltSize)
|
||||
|
||||
let u = ? uuidGenerate().mapErrTo(UuidError)
|
||||
|
||||
let
|
||||
dkey = case kdfkind
|
||||
of PBKDF2: ? deriveKey(password, saltstr, kdfkind, HashSHA2_256, workfactor)
|
||||
of SCRYPT: ? deriveKey(password, saltstr, workfactor, ScryptR, ScryptP)
|
||||
|
||||
ciphertext = ? encryptData(secret, cryptkind, dkey, iv)
|
||||
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
ctx.update(toOpenArray(dkey, 16, 31))
|
||||
ctx.update(ciphertext)
|
||||
var mac = ctx.finish()
|
||||
ctx.clear()
|
||||
|
||||
let params = ? kdfParams(kdfkind, toHex(salt, true), workfactor)
|
||||
|
||||
let json = %*
|
||||
{
|
||||
"crypto": {
|
||||
"cipher": $cryptkind,
|
||||
"cipherparams": {
|
||||
"iv": toHex(iv, true)
|
||||
},
|
||||
"ciphertext": toHex(ciphertext, true),
|
||||
"kdf": $kdfkind,
|
||||
"kdfparams": params,
|
||||
"mac": toHex(mac.data, true),
|
||||
},
|
||||
}
|
||||
|
||||
if IdInKeyfile:
|
||||
json.add("id", %($u))
|
||||
if VersionInKeyfile:
|
||||
json.add("version", %version)
|
||||
|
||||
ok(json)
|
||||
|
||||
# Parses Cipher JSON information
|
||||
proc decodeCrypto(n: JsonNode): KfResult[Crypto] =
|
||||
var crypto = n.getOrDefault("crypto")
|
||||
if isNil(crypto):
|
||||
return err(MalformedError)
|
||||
|
||||
var kdf = crypto.getOrDefault("kdf")
|
||||
if isNil(kdf):
|
||||
return err(MalformedError)
|
||||
|
||||
var c: Crypto
|
||||
case kdf.getStr()
|
||||
of "pbkdf2": c.kind = PBKDF2
|
||||
of "scrypt": c.kind = SCRYPT
|
||||
else: return err(KdfNotSupported)
|
||||
|
||||
var cipherparams = crypto.getOrDefault("cipherparams")
|
||||
if isNil(cipherparams):
|
||||
return err(MalformedError)
|
||||
|
||||
c.cipher.kind = getCipher(crypto.getOrDefault("cipher").getStr())
|
||||
c.cipher.params.iv = decodeHex(cipherparams.getOrDefault("iv").getStr())
|
||||
c.cipher.text = decodeHex(crypto.getOrDefault("ciphertext").getStr())
|
||||
c.mac = decodeHex(crypto.getOrDefault("mac").getStr())
|
||||
c.kdfParams = crypto.getOrDefault("kdfparams")
|
||||
|
||||
if c.cipher.kind == CipherNoSupport:
|
||||
return err(CipherNotSupported)
|
||||
if len(c.cipher.text) == 0:
|
||||
return err(EmptyCiphertext)
|
||||
if len(c.mac) == 0:
|
||||
return err(EmptyMac)
|
||||
if isNil(c.kdfParams):
|
||||
return err(MalformedError)
|
||||
|
||||
return ok(c)
|
||||
|
||||
# Parses PNKDF2 JSON parameters
|
||||
proc decodePbkdf2Params(params: JsonNode): KfResult[Pbkdf2Params] =
|
||||
var p: Pbkdf2Params
|
||||
p.salt = decodeSalt(params.getOrDefault("salt").getStr())
|
||||
if len(p.salt) == 0:
|
||||
return err(EmptySalt)
|
||||
|
||||
p.dklen = params.getOrDefault("dklen").getInt()
|
||||
p.c = params.getOrDefault("c").getInt()
|
||||
p.prf = getPrfHash(params.getOrDefault("prf").getStr())
|
||||
|
||||
if p.prf == HashNoSupport:
|
||||
return err(PrfNotSupported)
|
||||
if p.dklen == 0 or p.dklen > MaxDKLen:
|
||||
return err(IncorrectDKLen)
|
||||
|
||||
return ok(p)
|
||||
|
||||
# Parses JSON Scrypt parameters
|
||||
proc decodeScryptParams(params: JsonNode): KfResult[ScryptParams] =
|
||||
var p: ScryptParams
|
||||
p.salt = decodeSalt(params.getOrDefault("salt").getStr())
|
||||
if len(p.salt) == 0:
|
||||
return err(EmptySalt)
|
||||
|
||||
p.dklen = params.getOrDefault("dklen").getInt()
|
||||
p.n = params.getOrDefault("n").getInt()
|
||||
p.p = params.getOrDefault("p").getInt()
|
||||
p.r = params.getOrDefault("r").getInt()
|
||||
|
||||
if p.dklen == 0 or p.dklen > MaxDKLen:
|
||||
return err(IncorrectDKLen)
|
||||
|
||||
return ok(p)
|
||||
|
||||
# Decrypts data
|
||||
func decryptSecret(crypto: Crypto, dkey: DKey): KfResult[seq[byte]] =
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
ctx.update(toOpenArray(dkey, 16, 31))
|
||||
ctx.update(crypto.cipher.text)
|
||||
var mac = ctx.finish()
|
||||
ctx.clear()
|
||||
if not compareMac(mac.data, crypto.mac):
|
||||
return err(IncorrectMac)
|
||||
|
||||
let plaintext = ? decryptData(crypto.cipher.text, crypto.cipher.kind, dkey, crypto.cipher.params.iv)
|
||||
|
||||
ok(plaintext)
|
||||
|
||||
# Parse JSON keyfile and decrypts its content using password
|
||||
proc decodeKeyFileJson*(j: JsonNode,
|
||||
password: string): KfResult[seq[byte]] =
|
||||
## Decode secret from keyfile json object ``j`` using
|
||||
## password string ``password``.
|
||||
let res = decodeCrypto(j)
|
||||
if res.isErr:
|
||||
return err(res.error)
|
||||
let crypto = res.get()
|
||||
|
||||
case crypto.kind
|
||||
of PBKDF2:
|
||||
let res = decodePbkdf2Params(crypto.kdfParams)
|
||||
if res.isErr:
|
||||
return err(res.error)
|
||||
|
||||
let params = res.get()
|
||||
let dkey = ? deriveKey(password, params.salt, PBKDF2, params.prf, params.c)
|
||||
return decryptSecret(crypto, dkey)
|
||||
|
||||
of SCRYPT:
|
||||
let res = decodeScryptParams(crypto.kdfParams)
|
||||
if res.isErr:
|
||||
return err(res.error)
|
||||
|
||||
let params = res.get()
|
||||
let dkey = ? deriveKey(password, params.salt, params.n, params.r, params.p)
|
||||
return decryptSecret(crypto, dkey)
|
||||
|
||||
# Loads the file at pathname, decrypts and returns all keyfiles encrypted under password
|
||||
proc loadKeyFiles*(pathname: string,
|
||||
password: string): KfResult[seq[KfResult[seq[byte]]]] =
|
||||
## Load and decode data from file with pathname
|
||||
## ``pathname``, using password string ``password``.
|
||||
## The index successful decryptions is returned
|
||||
var data: JsonNode
|
||||
var decodedKeyfile: KfResult[seq[byte]]
|
||||
var successfullyDecodedKeyfiles: seq[KfResult[seq[byte]]]
|
||||
|
||||
if fileExists(pathname) == false:
|
||||
return err(KeyfileDoesNotExist)
|
||||
|
||||
# Note that lines strips the ending newline, if present
|
||||
try:
|
||||
for keyfile in lines(pathname):
|
||||
|
||||
# We skip empty lines
|
||||
if keyfile.len == 0:
|
||||
continue
|
||||
# We skip all lines that doesn't seem to define a json
|
||||
if keyfile[0] != '{' or keyfile[^1] != '}':
|
||||
continue
|
||||
|
||||
try:
|
||||
data = json.parseJson(keyfile)
|
||||
except JsonParsingError:
|
||||
return err(JsonError)
|
||||
except ValueError:
|
||||
return err(JsonError)
|
||||
except OSError:
|
||||
return err(OsError)
|
||||
except Exception: #parseJson raises Exception
|
||||
return err(OsError)
|
||||
|
||||
decodedKeyfile = decodeKeyFileJson(data, password)
|
||||
if decodedKeyfile.isOk():
|
||||
successfullyDecodedKeyfiles.add decodedKeyfile
|
||||
|
||||
except IOError:
|
||||
return err(IoError)
|
||||
|
||||
return ok(successfullyDecodedKeyfiles)
|
||||
|
||||
# Note that the keyfile is open in Append mode so that multiple credentials can be stored in same file
|
||||
proc saveKeyFile*(pathname: string,
|
||||
jobject: JsonNode): KfResult[void] =
|
||||
## Save JSON object ``jobject`` to file with pathname ``pathname``.
|
||||
var
|
||||
f: File
|
||||
if not f.open(pathname, fmAppend):
|
||||
return err(OsError)
|
||||
try:
|
||||
# To avoid other users/attackers to be able to read keyfiles, we make the file readable/writable only by the running user
|
||||
setFilePermissions(pathname, {fpUserWrite, fpUserRead})
|
||||
f.write($jobject)
|
||||
# We store a keyfile per line
|
||||
f.write("\n")
|
||||
ok()
|
||||
except CatchableError:
|
||||
err(OsError)
|
||||
finally:
|
||||
f.close()
|
||||
|
Loading…
x
Reference in New Issue
Block a user