mirror of
https://github.com/codex-storage/constantine.git
synced 2025-01-11 19:44:10 +00:00
Expose OS-provided cryptographically secure RNG (#257)
* Expose OS-provided cryptographically secure RNG * small fixes * some more csprngs fixes
This commit is contained in:
parent
8b43b55345
commit
ad04e6ea57
@ -274,9 +274,13 @@ const buildParallel = "test_parallel.txt"
|
||||
# Basic primitives should stay on to catch compiler regressions.
|
||||
const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
|
||||
|
||||
# CSPRNG
|
||||
# ----------------------------------------------------------
|
||||
("tests/t_csprngs.nim", false),
|
||||
|
||||
# Hashing vs OpenSSL
|
||||
# ----------------------------------------------------------
|
||||
("tests/t_hash_sha256_vs_openssl.nim", true), # skip OpenSSL tests on Windows
|
||||
("tests/t_hash_sha256_vs_openssl.nim", false), # skip OpenSSL tests on Windows
|
||||
|
||||
# Ciphers
|
||||
# ----------------------------------------------------------
|
||||
|
142
constantine/csprngs/sysrand.nim
Normal file
142
constantine/csprngs/sysrand.nim
Normal file
@ -0,0 +1,142 @@
|
||||
# Constantine
|
||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
||||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Operating System provided
|
||||
# Cryptographically Secure Pseudo-Random Number Generator
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
# We use Nim effect system to track RNG subroutines
|
||||
type
|
||||
CSPRNG = object
|
||||
|
||||
when defined(windows):
|
||||
# There are several Windows CSPRNG APIs:
|
||||
# - CryptGenRandom
|
||||
# - RtlGenRandom
|
||||
# - BCryptGenRandom
|
||||
#
|
||||
# CryptGenRandom is Intel CPU only.
|
||||
# RtlGenRandom is deprecated, in particular it doesn't work for Windows UWP
|
||||
# (Universal Windows Platform, single source for PC, mobile, Xbox, ...)
|
||||
# It is the API used by Chromium, Firefox, libsodium, Rust, Go, ...
|
||||
# BCryptGenRandom is supposedly the recommended API,
|
||||
# however it has sandbox issues (it tries to read the user config in registry)
|
||||
# and random crashes when trying to force an algorithm to avoid reading user config.
|
||||
#
|
||||
# So we pick RtlGenRandom.
|
||||
#
|
||||
# - https://github.com/rust-random/getrandom/issues/65#issuecomment-753634074
|
||||
# - https://stackoverflow.com/questions/48875929/rtlgenrandom-cryptgenrandom-or-other-winapi-to-generate-cryptographically-secure
|
||||
# - https://github.com/rust-random/getrandom/issues/314
|
||||
# - https://learn.microsoft.com/en-us/archive/blogs/michael_howard/cryptographically-secure-random-number-on-windows-without-using-cryptoapi
|
||||
|
||||
proc RtlGenRandom(pbuffer: pointer, len: culong): bool {.importc: "SystemFunction036", stdcall, dynlib: "advapi32.dll", sideeffect, tags: [CSPRNG].}
|
||||
#https://learn.microsoft.com/en-us/archive/blogs/michael_howard/cryptographically-secure-random-number-on-windows-without-using-cryptoapi
|
||||
# https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
|
||||
#
|
||||
# BOOLEAN RtlGenRandom(
|
||||
# [out] PVOID RandomBuffer,
|
||||
# [in] ULONG RandomBufferLength
|
||||
# );
|
||||
#
|
||||
# https://learn.microsoft.com/en-US/windows/win32/winprog/windows-data-types
|
||||
# BOOLEAN (to not be confused with winapi BOOL)
|
||||
# is `typedef BYTE BOOLEAN;` and so has the same representation as Nim bools.
|
||||
|
||||
proc sysrand*[T](buffer: var T): bool {.inline.} =
|
||||
## Fills the buffer with cryptographically secure random data
|
||||
return RtlGenRandom(buffer.addr, culong sizeof(T))
|
||||
|
||||
elif defined(linux):
|
||||
proc syscall(sysno: clong): cint {.importc, header:"<unistd.h>", varargs.}
|
||||
|
||||
let
|
||||
SYS_getrandom {.importc, header: "<sys/syscall.h>".}: clong
|
||||
EAGAIN {.importc, header: "<errno.h>".}: cint
|
||||
EINTR {.importc, header: "<errno.h>".}: cint
|
||||
|
||||
var errno {.importc, header: "<errno.h>".}: cint
|
||||
|
||||
# https://man7.org/linux/man-pages/man2/getrandom.2.html
|
||||
#
|
||||
# ssize_t getrandom(void buf[.buflen], size_t buflen, unsigned int flags);
|
||||
#
|
||||
# For buffer <= 256 bytes, getrandom is uninterruptible
|
||||
# otherwise it can be interrupted by signals.
|
||||
# So either we read by chunks of 256 or we handle partial buffer fills after signals interruption
|
||||
#
|
||||
# We choose to handle partial buffer fills to limit the number of syscalls
|
||||
|
||||
proc urandom(pbuffer: pointer, len: int): bool {.sideeffect, tags: [CSPRNG].} =
|
||||
|
||||
var cur = 0
|
||||
while cur < len:
|
||||
let bytesRead = syscall(SYS_getrandom, pbuffer, len-cur, 0)
|
||||
if bytesRead > 0:
|
||||
cur += bytesRead
|
||||
elif bytesRead == 0:
|
||||
# According to documentation this should never happen,
|
||||
# either we read a positive number of bytes, or we have a negative error code
|
||||
return false
|
||||
elif errno == EAGAIN or errno == EINTR:
|
||||
# No entropy yet or interrupted by signal => retry
|
||||
discard
|
||||
else:
|
||||
# EFAULT The address referred to by buf is outside the accessible address space.
|
||||
# EINVAL An invalid flag was specified in flags.
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
proc sysrand*[T](buffer: var T): bool {.inline.} =
|
||||
## Fills the buffer with cryptographically secure random data
|
||||
return urandom(buffer.addr, sizeof(T))
|
||||
|
||||
elif defined(ios) or defined(macosx):
|
||||
# There are 4 APIs we can use
|
||||
# - The getentropy(2) system call (similar to OpenBSD)
|
||||
# - The random device (/dev/random)
|
||||
# - SecRandomCopyBytes
|
||||
# - CCRandomGenerateBytes
|
||||
#
|
||||
# SecRandomCopyBytes (https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecFramework.c.auto.html)
|
||||
# requires linking with the Security framework,
|
||||
# uses pthread_once (so initializes Grand Central Dispatch)
|
||||
# and opens /dev/random
|
||||
# This is heavy https://github.com/rust-random/getrandom/issues/38#issuecomment-505629378
|
||||
# - It makes linking more complex
|
||||
# - It incurs a notable startup cost
|
||||
#
|
||||
# getentropy is private on IOS and can lead to appstore rejection: https://github.com/openssl/openssl/pull/15924
|
||||
# the random device can be subject to file descriptor exhaustion
|
||||
#
|
||||
# CCRandomGenerateBytes adds a DRBG on top of the raw system RNG, but it's fast
|
||||
# - https://github.com/dotnet/runtime/pull/51526
|
||||
# - https://github.com/aws/aws-lc/pull/300
|
||||
|
||||
type CCRNGStatus {.importc, header: "<CommonCrypto/CommonRandom.h>".} = distinct int32
|
||||
|
||||
let kCCSuccess {.importc, header: "<CommonCrypto/CommonCryptoError.h>".}: CCRNGStatus
|
||||
# https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60061.30.1/include/CommonCryptoError.h.auto.html
|
||||
|
||||
func `==`(x, y: CCRNGStatus): bool {.borrow.}
|
||||
|
||||
proc CCRandomGenerateBytes(pbuffer: pointer, len: int): CCRNGStatus {.sideeffect, tags: [CSPRNG], importc, header: "<CommonCrypto/CommonRandom.h>".}
|
||||
# https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60178.40.2/include/CommonRandom.h.auto.html
|
||||
|
||||
proc sysrand*[T](buffer: var T): bool {.inline.} =
|
||||
## Fills the buffer with cryptographically secure random data
|
||||
if kCCSuccess == CCRandomGenerateBytes(buffer.addr, sizeof(T)):
|
||||
return true
|
||||
return false
|
||||
|
||||
else:
|
||||
{.error: "The OS '" & $hostOS & "' has no CSPRNG configured.".}
|
@ -23,7 +23,7 @@ const
|
||||
FUTEX_WAIT_PRIVATE = 128
|
||||
FUTEX_WAKE_PRIVATE = 129
|
||||
|
||||
proc syscall(sysno: clong): cint {.header:"<unistd.h>", varargs.}
|
||||
proc syscall(sysno: clong): cint {.importc, header:"<unistd.h>", varargs.}
|
||||
|
||||
proc sysFutex(
|
||||
futexAddr: pointer, operation: uint32, expected: uint32 or int32,
|
||||
|
@ -39,6 +39,9 @@ import
|
||||
#
|
||||
# We use 2^512 to cover the range the base field elements
|
||||
|
||||
# We use Nim effect system to track RNG subroutines
|
||||
type UnsafePRNG* = object
|
||||
|
||||
type RngState* = object
|
||||
## This is the state of a Xoshiro512** PRNG
|
||||
## Unsafe: for testing and benchmarking purposes only
|
||||
@ -69,7 +72,7 @@ func rotl(x: uint64, k: static int): uint64 {.inline.} =
|
||||
template `^=`(x: var uint64, y: uint64) =
|
||||
x = x xor y
|
||||
|
||||
func next*(rng: var RngState): uint64 =
|
||||
func next*(rng: var RngState): uint64 {.tags: [UnsafePRNG].} =
|
||||
## Compute a random uint64 from the input state
|
||||
## using xoshiro512** algorithm by Vigna et al
|
||||
## State is updated.
|
||||
|
31
tests/t_csprngs.nim
Normal file
31
tests/t_csprngs.nim
Normal file
@ -0,0 +1,31 @@
|
||||
# Constantine
|
||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
||||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
std/unittest,
|
||||
../constantine/csprngs/sysrand
|
||||
|
||||
suite "[CSPRNG] sysrand":
|
||||
test "Non-nil initialization":
|
||||
# Initializing to full 0 has a chance of 2^-256
|
||||
proc checkNonNil() =
|
||||
var buf: array[32, byte] # zero-init
|
||||
|
||||
doAssert sysrand(buf)
|
||||
|
||||
var nonNil = false
|
||||
for b in buf:
|
||||
nonNil = nonNil or (b != byte 0)
|
||||
|
||||
doAssert nonNil
|
||||
|
||||
checkNonNil()
|
||||
|
||||
# TODO:
|
||||
# - Hamming weight average 50%
|
||||
# - statistics/hypothesis tests
|
Loading…
x
Reference in New Issue
Block a user