Add permissions checks and handling to wallets and bls keystores.
This commit is contained in:
parent
fc09c2b3d4
commit
86139839f1
|
@ -1095,6 +1095,8 @@ programMain:
|
||||||
# This is ref so we can mutate it (to erase it) after the initial loading.
|
# This is ref so we can mutate it (to erase it) after the initial loading.
|
||||||
stateSnapshotContents: ref string
|
stateSnapshotContents: ref string
|
||||||
|
|
||||||
|
setupStdoutLogging(config.logLevel)
|
||||||
|
|
||||||
if not(checkAndCreateDataDir(string(config.dataDir))):
|
if not(checkAndCreateDataDir(string(config.dataDir))):
|
||||||
# We are unable to access/create data folder or data folder's
|
# We are unable to access/create data folder or data folder's
|
||||||
# permissions are insecure.
|
# permissions are insecure.
|
||||||
|
@ -1321,12 +1323,16 @@ programMain:
|
||||||
of WalletsCmd.list:
|
of WalletsCmd.list:
|
||||||
for kind, walletFile in walkDir(config.walletsDir):
|
for kind, walletFile in walkDir(config.walletsDir):
|
||||||
if kind != pcFile: continue
|
if kind != pcFile: continue
|
||||||
let walletRes = loadWallet(walletFile)
|
if checkFilePermissions(walletFile):
|
||||||
if walletRes.isOk:
|
let walletRes = loadWallet(walletFile)
|
||||||
echo walletRes.get.longName
|
if walletRes.isOk:
|
||||||
|
echo walletRes.get.longName
|
||||||
|
else:
|
||||||
|
warn "Found corrupt wallet file",
|
||||||
|
wallet = walletFile, error = walletRes.error
|
||||||
else:
|
else:
|
||||||
warn "Found corrupt wallet file",
|
warn "Found wallet file with insecure permissions",
|
||||||
wallet = walletFile, error = walletRes.error
|
wallet = walletFile
|
||||||
|
|
||||||
of WalletsCmd.restore:
|
of WalletsCmd.restore:
|
||||||
restoreWalletInteractively(rng[], config)
|
restoreWalletInteractively(rng[], config)
|
||||||
|
|
|
@ -41,8 +41,8 @@ proc checkAndCreateDataDir*(dataDir: string): bool =
|
||||||
## If folder exists, procedure will check it for access and
|
## If folder exists, procedure will check it for access and
|
||||||
## permissions `0750 (rwxr-x---)`, if folder do not exists it will be created
|
## permissions `0750 (rwxr-x---)`, if folder do not exists it will be created
|
||||||
## with permissions `0750 (rwxr-x---)`.
|
## with permissions `0750 (rwxr-x---)`.
|
||||||
|
let amask = {AccessFlags.Read, AccessFlags.Write, AccessFlags.Execute}
|
||||||
when defined(posix):
|
when defined(posix):
|
||||||
let amask = {AccessFlags.Read, AccessFlags.Write, AccessFlags.Execute}
|
|
||||||
if fileAccessible(dataDir, amask):
|
if fileAccessible(dataDir, amask):
|
||||||
let gmask = {UserRead, UserWrite, UserExec, GroupRead, GroupExec}
|
let gmask = {UserRead, UserWrite, UserExec, GroupRead, GroupExec}
|
||||||
let pmask = {OtherRead, OtherWrite, OtherExec, GroupWrite}
|
let pmask = {OtherRead, OtherWrite, OtherExec, GroupWrite}
|
||||||
|
@ -51,32 +51,71 @@ proc checkAndCreateDataDir*(dataDir: string): bool =
|
||||||
fatal "Could not check data folder permissions",
|
fatal "Could not check data folder permissions",
|
||||||
data_dir = dataDir, errorCode = $pres.error,
|
data_dir = dataDir, errorCode = $pres.error,
|
||||||
errorMsg = ioErrorMsg(pres.error)
|
errorMsg = ioErrorMsg(pres.error)
|
||||||
return false
|
false
|
||||||
let insecurePermissions = pres.get() * pmask
|
else:
|
||||||
if insecurePermissions != {}:
|
let insecurePermissions = pres.get() * pmask
|
||||||
fatal "Data folder has insecure permissions",
|
if insecurePermissions != {}:
|
||||||
data_dir = dataDir,
|
fatal "Data folder has insecure permissions",
|
||||||
insecure_permissions = $insecurePermissions,
|
data_dir = dataDir,
|
||||||
current_permissions = pres.get().toString(),
|
insecure_permissions = $insecurePermissions,
|
||||||
required_permissions = gmask.toString()
|
current_permissions = pres.get().toString(),
|
||||||
return false
|
required_permissions = gmask.toString()
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
true
|
||||||
else:
|
else:
|
||||||
let res = createPath(dataDir, 0o750)
|
let res = createPath(dataDir, 0o750)
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
fatal "Could not create data folder", data_dir = dataDir,
|
fatal "Could not create data folder", data_dir = dataDir,
|
||||||
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
||||||
return false
|
false
|
||||||
return true
|
else:
|
||||||
|
true
|
||||||
elif defined(windows):
|
elif defined(windows):
|
||||||
let res = createPath(dataDir, 0o750)
|
if fileAccessible(dataDir, amask):
|
||||||
if res.isErr():
|
let res = createPath(dataDir, 0o750)
|
||||||
fatal "Could not create data folder", data_dir = dataDir,
|
if res.isErr():
|
||||||
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
fatal "Could not create data folder", data_dir = dataDir,
|
||||||
return false
|
errorMsg = ioErrorMsg(res.error), errorCode = $res.error
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
true
|
||||||
else:
|
else:
|
||||||
fatal "Unsupported operation system"
|
fatal "Unsupported operation system"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc checkFilePermissions*(filePath: string): bool =
|
||||||
|
## Check if ``filePath`` has only "(600) rw-------" permissions.
|
||||||
|
## Procedure returns ``false`` if permissions are different
|
||||||
|
when defined(windows):
|
||||||
|
# Windows do not support per-user/group/other permissions,
|
||||||
|
# skiping verification part.
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
let allowedMask = {UserRead, UserWrite}
|
||||||
|
let mask = {UserExec,
|
||||||
|
GroupRead, GroupWrite, GroupExec,
|
||||||
|
OtherRead, OtherWrite, OtherExec}
|
||||||
|
let pres = getPermissionsSet(filePath)
|
||||||
|
if pres.isErr():
|
||||||
|
error "Could not check file permissions",
|
||||||
|
key_path = filePath, errorCode = $pres.error,
|
||||||
|
errorMsg = ioErrorMsg(pres.error)
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
let insecurePermissions = pres.get() * mask
|
||||||
|
if insecurePermissions != {}:
|
||||||
|
error "File has insecure permissions",
|
||||||
|
key_path = filePath,
|
||||||
|
insecure_permissions = $insecurePermissions,
|
||||||
|
current_permissions = pres.get().toString(),
|
||||||
|
required_permissions = allowedMask.toString()
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
true
|
||||||
|
|
||||||
proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
||||||
nonInteractive: bool): Option[ValidatorPrivKey] =
|
nonInteractive: bool): Option[ValidatorPrivKey] =
|
||||||
let
|
let
|
||||||
|
@ -92,12 +131,17 @@ proc loadKeystore(validatorsDir, secretsDir, keyName: string,
|
||||||
|
|
||||||
let passphrasePath = secretsDir / keyName
|
let passphrasePath = secretsDir / keyName
|
||||||
if fileExists(passphrasePath):
|
if fileExists(passphrasePath):
|
||||||
let
|
if not(checkFilePermissions(passphrasePath)):
|
||||||
passphrase = KeystorePass:
|
error "Password file has insecure permissions", key_path = keyStorePath
|
||||||
try: readFile(passphrasePath)
|
return
|
||||||
except IOError as err:
|
|
||||||
error "Failed to read passphrase file", err = err.msg, path = passphrasePath
|
let passphrase = KeystorePass:
|
||||||
return
|
try:
|
||||||
|
readFile(passphrasePath)
|
||||||
|
except IOError as err:
|
||||||
|
error "Failed to read passphrase file", err = err.msg,
|
||||||
|
path = passphrasePath
|
||||||
|
return
|
||||||
|
|
||||||
let res = decryptKeystore(keystore, passphrase)
|
let res = decryptKeystore(keystore, passphrase)
|
||||||
if res.isOk:
|
if res.isOk:
|
||||||
|
@ -174,29 +218,11 @@ type
|
||||||
|
|
||||||
proc loadNetKeystore*(keyStorePath: string,
|
proc loadNetKeystore*(keyStorePath: string,
|
||||||
insecurePwd: Option[string]): Option[lcrypto.PrivateKey] =
|
insecurePwd: Option[string]): Option[lcrypto.PrivateKey] =
|
||||||
when defined(windows):
|
|
||||||
# Windows do not support per-user permissions, skiping verification part.
|
|
||||||
discard
|
|
||||||
else:
|
|
||||||
let allowedMask = {UserRead, UserWrite}
|
|
||||||
let mask = {UserExec,
|
|
||||||
GroupRead, GroupWrite, GroupExec,
|
|
||||||
OtherRead, OtherWrite, OtherExec}
|
|
||||||
let pres = getPermissionsSet(keyStorePath)
|
|
||||||
if pres.isErr():
|
|
||||||
error "Could not check key file permissions",
|
|
||||||
key_path = keyStorePath, errorCode = $pres.error,
|
|
||||||
errorMsg = ioErrorMsg(pres.error)
|
|
||||||
return
|
|
||||||
|
|
||||||
let insecurePermissions = pres.get() * mask
|
if not(checkFilePermissions(keystorePath)):
|
||||||
if insecurePermissions != {}:
|
error "Network keystorage file has insecure permissions",
|
||||||
error "Network key file has insecure permissions",
|
key_path = keyStorePath
|
||||||
key_path = keyStorePath,
|
return
|
||||||
insecure_permissions = $insecurePermissions,
|
|
||||||
current_permissions = pres.get().toString(),
|
|
||||||
required_permissions = allowedMask.toString()
|
|
||||||
return
|
|
||||||
|
|
||||||
let keyStore =
|
let keyStore =
|
||||||
try:
|
try:
|
||||||
|
@ -313,18 +339,28 @@ proc saveKeystore(rng: var BrHmacDrbgContext,
|
||||||
password, signingKeyPath)
|
password, signingKeyPath)
|
||||||
keystoreFile = validatorDir / keystoreFileName
|
keystoreFile = validatorDir / keystoreFileName
|
||||||
|
|
||||||
try: createDir validatorDir
|
var encodedStorage: string
|
||||||
except OSError, IOError: return err FailedToCreateValidatorDir
|
try:
|
||||||
|
encodedStorage = Json.encode(keyStore)
|
||||||
|
except SerializationError:
|
||||||
|
error "Could not serialize keystorage", key_path = keystoreFile
|
||||||
|
return err(FailedToCreateKeystoreFile)
|
||||||
|
|
||||||
try: createDir secretsDir
|
let vres = createPath(validatorDir, 0o750)
|
||||||
except OSError, IOError: return err FailedToCreateSecretsDir
|
if vres.isErr():
|
||||||
|
return err(FailedToCreateValidatorDir)
|
||||||
|
|
||||||
try: writeFile(secretsDir / keyName, password.string)
|
let sres = createPath(secretsDir, 0o750)
|
||||||
except IOError: return err FailedToCreateSecretFile
|
if sres.isErr():
|
||||||
|
return err(FailedToCreateSecretsDir)
|
||||||
|
|
||||||
try: Json.saveFile(keystoreFile, keyStore)
|
let swres = writeFile(secretsDir / keyName, string(password), 0o600)
|
||||||
except IOError, SerializationError:
|
if swres.isErr():
|
||||||
return err FailedToCreateKeystoreFile
|
return err(FailedToCreateSecretFile)
|
||||||
|
|
||||||
|
let kwres = writeFile(keystoreFile, encodedStorage, 0o600)
|
||||||
|
if kwres.isErr():
|
||||||
|
return err(FailedToCreateKeystoreFile)
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
|
@ -359,19 +395,18 @@ proc generateDeposits*(preset: RuntimePreset,
|
||||||
ok deposits
|
ok deposits
|
||||||
|
|
||||||
proc saveWallet*(wallet: Wallet, outWalletPath: string): Result[void, string] =
|
proc saveWallet*(wallet: Wallet, outWalletPath: string): Result[void, string] =
|
||||||
try: createDir splitFile(outWalletPath).dir
|
let walletDir = splitFile(outWalletPath).dir
|
||||||
except OSError, IOError:
|
var encodedWallet: string
|
||||||
let e = getCurrentException()
|
try:
|
||||||
return err("failure to create wallet directory: " & e.msg)
|
encodedWallet = Json.encode(wallet, pretty = true)
|
||||||
|
except SerializationError:
|
||||||
try: Json.saveFile(outWalletPath, wallet, pretty = true)
|
return err("Could not serialize wallet")
|
||||||
except IOError as e:
|
let pres = createPath(walletDir, 0o750)
|
||||||
return err("failure to write file: " & e.msg)
|
if pres.isErr():
|
||||||
except SerializationError as e:
|
return err("Could not create wallet directory [" & walletDir & "]")
|
||||||
# TODO: Saving a wallet should not produce SerializationErrors.
|
let wres = writeFile(outWalletPath, encodedWallet, 0o600)
|
||||||
# Investigate the source of this exception.
|
if wres.isErr():
|
||||||
return err("failure to serialize wallet: " & e.formatMsg("wallet"))
|
return err("Could not write wallet to file [" & outWalletPath & "]")
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
proc saveWallet*(wallet: WalletPathPair): Result[void, string] =
|
proc saveWallet*(wallet: WalletPathPair): Result[void, string] =
|
||||||
|
@ -424,14 +459,15 @@ proc importKeystoresFromDir*(rng: var BrHmacDrbgContext,
|
||||||
if toLowerAscii(ext) != ".json":
|
if toLowerAscii(ext) != ".json":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let keystore = try:
|
let keystore =
|
||||||
Json.loadFile(file, Keystore)
|
try:
|
||||||
except SerializationError as e:
|
Json.loadFile(file, Keystore)
|
||||||
warn "Invalid keystore", err = e.formatMsg(file)
|
except SerializationError as e:
|
||||||
continue
|
warn "Invalid keystore", err = e.formatMsg(file)
|
||||||
except IOError as e:
|
continue
|
||||||
warn "Failed to read keystore file", file, err = e.msg
|
except IOError as e:
|
||||||
continue
|
warn "Failed to read keystore file", file, err = e.msg
|
||||||
|
continue
|
||||||
|
|
||||||
var firstDecryptionAttempt = true
|
var firstDecryptionAttempt = true
|
||||||
|
|
||||||
|
@ -643,8 +679,10 @@ proc restoreWalletInteractively*(rng: var BrHmacDrbgContext,
|
||||||
proc loadWallet*(fileName: string): Result[Wallet, string] =
|
proc loadWallet*(fileName: string): Result[Wallet, string] =
|
||||||
try:
|
try:
|
||||||
ok Json.loadFile(fileName, Wallet)
|
ok Json.loadFile(fileName, Wallet)
|
||||||
except CatchableError as e:
|
except IOError as exc:
|
||||||
err e.msg
|
err exc.msg
|
||||||
|
except SerializationError as exc:
|
||||||
|
err exc.msg
|
||||||
|
|
||||||
proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
||||||
echo "Please enter the password for unlocking the wallet"
|
echo "Please enter the password for unlocking the wallet"
|
||||||
|
@ -666,7 +704,8 @@ proc unlockWalletInteractively*(wallet: Wallet): Result[Mnemonic, string] =
|
||||||
|
|
||||||
return err "failure to unlock wallet"
|
return err "failure to unlock wallet"
|
||||||
|
|
||||||
proc findWallet*(config: BeaconNodeConf, name: WalletName): Result[WalletPathPair, string] =
|
proc findWallet*(config: BeaconNodeConf,
|
||||||
|
name: WalletName): Result[WalletPathPair, string] =
|
||||||
var walletFiles = newSeq[string]()
|
var walletFiles = newSeq[string]()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,7 +18,7 @@ import
|
||||||
# Local modules
|
# Local modules
|
||||||
spec/[datatypes, crypto, helpers], eth2_network, time
|
spec/[datatypes, crypto, helpers], eth2_network, time
|
||||||
|
|
||||||
proc setupLogging*(logLevel: string, logFile: Option[OutFile]) =
|
proc setupStdoutLogging*(logLevel: string) =
|
||||||
when compiles(defaultChroniclesStream.output.writer):
|
when compiles(defaultChroniclesStream.output.writer):
|
||||||
defaultChroniclesStream.outputs[0].writer =
|
defaultChroniclesStream.outputs[0].writer =
|
||||||
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe, raises: [Defect].} =
|
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe, raises: [Defect].} =
|
||||||
|
@ -27,6 +27,7 @@ proc setupLogging*(logLevel: string, logFile: Option[OutFile]) =
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
logLoggingFailure(cstring(msg), err)
|
logLoggingFailure(cstring(msg), err)
|
||||||
|
|
||||||
|
proc setupLogging*(logLevel: string, logFile: Option[OutFile]) =
|
||||||
randomize()
|
randomize()
|
||||||
|
|
||||||
if logFile.isSome:
|
if logFile.isSome:
|
||||||
|
|
Loading…
Reference in New Issue