Keystore cache implementation. (#4372)
This commit is contained in:
parent
d63179ab57
commit
e91415662b
|
@ -73,6 +73,7 @@ type
|
||||||
restServer*: RestServerRef
|
restServer*: RestServerRef
|
||||||
keymanagerHost*: ref KeymanagerHost
|
keymanagerHost*: ref KeymanagerHost
|
||||||
keymanagerServer*: RestServerRef
|
keymanagerServer*: RestServerRef
|
||||||
|
keystoreCache*: KeystoreCacheRef
|
||||||
eventBus*: EventBus
|
eventBus*: EventBus
|
||||||
vcProcess*: Process
|
vcProcess*: Process
|
||||||
requestManager*: RequestManager
|
requestManager*: RequestManager
|
||||||
|
|
|
@ -122,6 +122,10 @@ type
|
||||||
Mainnet = "mainnet"
|
Mainnet = "mainnet"
|
||||||
None = "none"
|
None = "none"
|
||||||
|
|
||||||
|
ImportMethod* {.pure.} = enum
|
||||||
|
Normal = "normal"
|
||||||
|
SingleSalt = "single-salt"
|
||||||
|
|
||||||
BeaconNodeConf* = object
|
BeaconNodeConf* = object
|
||||||
configFile* {.
|
configFile* {.
|
||||||
desc: "Loads the configuration from a TOML file"
|
desc: "Loads the configuration from a TOML file"
|
||||||
|
@ -714,6 +718,12 @@ type
|
||||||
argument
|
argument
|
||||||
desc: "A directory with keystores to import" .}: Option[InputDir]
|
desc: "A directory with keystores to import" .}: Option[InputDir]
|
||||||
|
|
||||||
|
importMethod* {.
|
||||||
|
desc: "Specifies which import method will be used (" &
|
||||||
|
"normal, single-salt)"
|
||||||
|
defaultValue: ImportMethod.Normal
|
||||||
|
name: "method" .}: ImportMethod
|
||||||
|
|
||||||
of DepositsCmd.exit:
|
of DepositsCmd.exit:
|
||||||
exitedValidator* {.
|
exitedValidator* {.
|
||||||
name: "validator"
|
name: "validator"
|
||||||
|
|
|
@ -34,7 +34,8 @@ proc getSignedExitMessage(config: BeaconNodeConf,
|
||||||
validatorsDir,
|
validatorsDir,
|
||||||
config.secretsDir,
|
config.secretsDir,
|
||||||
validatorKeyAsStr,
|
validatorKeyAsStr,
|
||||||
config.nonInteractive)
|
config.nonInteractive,
|
||||||
|
nil)
|
||||||
|
|
||||||
if signingItem.isNone:
|
if signingItem.isNone:
|
||||||
fatal "Unable to continue without decrypted signing key"
|
fatal "Unable to continue without decrypted signing key"
|
||||||
|
@ -344,7 +345,7 @@ proc doDeposits*(config: BeaconNodeConf, rng: var HmacDrbgContext) {.
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
importKeystoresFromDir(
|
importKeystoresFromDir(
|
||||||
rng,
|
rng, config.importMethod,
|
||||||
validatorKeysDir.string,
|
validatorKeysDir.string,
|
||||||
config.validatorsDir, config.secretsDir)
|
config.validatorsDir, config.secretsDir)
|
||||||
|
|
||||||
|
|
|
@ -675,6 +675,7 @@ proc init*(T: type BeaconNode,
|
||||||
restServer: restServer,
|
restServer: restServer,
|
||||||
keymanagerHost: keymanagerHost,
|
keymanagerHost: keymanagerHost,
|
||||||
keymanagerServer: keymanagerInitResult.server,
|
keymanagerServer: keymanagerInitResult.server,
|
||||||
|
keystoreCache: KeystoreCacheRef.init(),
|
||||||
eventBus: eventBus,
|
eventBus: eventBus,
|
||||||
gossipState: {},
|
gossipState: {},
|
||||||
blocksGossipState: {},
|
blocksGossipState: {},
|
||||||
|
@ -1612,6 +1613,7 @@ proc run(node: BeaconNode) {.raises: [Defect, CatchableError].} =
|
||||||
asyncSpawn runSlotLoop(node, wallTime, onSlotStart)
|
asyncSpawn runSlotLoop(node, wallTime, onSlotStart)
|
||||||
asyncSpawn runOnSecondLoop(node)
|
asyncSpawn runOnSecondLoop(node)
|
||||||
asyncSpawn runQueueProcessingLoop(node.blockProcessor)
|
asyncSpawn runQueueProcessingLoop(node.blockProcessor)
|
||||||
|
asyncSpawn runKeystoreCachePruningLoop(node.keystoreCache)
|
||||||
|
|
||||||
## Ctrl+C handling
|
## Ctrl+C handling
|
||||||
proc controlCHandler() {.noconv.} =
|
proc controlCHandler() {.noconv.} =
|
||||||
|
|
|
@ -19,7 +19,7 @@ import
|
||||||
stew/io2,
|
stew/io2,
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
./spec/[helpers],
|
./spec/[helpers, keystore],
|
||||||
./spec/datatypes/base,
|
./spec/datatypes/base,
|
||||||
"."/[beacon_clock, beacon_node_status, conf, version]
|
"."/[beacon_clock, beacon_node_status, conf, version]
|
||||||
|
|
||||||
|
@ -245,6 +245,18 @@ proc resetStdin*() =
|
||||||
attrs.c_lflag = attrs.c_lflag or Cflag(ECHO)
|
attrs.c_lflag = attrs.c_lflag or Cflag(ECHO)
|
||||||
discard fd.tcSetAttr(TCSANOW, attrs.addr)
|
discard fd.tcSetAttr(TCSANOW, attrs.addr)
|
||||||
|
|
||||||
|
proc runKeystoreCachePruningLoop*(cache: KeystoreCacheRef) {.async.} =
|
||||||
|
while true:
|
||||||
|
let exitLoop =
|
||||||
|
try:
|
||||||
|
await sleepAsync(60.seconds)
|
||||||
|
false
|
||||||
|
except CatchableError:
|
||||||
|
cache.clear()
|
||||||
|
true
|
||||||
|
if exitLoop: break
|
||||||
|
cache.pruneExpiredKeys()
|
||||||
|
|
||||||
proc runSlotLoop*[T](node: T, startTime: BeaconTime,
|
proc runSlotLoop*[T](node: T, startTime: BeaconTime,
|
||||||
slotProc: SlotStartProc[T]) {.async.} =
|
slotProc: SlotStartProc[T]) {.async.} =
|
||||||
var
|
var
|
||||||
|
|
|
@ -34,6 +34,7 @@ type
|
||||||
config: SigningNodeConf
|
config: SigningNodeConf
|
||||||
attachedValidators: ValidatorPool
|
attachedValidators: ValidatorPool
|
||||||
signingServer: SigningNodeServer
|
signingServer: SigningNodeServer
|
||||||
|
keystoreCache: KeystoreCacheRef
|
||||||
keysList: string
|
keysList: string
|
||||||
|
|
||||||
proc getRouter*(): RestRouter
|
proc getRouter*(): RestRouter
|
||||||
|
@ -97,7 +98,7 @@ proc loadTLSKey(pathName: InputFile): Result[TLSPrivateKey, cstring] =
|
||||||
proc initValidators(sn: var SigningNode): bool =
|
proc initValidators(sn: var SigningNode): bool =
|
||||||
info "Initializaing validators", path = sn.config.validatorsDir()
|
info "Initializaing validators", path = sn.config.validatorsDir()
|
||||||
var publicKeyIdents: seq[string]
|
var publicKeyIdents: seq[string]
|
||||||
for keystore in listLoadableKeystores(sn.config):
|
for keystore in listLoadableKeystores(sn.config, sn.keystoreCache):
|
||||||
# Not relevant in signing node
|
# Not relevant in signing node
|
||||||
# TODO don't print when loading validators
|
# TODO don't print when loading validators
|
||||||
let feeRecipient = default(Eth1Address)
|
let feeRecipient = default(Eth1Address)
|
||||||
|
@ -115,12 +116,17 @@ proc initValidators(sn: var SigningNode): bool =
|
||||||
true
|
true
|
||||||
|
|
||||||
proc init(t: typedesc[SigningNode], config: SigningNodeConf): SigningNode =
|
proc init(t: typedesc[SigningNode], config: SigningNodeConf): SigningNode =
|
||||||
var sn = SigningNode(config: config)
|
var sn = SigningNode(
|
||||||
|
config: config,
|
||||||
|
keystoreCache: KeystoreCacheRef.init()
|
||||||
|
)
|
||||||
|
|
||||||
if not(initValidators(sn)):
|
if not(initValidators(sn)):
|
||||||
fatal "Could not find/initialize local validators"
|
fatal "Could not find/initialize local validators"
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
|
asyncSpawn runKeystoreCachePruningLoop(sn.keystoreCache)
|
||||||
|
|
||||||
let
|
let
|
||||||
address = initTAddress(config.bindAddress, config.bindPort)
|
address = initTAddress(config.bindAddress, config.bindPort)
|
||||||
serverFlags = {HttpServerFlags.QueryCommaSeparatedArray,
|
serverFlags = {HttpServerFlags.QueryCommaSeparatedArray,
|
||||||
|
|
|
@ -86,7 +86,7 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} =
|
||||||
proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} =
|
proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} =
|
||||||
info "Loading validators", validatorsDir = vc.config.validatorsDir()
|
info "Loading validators", validatorsDir = vc.config.validatorsDir()
|
||||||
var duplicates: seq[ValidatorPubKey]
|
var duplicates: seq[ValidatorPubKey]
|
||||||
for keystore in listLoadableKeystores(vc.config):
|
for keystore in listLoadableKeystores(vc.config, vc.keystoreCache):
|
||||||
vc.addValidator(keystore)
|
vc.addValidator(keystore)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@ -208,7 +208,8 @@ proc new*(T: type ValidatorClientRef,
|
||||||
indicesAvailable: newAsyncEvent(),
|
indicesAvailable: newAsyncEvent(),
|
||||||
dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()),
|
dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()),
|
||||||
sigintHandleFut: waitSignal(SIGINT),
|
sigintHandleFut: waitSignal(SIGINT),
|
||||||
sigtermHandleFut: waitSignal(SIGTERM)
|
sigtermHandleFut: waitSignal(SIGTERM),
|
||||||
|
keystoreCache: KeystoreCacheRef.init()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ValidatorClientRef(
|
ValidatorClientRef(
|
||||||
|
@ -222,7 +223,8 @@ proc new*(T: type ValidatorClientRef,
|
||||||
doppelExit: newAsyncEvent(),
|
doppelExit: newAsyncEvent(),
|
||||||
dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()),
|
dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()),
|
||||||
sigintHandleFut: newFuture[void]("sigint_placeholder"),
|
sigintHandleFut: newFuture[void]("sigint_placeholder"),
|
||||||
sigtermHandleFut: newFuture[void]("sigterm_placeholder")
|
sigtermHandleFut: newFuture[void]("sigterm_placeholder"),
|
||||||
|
keystoreCache: KeystoreCacheRef.init()
|
||||||
)
|
)
|
||||||
|
|
||||||
proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
|
proc asyncInit(vc: ValidatorClientRef): Future[ValidatorClientRef] {.async.} =
|
||||||
|
@ -311,6 +313,8 @@ proc asyncRun*(vc: ValidatorClientRef) {.async.} =
|
||||||
var doppelEventFut = vc.doppelExit.wait()
|
var doppelEventFut = vc.doppelExit.wait()
|
||||||
try:
|
try:
|
||||||
vc.runSlotLoopFut = runSlotLoop(vc, vc.beaconClock.now(), onSlotStart)
|
vc.runSlotLoopFut = runSlotLoop(vc, vc.beaconClock.now(), onSlotStart)
|
||||||
|
vc.runKeystoreCachePruningLoopFut =
|
||||||
|
runKeystorecachePruningLoop(vc.keystoreCache)
|
||||||
discard await race(vc.runSlotLoopFut, doppelEventFut)
|
discard await race(vc.runSlotLoopFut, doppelEventFut)
|
||||||
if not(vc.runSlotLoopFut.finished()):
|
if not(vc.runSlotLoopFut.finished()):
|
||||||
notice "Received shutdown event, exiting"
|
notice "Received shutdown event, exiting"
|
||||||
|
@ -334,6 +338,8 @@ proc asyncRun*(vc: ValidatorClientRef) {.async.} =
|
||||||
var pending: seq[Future[void]]
|
var pending: seq[Future[void]]
|
||||||
if not(vc.runSlotLoopFut.finished()):
|
if not(vc.runSlotLoopFut.finished()):
|
||||||
pending.add(vc.runSlotLoopFut.cancelAndWait())
|
pending.add(vc.runSlotLoopFut.cancelAndWait())
|
||||||
|
if not(vc.runKeystoreCachePruningLoopFut.finished()):
|
||||||
|
pending.add(vc.runKeystoreCachePruningLoopFut.cancelAndWait())
|
||||||
if not(doppelEventFut.finished()):
|
if not(doppelEventFut.finished()):
|
||||||
pending.add(doppelEventFut.cancelAndWait())
|
pending.add(doppelEventFut.cancelAndWait())
|
||||||
debug "Stopping running services"
|
debug "Stopping running services"
|
||||||
|
|
|
@ -10,13 +10,14 @@
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode,
|
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode,
|
||||||
uri],
|
uri, hashes],
|
||||||
# Third-party libraries
|
# Third-party libraries
|
||||||
normalize,
|
normalize,
|
||||||
# Status libraries
|
# Status libraries
|
||||||
stew/[results, bitops2, base10, io2], stew/shims/macros,
|
stew/[results, bitops2, base10, io2, endians2], stew/shims/macros,
|
||||||
eth/keyfile/uuid, blscurve,
|
eth/keyfile/uuid, blscurve,
|
||||||
json_serialization, json_serialization/std/options,
|
json_serialization, json_serialization/std/options,
|
||||||
|
chronos/timer,
|
||||||
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
||||||
# Local modules
|
# Local modules
|
||||||
libp2p/crypto/crypto as lcrypto,
|
libp2p/crypto/crypto as lcrypto,
|
||||||
|
@ -198,6 +199,24 @@ type
|
||||||
|
|
||||||
SimpleHexEncodedTypes* = ScryptSalt|ChecksumBytes|CipherBytes
|
SimpleHexEncodedTypes* = ScryptSalt|ChecksumBytes|CipherBytes
|
||||||
|
|
||||||
|
CacheItemFlag {.pure.} = enum
|
||||||
|
Missing, Present
|
||||||
|
|
||||||
|
KeystoreCacheItem = object
|
||||||
|
flag: CacheItemFlag
|
||||||
|
kdf: Kdf
|
||||||
|
cipher: Cipher
|
||||||
|
decryptionKey: seq[byte]
|
||||||
|
timestamp: Moment
|
||||||
|
|
||||||
|
KdfSaltKey* = distinct array[32, byte]
|
||||||
|
|
||||||
|
KeystoreCache* = object
|
||||||
|
expireTime*: Duration
|
||||||
|
table*: Table[KdfSaltKey, KeystoreCacheItem]
|
||||||
|
|
||||||
|
KeystoreCacheRef* = ref KeystoreCache
|
||||||
|
|
||||||
const
|
const
|
||||||
keyLen = 32
|
keyLen = 32
|
||||||
|
|
||||||
|
@ -223,6 +242,8 @@ const
|
||||||
wordListLen = 2048
|
wordListLen = 2048
|
||||||
maxWordLen = 16
|
maxWordLen = 16
|
||||||
|
|
||||||
|
KeystoreCachePruningTime* = 5.minutes
|
||||||
|
|
||||||
UUID.serializesAsBaseIn Json
|
UUID.serializesAsBaseIn Json
|
||||||
KeyPath.serializesAsBaseIn Json
|
KeyPath.serializesAsBaseIn Json
|
||||||
WalletName.serializesAsBaseIn Json
|
WalletName.serializesAsBaseIn Json
|
||||||
|
@ -736,48 +757,56 @@ func areValid(params: ScryptParams): bool =
|
||||||
params.p == scryptParams.p and
|
params.p == scryptParams.p and
|
||||||
params.salt.bytes.len > 0
|
params.salt.bytes.len > 0
|
||||||
|
|
||||||
|
proc decryptCryptoField*(crypto: Crypto, decKey: openArray[byte],
|
||||||
|
outSecret: var seq[byte]): DecryptionStatus =
|
||||||
|
if crypto.cipher.message.bytes.len == 0:
|
||||||
|
return DecryptionStatus.InvalidKeystore
|
||||||
|
if len(decKey) < keyLen:
|
||||||
|
return DecryptionStatus.InvalidKeystore
|
||||||
|
let derivedChecksum = shaChecksum(decKey.toOpenArray(16, 31),
|
||||||
|
crypto.cipher.message.bytes)
|
||||||
|
if derivedChecksum != crypto.checksum.message:
|
||||||
|
return DecryptionStatus.InvalidPassword
|
||||||
|
|
||||||
|
var aesCipher: CTR[aes128]
|
||||||
|
outSecret.setLen(crypto.cipher.message.bytes.len)
|
||||||
|
aesCipher.init(decKey.toOpenArray(0, 15), crypto.cipher.params.iv.bytes)
|
||||||
|
aesCipher.decrypt(crypto.cipher.message.bytes, outSecret)
|
||||||
|
aesCipher.clear()
|
||||||
|
DecryptionStatus.Success
|
||||||
|
|
||||||
|
proc getDecryptionKey*(crypto: Crypto, password: KeystorePass,
|
||||||
|
decKey: var seq[byte]): DecryptionStatus =
|
||||||
|
let res =
|
||||||
|
case crypto.kdf.function
|
||||||
|
of kdfPbkdf2:
|
||||||
|
template params: auto = crypto.kdf.pbkdf2Params
|
||||||
|
if not params.areValid or params.c > high(int).uint64:
|
||||||
|
return DecryptionStatus.InvalidKeystore
|
||||||
|
Eth2DigestCtx.pbkdf2(password.str, params.salt.bytes, int(params.c),
|
||||||
|
int(params.dklen))
|
||||||
|
of kdfScrypt:
|
||||||
|
template params: auto = crypto.kdf.scryptParams
|
||||||
|
if not params.areValid:
|
||||||
|
return DecryptionStatus.InvalidKeystore
|
||||||
|
@(scrypt(password.str, params.salt.bytes, scryptParams.n,
|
||||||
|
scryptParams.r, scryptParams.p, int(scryptParams.dklen)))
|
||||||
|
decKey = res
|
||||||
|
DecryptionStatus.Success
|
||||||
|
|
||||||
proc decryptCryptoField*(crypto: Crypto,
|
proc decryptCryptoField*(crypto: Crypto,
|
||||||
password: KeystorePass,
|
password: KeystorePass,
|
||||||
outSecret: var seq[byte]): DecryptionStatus =
|
outSecret: var seq[byte]): DecryptionStatus =
|
||||||
# https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
# https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||||
|
var decKey: seq[byte]
|
||||||
if crypto.cipher.message.bytes.len == 0:
|
if crypto.cipher.message.bytes.len == 0:
|
||||||
return InvalidKeystore
|
return InvalidKeystore
|
||||||
|
|
||||||
let decKey = case crypto.kdf.function
|
let res = getDecryptionKey(crypto, password, decKey)
|
||||||
of kdfPbkdf2:
|
if res != DecryptionStatus.Success:
|
||||||
template params: auto = crypto.kdf.pbkdf2Params
|
return res
|
||||||
if not params.areValid or params.c > high(int).uint64:
|
|
||||||
return InvalidKeystore
|
|
||||||
Eth2DigestCtx.pbkdf2(
|
|
||||||
password.str,
|
|
||||||
params.salt.bytes,
|
|
||||||
int params.c,
|
|
||||||
int params.dklen)
|
|
||||||
of kdfScrypt:
|
|
||||||
template params: auto = crypto.kdf.scryptParams
|
|
||||||
if not params.areValid:
|
|
||||||
return InvalidKeystore
|
|
||||||
@(scrypt(password.str,
|
|
||||||
params.salt.bytes,
|
|
||||||
scryptParams.n,
|
|
||||||
scryptParams.r,
|
|
||||||
scryptParams.p,
|
|
||||||
int scryptParams.dklen))
|
|
||||||
|
|
||||||
let derivedChecksum = shaChecksum(decKey.toOpenArray(16, 31),
|
decryptCryptoField(crypto, decKey, outSecret)
|
||||||
crypto.cipher.message.bytes)
|
|
||||||
if derivedChecksum != crypto.checksum.message:
|
|
||||||
return InvalidPassword
|
|
||||||
|
|
||||||
var aesCipher: CTR[aes128]
|
|
||||||
outSecret.setLen(crypto.cipher.message.bytes.len)
|
|
||||||
|
|
||||||
aesCipher.init(decKey.toOpenArray(0, 15), crypto.cipher.params.iv.bytes)
|
|
||||||
aesCipher.decrypt(crypto.cipher.message.bytes, outSecret)
|
|
||||||
aesCipher.clear()
|
|
||||||
|
|
||||||
return Success
|
|
||||||
|
|
||||||
func cstringToStr(v: cstring): string = $v
|
func cstringToStr(v: cstring): string = $v
|
||||||
|
|
||||||
|
@ -796,23 +825,167 @@ template parseRemoteKeystore*(jsonContent: string): RemoteKeystore =
|
||||||
requireAllFields = false,
|
requireAllFields = false,
|
||||||
allowUnknownFields = true)
|
allowUnknownFields = true)
|
||||||
|
|
||||||
|
proc getSaltKey(keystore: Keystore, password: KeystorePass): KdfSaltKey =
|
||||||
|
let digest =
|
||||||
|
case keystore.crypto.kdf.function
|
||||||
|
of kdfPbkdf2:
|
||||||
|
template params: auto = keystore.crypto.kdf.pbkdf2Params
|
||||||
|
withEth2Hash:
|
||||||
|
h.update(seq[byte](params.salt))
|
||||||
|
h.update(password.str.toOpenArrayByte(0, len(password.str) - 1))
|
||||||
|
h.update(toBytesLE(params.dklen))
|
||||||
|
h.update(toBytesLE(params.c))
|
||||||
|
let prf = $params.prf
|
||||||
|
h.update(prf.toOpenArrayByte(0, len(prf) - 1))
|
||||||
|
of kdfScrypt:
|
||||||
|
template params: auto = keystore.crypto.kdf.scryptParams
|
||||||
|
withEth2Hash:
|
||||||
|
h.update(seq[byte](params.salt))
|
||||||
|
h.update(password.str.toOpenArrayByte(0, len(password.str) - 1))
|
||||||
|
h.update(toBytesLE(params.dklen))
|
||||||
|
h.update(toBytesLE(uint64(params.n)))
|
||||||
|
h.update(toBytesLE(uint64(params.p)))
|
||||||
|
h.update(toBytesLE(uint64(params.r)))
|
||||||
|
KdfSaltKey(digest.data)
|
||||||
|
|
||||||
|
proc `==`*(a, b: KdfSaltKey): bool {.borrow.}
|
||||||
|
proc hash*(salt: KdfSaltKey): Hash {.borrow.}
|
||||||
|
|
||||||
|
func `==`*(a, b: Kdf): bool =
|
||||||
|
# We do not care about `message` field.
|
||||||
|
if a.function != b.function:
|
||||||
|
return false
|
||||||
|
case a.function
|
||||||
|
of kdfPbkdf2:
|
||||||
|
template aparams: auto = a.pbkdf2Params
|
||||||
|
template bparams: auto = b.pbkdf2Params
|
||||||
|
(aparams.dklen == bparams.dklen) and (aparams.c == bparams.c) and
|
||||||
|
(aparams.prf == bparams.prf) and (len(seq[byte](aparams.salt)) > 0) and
|
||||||
|
(seq[byte](aparams.salt) == seq[byte](bparams.salt))
|
||||||
|
of kdfScrypt:
|
||||||
|
template aparams: auto = a.scryptParams
|
||||||
|
template bparams: auto = b.scryptParams
|
||||||
|
(aparams.dklen == bparams.dklen) and (aparams.n == bparams.n) and
|
||||||
|
(aparams.p == bparams.p) and (aparams.r == bparams.r) and
|
||||||
|
(len(seq[byte](aparams.salt)) > 0) and
|
||||||
|
(seq[byte](aparams.salt) == seq[byte](bparams.salt))
|
||||||
|
|
||||||
|
func `==`*(a, b: Cipher): bool =
|
||||||
|
# We do not care about `params` and `message` fields.
|
||||||
|
a.function == b.function
|
||||||
|
|
||||||
|
func `==`*(a, b: KeystoreCacheItem): bool =
|
||||||
|
(a.kdf == b.kdf) and (a.cipher == b.cipher) and
|
||||||
|
(a.decryptionKey == b.decryptionKey)
|
||||||
|
|
||||||
|
func init*(t: typedesc[KeystoreCacheRef],
|
||||||
|
expireTime = KeystoreCachePruningTime): KeystoreCacheRef =
|
||||||
|
KeystoreCacheRef(
|
||||||
|
table: initTable[KdfSaltKey, KeystoreCacheItem](),
|
||||||
|
expireTime: expireTime
|
||||||
|
)
|
||||||
|
|
||||||
|
proc clear*(cache: KeystoreCacheRef) =
|
||||||
|
cache.table.clear()
|
||||||
|
|
||||||
|
proc pruneExpiredKeys*(cache: KeystoreCacheRef) =
|
||||||
|
if cache.expireTime == InfiniteDuration:
|
||||||
|
return
|
||||||
|
let currentTime = Moment.now()
|
||||||
|
var keys: seq[KdfSaltKey]
|
||||||
|
for key, value in cache.table.mpairs():
|
||||||
|
if currentTime - value.timestamp >= cache.expireTime:
|
||||||
|
keys.add(key)
|
||||||
|
burnMem(value.decryptionKey)
|
||||||
|
for item in keys:
|
||||||
|
cache.table.del(item)
|
||||||
|
|
||||||
|
proc init*(t: typedesc[KeystoreCacheItem], keystore: Keystore,
|
||||||
|
key: openArray[byte]): KeystoreCacheItem =
|
||||||
|
KeystoreCacheItem(flag: CacheItemFlag.Present, kdf: keystore.crypto.kdf,
|
||||||
|
cipher: keystore.crypto.cipher, decryptionKey: @key,
|
||||||
|
timestamp: Moment.now())
|
||||||
|
|
||||||
|
proc getCachedKey*(cache: KeystoreCacheRef,
|
||||||
|
keystore: Keystore, password: KeystorePass): Opt[seq[byte]] =
|
||||||
|
if isNil(cache): return Opt.none(seq[byte])
|
||||||
|
let
|
||||||
|
saltKey = keystore.getSaltKey(password)
|
||||||
|
item = cache.table.getOrDefault(saltKey)
|
||||||
|
case item.flag
|
||||||
|
of CacheItemFlag.Present:
|
||||||
|
if (item.kdf == keystore.crypto.kdf) and
|
||||||
|
(item.cipher == keystore.crypto.cipher):
|
||||||
|
Opt.some(item.decryptionKey)
|
||||||
|
else:
|
||||||
|
Opt.none(seq[byte])
|
||||||
|
else:
|
||||||
|
Opt.none(seq[byte])
|
||||||
|
|
||||||
|
proc setCachedKey*(cache: KeystoreCacheRef, keystore: Keystore,
|
||||||
|
password: KeystorePass, key: openArray[byte]) =
|
||||||
|
if isNil(cache): return
|
||||||
|
let saltKey = keystore.getSaltKey(password)
|
||||||
|
cache.table[saltKey] = KeystoreCacheItem.init(keystore, key)
|
||||||
|
|
||||||
|
proc destroyCacheKey*(cache: KeystoreCacheRef,
|
||||||
|
keystore: Keystore, password: KeystorePass) =
|
||||||
|
if isNil(cache): return
|
||||||
|
let saltKey = keystore.getSaltKey(password)
|
||||||
|
cache.table.withValue(saltKey, item):
|
||||||
|
burnMem(item[].decryptionKey)
|
||||||
|
cache.table.del(saltKey)
|
||||||
|
|
||||||
proc decryptKeystore*(keystore: Keystore,
|
proc decryptKeystore*(keystore: Keystore,
|
||||||
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
password: KeystorePass,
|
||||||
|
cache: KeystoreCacheRef): KsResult[ValidatorPrivKey] =
|
||||||
var secret: seq[byte]
|
var secret: seq[byte]
|
||||||
defer: burnMem(secret)
|
defer: burnMem(secret)
|
||||||
let status = decryptCryptoField(keystore.crypto, password, secret)
|
|
||||||
case status
|
while true:
|
||||||
of Success:
|
let res = cache.getCachedKey(keystore, password)
|
||||||
ValidatorPrivKey.fromRaw(secret).mapErr(cstringToStr)
|
if res.isNone():
|
||||||
|
var decKey: seq[byte]
|
||||||
|
defer: burnMem(decKey)
|
||||||
|
|
||||||
|
let kres = getDecryptionKey(keystore.crypto, password, decKey)
|
||||||
|
if kres != DecryptionStatus.Success:
|
||||||
|
return err($kres)
|
||||||
|
let dres = decryptCryptoField(keystore.crypto, decKey, secret)
|
||||||
|
if dres != DecryptionStatus.Success:
|
||||||
|
return err($dres)
|
||||||
|
cache.setCachedKey(keystore, password, decKey)
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
err $status
|
var decKey = res.get()
|
||||||
|
defer: burnMem(decKey)
|
||||||
|
|
||||||
|
let dres = decryptCryptoField(keystore.crypto, decKey, secret)
|
||||||
|
if dres == DecryptionStatus.Success:
|
||||||
|
break
|
||||||
|
|
||||||
|
cache.destroyCacheKey(keystore, password)
|
||||||
|
|
||||||
|
ValidatorPrivKey.fromRaw(secret).mapErr(cstringToStr)
|
||||||
|
|
||||||
|
proc decryptKeystore*(keystore: JsonString,
|
||||||
|
password: KeystorePass,
|
||||||
|
cache: KeystoreCacheRef): KsResult[ValidatorPrivKey] =
|
||||||
|
let keystore =
|
||||||
|
try:
|
||||||
|
parseKeystore(string(keystore))
|
||||||
|
except SerializationError as e:
|
||||||
|
return err(e.formatMsg("<keystore>"))
|
||||||
|
|
||||||
|
decryptKeystore(keystore, password, cache)
|
||||||
|
|
||||||
|
proc decryptKeystore*(keystore: Keystore,
|
||||||
|
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
||||||
|
decryptKeystore(keystore, password, nil)
|
||||||
|
|
||||||
proc decryptKeystore*(keystore: JsonString,
|
proc decryptKeystore*(keystore: JsonString,
|
||||||
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
||||||
let keystore = try: parseKeystore(string keystore)
|
decryptKeystore(keystore, password, nil)
|
||||||
except SerializationError as e:
|
|
||||||
return err e.formatMsg("<keystore>")
|
|
||||||
decryptKeystore(keystore, password)
|
|
||||||
|
|
||||||
proc writeValue*(writer: var JsonWriter, value: lcrypto.PublicKey) {.
|
proc writeValue*(writer: var JsonWriter, value: lcrypto.PublicKey) {.
|
||||||
inline, raises: [IOError, Defect].} =
|
inline, raises: [IOError, Defect].} =
|
||||||
|
@ -851,6 +1024,9 @@ proc decryptNetKeystore*(nkeystore: JsonString,
|
||||||
except SerializationError as exc:
|
except SerializationError as exc:
|
||||||
return err(exc.formatMsg("<keystore>"))
|
return err(exc.formatMsg("<keystore>"))
|
||||||
|
|
||||||
|
proc generateKeystoreSalt*(rng: var HmacDrbgContext): seq[byte] =
|
||||||
|
rng.generateBytes(keyLen)
|
||||||
|
|
||||||
proc createCryptoField(kdfKind: KdfKind,
|
proc createCryptoField(kdfKind: KdfKind,
|
||||||
rng: var HmacDrbgContext,
|
rng: var HmacDrbgContext,
|
||||||
secret: openArray[byte],
|
secret: openArray[byte],
|
||||||
|
|
|
@ -154,10 +154,12 @@ type
|
||||||
syncCommitteeService*: SyncCommitteeServiceRef
|
syncCommitteeService*: SyncCommitteeServiceRef
|
||||||
doppelgangerService*: DoppelgangerServiceRef
|
doppelgangerService*: DoppelgangerServiceRef
|
||||||
runSlotLoopFut*: Future[void]
|
runSlotLoopFut*: Future[void]
|
||||||
|
runKeystoreCachePruningLoopFut*: Future[void]
|
||||||
sigintHandleFut*: Future[void]
|
sigintHandleFut*: Future[void]
|
||||||
sigtermHandleFut*: Future[void]
|
sigtermHandleFut*: Future[void]
|
||||||
keymanagerHost*: ref KeymanagerHost
|
keymanagerHost*: ref KeymanagerHost
|
||||||
keymanagerServer*: RestServerRef
|
keymanagerServer*: RestServerRef
|
||||||
|
keystoreCache*: KeystoreCacheRef
|
||||||
beaconClock*: BeaconClock
|
beaconClock*: BeaconClock
|
||||||
attachedValidators*: ref ValidatorPool
|
attachedValidators*: ref ValidatorPool
|
||||||
forks*: seq[Fork]
|
forks*: seq[Fork]
|
||||||
|
|
|
@ -381,13 +381,13 @@ proc loadSecretFile*(path: string): KsResult[KeystorePass] {.
|
||||||
ok(KeystorePass.init(res.get()))
|
ok(KeystorePass.init(res.get()))
|
||||||
|
|
||||||
proc loadRemoteKeystoreImpl(validatorsDir,
|
proc loadRemoteKeystoreImpl(validatorsDir,
|
||||||
keyName: string): Option[KeystoreData] =
|
keyName: string): Opt[KeystoreData] =
|
||||||
let keystorePath = validatorsDir / keyName / RemoteKeystoreFileName
|
let keystorePath = validatorsDir / keyName / RemoteKeystoreFileName
|
||||||
|
|
||||||
if not(checkSensitiveFilePermissions(keystorePath)):
|
if not(checkSensitiveFilePermissions(keystorePath)):
|
||||||
error "Remote keystorage file has insecure permissions",
|
error "Remote keystorage file has insecure permissions",
|
||||||
key_path = keystorePath
|
key_path = keystorePath
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
|
|
||||||
let handle =
|
let handle =
|
||||||
block:
|
block:
|
||||||
|
@ -395,7 +395,7 @@ proc loadRemoteKeystoreImpl(validatorsDir,
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
error "Unable to lock keystore file", key_path = keystorePath,
|
error "Unable to lock keystore file", key_path = keystorePath,
|
||||||
error_msg = ioErrorMsg(res.error())
|
error_msg = ioErrorMsg(res.error())
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
res.get()
|
res.get()
|
||||||
|
|
||||||
var success = false
|
var success = false
|
||||||
|
@ -409,7 +409,7 @@ proc loadRemoteKeystoreImpl(validatorsDir,
|
||||||
if gres.isErr():
|
if gres.isErr():
|
||||||
error "Could not read remote keystore file", key_path = keystorePath,
|
error "Could not read remote keystore file", key_path = keystorePath,
|
||||||
error_msg = ioErrorMsg(gres.error())
|
error_msg = ioErrorMsg(gres.error())
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
let buffer = gres.get()
|
let buffer = gres.get()
|
||||||
let data =
|
let data =
|
||||||
try:
|
try:
|
||||||
|
@ -417,19 +417,20 @@ proc loadRemoteKeystoreImpl(validatorsDir,
|
||||||
except SerializationError as e:
|
except SerializationError as e:
|
||||||
error "Invalid remote keystore file", key_path = keystorePath,
|
error "Invalid remote keystore file", key_path = keystorePath,
|
||||||
error_msg = e.formatMsg(keystorePath)
|
error_msg = e.formatMsg(keystorePath)
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
let kres = KeystoreData.init(data, handle)
|
let kres = KeystoreData.init(data, handle)
|
||||||
if kres.isErr():
|
if kres.isErr():
|
||||||
error "Invalid remote keystore file", key_path = keystorePath,
|
error "Invalid remote keystore file", key_path = keystorePath,
|
||||||
error_msg = kres.error()
|
error_msg = kres.error()
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
kres.get()
|
kres.get()
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
some(keystore)
|
Opt.some(keystore)
|
||||||
|
|
||||||
proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
nonInteractive: bool): Option[KeystoreData] =
|
nonInteractive: bool,
|
||||||
|
cache: KeystoreCacheRef): Opt[KeystoreData] =
|
||||||
let
|
let
|
||||||
keystorePath = validatorsDir / keyName / KeystoreFileName
|
keystorePath = validatorsDir / keyName / KeystoreFileName
|
||||||
passphrasePath = secretsDir / keyName
|
passphrasePath = secretsDir / keyName
|
||||||
|
@ -439,7 +440,7 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
error "Unable to lock keystore file", key_path = keystorePath,
|
error "Unable to lock keystore file", key_path = keystorePath,
|
||||||
error_msg = ioErrorMsg(res.error())
|
error_msg = ioErrorMsg(res.error())
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
res.get()
|
res.get()
|
||||||
|
|
||||||
var success = false
|
var success = false
|
||||||
|
@ -454,7 +455,7 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
if gres.isErr():
|
if gres.isErr():
|
||||||
error "Could not read local keystore file", key_path = keystorePath,
|
error "Could not read local keystore file", key_path = keystorePath,
|
||||||
error_msg = ioErrorMsg(gres.error())
|
error_msg = ioErrorMsg(gres.error())
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
let buffer = gres.get()
|
let buffer = gres.get()
|
||||||
let data =
|
let data =
|
||||||
try:
|
try:
|
||||||
|
@ -462,13 +463,13 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
except SerializationError as e:
|
except SerializationError as e:
|
||||||
error "Invalid local keystore file", key_path = keystorePath,
|
error "Invalid local keystore file", key_path = keystorePath,
|
||||||
error_msg = e.formatMsg(keystorePath)
|
error_msg = e.formatMsg(keystorePath)
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
data
|
data
|
||||||
|
|
||||||
if fileExists(passphrasePath):
|
if fileExists(passphrasePath):
|
||||||
if not(checkSensitiveFilePermissions(passphrasePath)):
|
if not(checkSensitiveFilePermissions(passphrasePath)):
|
||||||
error "Password file has insecure permissions", key_path = keystorePath
|
error "Password file has insecure permissions", key_path = keystorePath
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
|
|
||||||
let passphrase =
|
let passphrase =
|
||||||
block:
|
block:
|
||||||
|
@ -476,29 +477,30 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
error "Failed to read passphrase file", error_msg = res.error(),
|
error "Failed to read passphrase file", error_msg = res.error(),
|
||||||
path = passphrasePath
|
path = passphrasePath
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
res.get()
|
res.get()
|
||||||
|
|
||||||
let res = decryptKeystore(keystore, passphrase)
|
let res = decryptKeystore(keystore, passphrase, cache)
|
||||||
if res.isOk():
|
if res.isOk():
|
||||||
success = true
|
success = true
|
||||||
return some(KeystoreData.init(res.get(), keystore, handle))
|
return Opt.some(KeystoreData.init(res.get(), keystore, handle))
|
||||||
else:
|
else:
|
||||||
error "Failed to decrypt keystore", key_path = keystorePath,
|
error "Failed to decrypt keystore", key_path = keystorePath,
|
||||||
secure_path = passphrasePath
|
secure_path = passphrasePath
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
|
|
||||||
if nonInteractive:
|
if nonInteractive:
|
||||||
error "Unable to load validator key store. Please ensure matching " &
|
error "Unable to load validator key store. Please ensure matching " &
|
||||||
"passphrase exists in the secrets dir", key_path = keystorePath,
|
"passphrase exists in the secrets dir", key_path = keystorePath,
|
||||||
key_name = keyName, validatorsDir, secretsDir = secretsDir
|
key_name = keyName, validatorsDir, secretsDir = secretsDir
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
|
|
||||||
let prompt = "Please enter passphrase for key \"" &
|
let prompt = "Please enter passphrase for key \"" &
|
||||||
(validatorsDir / keyName) & "\": "
|
(validatorsDir / keyName) & "\": "
|
||||||
let res = keyboardGetPassword[ValidatorPrivKey](prompt, 3,
|
let res = keyboardGetPassword[ValidatorPrivKey](prompt, 3,
|
||||||
proc (password: string): KsResult[ValidatorPrivKey] =
|
proc (password: string): KsResult[ValidatorPrivKey] =
|
||||||
let decrypted = decryptKeystore(keystore, KeystorePass.init password)
|
let decrypted = decryptKeystore(keystore, KeystorePass.init password,
|
||||||
|
cache)
|
||||||
if decrypted.isErr():
|
if decrypted.isErr():
|
||||||
error "Keystore decryption failed. Please try again",
|
error "Keystore decryption failed. Please try again",
|
||||||
keystore_path = keystorePath
|
keystore_path = keystorePath
|
||||||
|
@ -506,25 +508,27 @@ proc loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName: string,
|
||||||
)
|
)
|
||||||
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return
|
return Opt.none(KeystoreData)
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
some(KeystoreData.init(res.get(), keystore, handle))
|
Opt.some(KeystoreData.init(res.get(), keystore, handle))
|
||||||
|
|
||||||
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
|
||||||
nonInteractive: bool): Option[KeystoreData] =
|
nonInteractive: bool,
|
||||||
|
cache: KeystoreCacheRef): Opt[KeystoreData] =
|
||||||
let
|
let
|
||||||
keystorePath = validatorsDir / keyName
|
keystorePath = validatorsDir / keyName
|
||||||
localKeystorePath = keystorePath / KeystoreFileName
|
localKeystorePath = keystorePath / KeystoreFileName
|
||||||
remoteKeystorePath = keystorePath / RemoteKeystoreFileName
|
remoteKeystorePath = keystorePath / RemoteKeystoreFileName
|
||||||
|
|
||||||
if fileExists(localKeystorePath):
|
if fileExists(localKeystorePath):
|
||||||
loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName, nonInteractive)
|
loadLocalKeystoreImpl(validatorsDir, secretsDir, keyName, nonInteractive,
|
||||||
|
cache)
|
||||||
elif fileExists(remoteKeystorePath):
|
elif fileExists(remoteKeystorePath):
|
||||||
loadRemoteKeystoreImpl(validatorsDir, keyName)
|
loadRemoteKeystoreImpl(validatorsDir, keyName)
|
||||||
else:
|
else:
|
||||||
error "Unable to find any keystore files", keystorePath
|
error "Unable to find any keystore files", keystorePath
|
||||||
none[KeystoreData]()
|
Opt.none(KeystoreData)
|
||||||
|
|
||||||
proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
|
proc removeValidatorFiles*(validatorsDir, secretsDir, keyName: string,
|
||||||
kind: KeystoreKind
|
kind: KeystoreKind
|
||||||
|
@ -662,7 +666,8 @@ iterator listLoadableKeys*(validatorsDir, secretsDir: string,
|
||||||
|
|
||||||
iterator listLoadableKeystores*(validatorsDir, secretsDir: string,
|
iterator listLoadableKeystores*(validatorsDir, secretsDir: string,
|
||||||
nonInteractive: bool,
|
nonInteractive: bool,
|
||||||
keysMask: set[KeystoreKind]): KeystoreData =
|
keysMask: set[KeystoreKind],
|
||||||
|
cache: KeystoreCacheRef): KeystoreData =
|
||||||
try:
|
try:
|
||||||
for kind, file in walkDir(validatorsDir):
|
for kind, file in walkDir(validatorsDir):
|
||||||
if kind == pcDir:
|
if kind == pcDir:
|
||||||
|
@ -683,23 +688,24 @@ iterator listLoadableKeystores*(validatorsDir, secretsDir: string,
|
||||||
let
|
let
|
||||||
secretFile = secretsDir / keyName
|
secretFile = secretsDir / keyName
|
||||||
keystore = loadKeystore(validatorsDir, secretsDir, keyName,
|
keystore = loadKeystore(validatorsDir, secretsDir, keyName,
|
||||||
nonInteractive)
|
nonInteractive, cache).valueOr:
|
||||||
if keystore.isSome():
|
|
||||||
yield keystore.get()
|
|
||||||
else:
|
|
||||||
fatal "Unable to load keystore", keystore = file
|
fatal "Unable to load keystore", keystore = file
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
|
yield keystore
|
||||||
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
error "Validator keystores directory not accessible",
|
error "Validator keystores directory not accessible",
|
||||||
path = validatorsDir, err = err.msg
|
path = validatorsDir, err = err.msg
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
iterator listLoadableKeystores*(config: AnyConf): KeystoreData =
|
iterator listLoadableKeystores*(config: AnyConf,
|
||||||
|
cache: KeystoreCacheRef): KeystoreData =
|
||||||
for el in listLoadableKeystores(config.validatorsDir(),
|
for el in listLoadableKeystores(config.validatorsDir(),
|
||||||
config.secretsDir(),
|
config.secretsDir(),
|
||||||
config.nonInteractive,
|
config.nonInteractive,
|
||||||
{KeystoreKind.Local, KeystoreKind.Remote}):
|
{KeystoreKind.Local, KeystoreKind.Remote},
|
||||||
|
cache):
|
||||||
yield el
|
yield el
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -1031,6 +1037,7 @@ proc saveKeystore*(
|
||||||
signingPubKey: CookedPubKey,
|
signingPubKey: CookedPubKey,
|
||||||
signingKeyPath: KeyPath,
|
signingKeyPath: KeyPath,
|
||||||
password: string,
|
password: string,
|
||||||
|
salt: openArray[byte] = @[],
|
||||||
mode = Secure
|
mode = Secure
|
||||||
): Result[void, KeystoreGenerationError] {.raises: [Defect].} =
|
): Result[void, KeystoreGenerationError] {.raises: [Defect].} =
|
||||||
let
|
let
|
||||||
|
@ -1048,7 +1055,7 @@ proc saveKeystore*(
|
||||||
|
|
||||||
let keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
|
let keyStore = createKeystore(kdfPbkdf2, rng, signingKey,
|
||||||
keypass, signingKeyPath,
|
keypass, signingKeyPath,
|
||||||
mode = mode)
|
mode = mode, salt = salt)
|
||||||
|
|
||||||
let encodedStorage =
|
let encodedStorage =
|
||||||
try:
|
try:
|
||||||
|
@ -1276,7 +1283,7 @@ proc importKeystore*(pool: var ValidatorPool,
|
||||||
|
|
||||||
ok(KeystoreData.init(privateKey, keystore, res.get()))
|
ok(KeystoreData.init(privateKey, keystore, res.get()))
|
||||||
|
|
||||||
proc generateDistirbutedStore*(rng: var HmacDrbgContext,
|
proc generateDistributedStore*(rng: var HmacDrbgContext,
|
||||||
shares: seq[SecretShare],
|
shares: seq[SecretShare],
|
||||||
pubKey: ValidatorPubKey,
|
pubKey: ValidatorPubKey,
|
||||||
validatorIdx: Natural,
|
validatorIdx: Natural,
|
||||||
|
@ -1296,7 +1303,7 @@ proc generateDistirbutedStore*(rng: var HmacDrbgContext,
|
||||||
shareSecretsDir / $share.id,
|
shareSecretsDir / $share.id,
|
||||||
share.key, share.key.toPubKey,
|
share.key, share.key.toPubKey,
|
||||||
makeKeyPath(validatorIdx, signingKeyKind),
|
makeKeyPath(validatorIdx, signingKeyKind),
|
||||||
password.str,
|
password.str, @[],
|
||||||
mode)
|
mode)
|
||||||
|
|
||||||
signers.add RemoteSignerInfo(
|
signers.add RemoteSignerInfo(
|
||||||
|
@ -1402,6 +1409,14 @@ proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
defer: burnMem(baseKey)
|
defer: burnMem(baseKey)
|
||||||
baseKey = deriveChildKey(baseKey, baseKeyPath)
|
baseKey = deriveChildKey(baseKey, baseKeyPath)
|
||||||
|
|
||||||
|
var
|
||||||
|
salt = rng.generateKeystoreSalt()
|
||||||
|
password = KeystorePass.init ncrutils.toHex(rng.generateBytes(32))
|
||||||
|
|
||||||
|
defer:
|
||||||
|
burnMem(salt)
|
||||||
|
burnMem(password)
|
||||||
|
|
||||||
let localValidatorsCount = totalNewValidators - int(remoteValidatorsCount)
|
let localValidatorsCount = totalNewValidators - int(remoteValidatorsCount)
|
||||||
for i in 0 ..< localValidatorsCount:
|
for i in 0 ..< localValidatorsCount:
|
||||||
let validatorIdx = firstValidatorIdx + i
|
let validatorIdx = firstValidatorIdx + i
|
||||||
|
@ -1416,12 +1431,10 @@ proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
derivedKey = deriveChildKey(derivedKey, 0) # This is the signing key
|
derivedKey = deriveChildKey(derivedKey, 0) # This is the signing key
|
||||||
let signingPubKey = derivedKey.toPubKey
|
let signingPubKey = derivedKey.toPubKey
|
||||||
|
|
||||||
var password = KeystorePass.init ncrutils.toHex(rng.generateBytes(32))
|
|
||||||
defer: burnMem(password)
|
|
||||||
? saveKeystore(rng, validatorsDir, secretsDir,
|
? saveKeystore(rng, validatorsDir, secretsDir,
|
||||||
derivedKey, signingPubKey,
|
derivedKey, signingPubKey,
|
||||||
makeKeyPath(validatorIdx, signingKeyKind), password.str,
|
makeKeyPath(validatorIdx, signingKeyKind), password.str,
|
||||||
mode)
|
salt, mode)
|
||||||
|
|
||||||
deposits.add prepareDeposit(
|
deposits.add prepareDeposit(
|
||||||
cfg, withdrawalPubKey, derivedKey, signingPubKey)
|
cfg, withdrawalPubKey, derivedKey, signingPubKey)
|
||||||
|
@ -1446,7 +1459,7 @@ proc generateDeposits*(cfg: RuntimeConfig,
|
||||||
error "Failed to generate distributed key: ", threshold, sharesCount
|
error "Failed to generate distributed key: ", threshold, sharesCount
|
||||||
continue
|
continue
|
||||||
|
|
||||||
? generateDistirbutedStore(rng,
|
? generateDistributedStore(rng,
|
||||||
shares.get,
|
shares.get,
|
||||||
signingPubKey.toPubKey,
|
signingPubKey.toPubKey,
|
||||||
validatorIdx,
|
validatorIdx,
|
||||||
|
@ -1517,11 +1530,24 @@ proc resetAttributesNoError() =
|
||||||
try: stdout.resetAttributes()
|
try: stdout.resetAttributes()
|
||||||
except IOError: discard
|
except IOError: discard
|
||||||
|
|
||||||
proc importKeystoresFromDir*(rng: var HmacDrbgContext,
|
proc importKeystoresFromDir*(rng: var HmacDrbgContext, meth: ImportMethod,
|
||||||
importedDir, validatorsDir, secretsDir: string) =
|
importedDir, validatorsDir, secretsDir: string) =
|
||||||
var password: string # TODO consider using a SecretString type
|
var password: string # TODO consider using a SecretString type
|
||||||
defer: burnMem(password)
|
defer: burnMem(password)
|
||||||
|
|
||||||
|
var (singleSaltPassword, singleSaltSalt) =
|
||||||
|
case meth
|
||||||
|
of ImportMethod.Normal:
|
||||||
|
var defaultSeq: seq[byte]
|
||||||
|
(KeystorePass.init(""), defaultSeq)
|
||||||
|
of ImportMethod.SingleSalt:
|
||||||
|
(KeystorePass.init(ncrutils.toHex(rng.generateBytes(32))),
|
||||||
|
rng.generateBytes(32))
|
||||||
|
|
||||||
|
defer:
|
||||||
|
burnMem(singleSaltPassword)
|
||||||
|
burnMem(singleSaltSalt)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for file in walkDirRec(importedDir):
|
for file in walkDirRec(importedDir):
|
||||||
let filenameParts = splitFile(file)
|
let filenameParts = splitFile(file)
|
||||||
|
@ -1559,12 +1585,23 @@ proc importKeystoresFromDir*(rng: var HmacDrbgContext,
|
||||||
let privKey = ValidatorPrivKey.fromRaw(secret)
|
let privKey = ValidatorPrivKey.fromRaw(secret)
|
||||||
if privKey.isOk:
|
if privKey.isOk:
|
||||||
let pubkey = privKey.value.toPubKey
|
let pubkey = privKey.value.toPubKey
|
||||||
var
|
var (password, salt) =
|
||||||
password = KeystorePass.init ncrutils.toHex(rng.generateBytes(32))
|
case meth
|
||||||
defer: burnMem(password)
|
of ImportMethod.Normal:
|
||||||
|
var defaultSeq: seq[byte]
|
||||||
|
(KeystorePass.init ncrutils.toHex(rng.generateBytes(32)),
|
||||||
|
defaultSeq)
|
||||||
|
of ImportMethod.SingleSalt:
|
||||||
|
(singleSaltPassword, singleSaltSalt)
|
||||||
|
|
||||||
|
defer:
|
||||||
|
burnMem(password)
|
||||||
|
burnMem(salt)
|
||||||
|
|
||||||
let status = saveKeystore(rng, validatorsDir, secretsDir,
|
let status = saveKeystore(rng, validatorsDir, secretsDir,
|
||||||
privKey.value, pubkey,
|
privKey.value, pubkey,
|
||||||
keystore.path, password.str)
|
keystore.path, password.str,
|
||||||
|
salt)
|
||||||
if status.isOk:
|
if status.isOk:
|
||||||
notice "Keystore imported", file
|
notice "Keystore imported", file
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -105,10 +105,11 @@ proc getValidator*(validators: auto,
|
||||||
validator: validators[idx])
|
validator: validators[idx])
|
||||||
|
|
||||||
proc addValidators*(node: BeaconNode) =
|
proc addValidators*(node: BeaconNode) =
|
||||||
info "Loading validators", validatorsDir = node.config.validatorsDir()
|
info "Loading validators", validatorsDir = node.config.validatorsDir(),
|
||||||
|
keystore_cache_available = not(isNil(node.keystoreCache))
|
||||||
let
|
let
|
||||||
epoch = node.currentSlot().epoch
|
epoch = node.currentSlot().epoch
|
||||||
for keystore in listLoadableKeystores(node.config):
|
for keystore in listLoadableKeystores(node.config, node.keystoreCache):
|
||||||
let
|
let
|
||||||
data = withState(node.dag.headState):
|
data = withState(node.dag.headState):
|
||||||
getValidator(forkyState.data.validators.asSeq(), keystore.pubkey)
|
getValidator(forkyState.data.validators.asSeq(), keystore.pubkey)
|
||||||
|
|
|
@ -67,7 +67,7 @@ proc main =
|
||||||
secretsDir = conf.secretsDir
|
secretsDir = conf.secretsDir
|
||||||
keystore = loadKeystore(validatorsDir,
|
keystore = loadKeystore(validatorsDir,
|
||||||
secretsDir,
|
secretsDir,
|
||||||
conf.key, true).valueOr:
|
conf.key, true, nil).valueOr:
|
||||||
error "Can't load keystore", validatorsDir, secretsDir, pubkey = conf.key
|
error "Can't load keystore", validatorsDir, secretsDir, pubkey = conf.key
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ proc main =
|
||||||
|
|
||||||
let
|
let
|
||||||
outSharesDir = conf.outDir / "shares"
|
outSharesDir = conf.outDir / "shares"
|
||||||
status = generateDistirbutedStore(
|
status = generateDistributedStore(
|
||||||
rngCtx,
|
rngCtx,
|
||||||
shares,
|
shares,
|
||||||
signingPubKey,
|
signingPubKey,
|
||||||
|
|
|
@ -57,7 +57,7 @@ cli do(validatorsDir: string, secretsDir: string,
|
||||||
validatorKeys: Table[ValidatorPubKey, ValidatorPrivKey]
|
validatorKeys: Table[ValidatorPubKey, ValidatorPrivKey]
|
||||||
|
|
||||||
for item in listLoadableKeystores(validatorsDir, secretsDir, true,
|
for item in listLoadableKeystores(validatorsDir, secretsDir, true,
|
||||||
{KeystoreKind.Local}):
|
{KeystoreKind.Local}, nil):
|
||||||
let
|
let
|
||||||
pubkey = item.privateKey.toPubKey().toPubKey()
|
pubkey = item.privateKey.toPubKey().toPubKey()
|
||||||
idx = findValidator(getStateField(state[], validators).toSeq, pubkey)
|
idx = findValidator(getStateField(state[], validators).toSeq, pubkey)
|
||||||
|
|
Loading…
Reference in New Issue