Add permissions checks and handling to wallets and bls keystores.

This commit is contained in:
cheatfate 2020-08-27 16:24:30 +03:00 committed by zah
parent fc09c2b3d4
commit 86139839f1
3 changed files with 130 additions and 84 deletions

View File

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

View File

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

View File

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