diff --git a/constantine.nimble b/constantine.nimble index dde4c4b9..6690d6f1 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -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 # ---------------------------------------------------------- diff --git a/constantine/csprngs/sysrand.nim b/constantine/csprngs/sysrand.nim new file mode 100644 index 00000000..8f405106 --- /dev/null +++ b/constantine/csprngs/sysrand.nim @@ -0,0 +1,139 @@ +# 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, stdcall, dynlib: "advapi32.dll", sideeffect, tags: [CSPRNG].} + # 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, sizeof(T)) + +elif defined(linux): + proc syscall(sysno: clong): cint {.importc, header:"", varargs.} + + let + SYS_getrandom {.importc, header: "".}: clong + EAGAIN {.importc, header: "".}: cint + EINTR {.importc, header: "".}: cint + + var errno {.importc, header: "".}: 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: "".} = distinct int32 + + let kCCSuccess {.importc, header: "".} = CCRNGStatus + # https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60061.30.1/include/CommonCryptoError.h.auto.html + + proc CCRandomGenerateBytes(pbuffer: pointer, len: int): CCRNGStatus {.sideeffect, tags: [CSPRNG], importc, header: "".} + # 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.".} \ No newline at end of file diff --git a/constantine/threadpool/primitives/futexes_linux.nim b/constantine/threadpool/primitives/futexes_linux.nim index 149723bd..cf88d20e 100644 --- a/constantine/threadpool/primitives/futexes_linux.nim +++ b/constantine/threadpool/primitives/futexes_linux.nim @@ -23,7 +23,7 @@ const FUTEX_WAIT_PRIVATE = 128 FUTEX_WAKE_PRIVATE = 129 -proc syscall(sysno: clong): cint {.header:"", varargs.} +proc syscall(sysno: clong): cint {.importc, header:"", varargs.} proc sysFutex( futexAddr: pointer, operation: uint32, expected: uint32 or int32, diff --git a/helpers/prng_unsafe.nim b/helpers/prng_unsafe.nim index 3a68d6bd..c1ed2ec0 100644 --- a/helpers/prng_unsafe.nim +++ b/helpers/prng_unsafe.nim @@ -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. diff --git a/tests/t_csprngs.nim b/tests/t_csprngs.nim new file mode 100644 index 00000000..8faeb1fd --- /dev/null +++ b/tests/t_csprngs.nim @@ -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 \ No newline at end of file