-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add system random to stdlib: std/sysrand (#16459)
- Loading branch information
Showing
6 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
# | ||
# | ||
# Nim's Runtime Library | ||
# (c) Copyright 2021 Nim contributors | ||
# | ||
# See the file "copying.txt", included in this | ||
# distribution, for details about the copyright. | ||
# | ||
|
||
## `std/sysrand` generates random numbers from a secure source provided by the operating system. | ||
## It is also called Cryptographically secure pseudorandom number generator. | ||
## It should be unpredictable enough for cryptographic applications, | ||
## though its exact quality depends on the OS implementation. | ||
## | ||
## | Targets | Implementation| | ||
## | :--- | ----: | | ||
## | Windows | `BCryptGenRandom`_ | | ||
## | Linux | `getrandom`_ | | ||
## | MacOSX | `getentropy`_ | | ||
## | IOS | `SecRandomCopyBytes`_ | | ||
## | OpenBSD | `getentropy openbsd`_ | | ||
## | FreeBSD | `getrandom freebsd`_ | | ||
## | JS(Web Browser) | `getRandomValues`_ | | ||
## | Nodejs | `randomFillSync`_ | | ||
## | Other Unix platforms | `/dev/urandom`_ | | ||
## | ||
## .. _BCryptGenRandom: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | ||
## .. _getrandom: https://man7.org/linux/man-pages/man2/getrandom.2.html | ||
## .. _getentropy: https://www.unix.com/man-page/mojave/2/getentropy | ||
## .. _SecRandomCopyBytes: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc | ||
## .. _getentropy openbsd: https://man.openbsd.org/getentropy.2 | ||
## .. _getrandom freebsd: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable | ||
## .. _getRandomValues: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues | ||
## .. _randomFillSync: https://nodejs.org/api/crypto.html#crypto_crypto_randomfillsync_buffer_offset_size | ||
## .. _/dev/urandom: https://en.wikipedia.org/wiki//dev/random | ||
## | ||
|
||
runnableExamples: | ||
doAssert urandom(0).len == 0 | ||
doAssert urandom(113).len == 113 | ||
doAssert urandom(1234) != urandom(1234) # unlikely to fail in practice | ||
|
||
## | ||
## See also | ||
## ======== | ||
## * `random module <random.html>`_ | ||
## | ||
|
||
|
||
when not defined(js): | ||
import std/os | ||
|
||
when defined(posix): | ||
import std/posix | ||
|
||
const batchImplOS = defined(freebsd) or defined(openbsd) or (defined(macosx) and not defined(ios)) | ||
|
||
when batchImplOS: | ||
const batchSize = 256 | ||
|
||
template batchImpl(result: var int, dest: var openArray[byte], getRandomImpl) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
let | ||
chunks = (size - 1) div batchSize | ||
left = size - chunks * batchSize | ||
|
||
for i in 0 ..< chunks: | ||
let readBytes = getRandomImpl(addr dest[result], batchSize) | ||
if readBytes < 0: | ||
return readBytes | ||
inc(result, batchSize) | ||
|
||
result = getRandomImpl(addr dest[result], left) | ||
|
||
when defined(js): | ||
import std/private/jsutils | ||
|
||
when defined(nodejs): | ||
{.emit: "const _nim_nodejs_crypto = require('crypto');".} | ||
|
||
proc randomFillSync(p: Uint8Array) {.importjs: "_nim_nodejs_crypto.randomFillSync(#)".} | ||
|
||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
var src = newUint8Array(size) | ||
randomFillSync(src) | ||
for i in 0 ..< size: | ||
dest[i] = src[i] | ||
|
||
else: | ||
const batchSize = 256 | ||
|
||
proc getRandomValues(p: Uint8Array) {.importjs: "window.crypto.getRandomValues(#)".} | ||
# The requested length of `p` must not be more than 65536. | ||
|
||
proc assign(dest: var openArray[byte], src: Uint8Array, base: int, size: int) = | ||
getRandomValues(src) | ||
for j in 0 ..< size: | ||
dest[base + j] = src[j] | ||
|
||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
if size <= batchSize: | ||
var src = newUint8Array(size) | ||
assign(dest, src, 0, size) | ||
return | ||
|
||
let | ||
chunks = (size - 1) div batchSize | ||
left = size - chunks * batchSize | ||
|
||
var srcArray = newUint8Array(batchSize) | ||
for i in 0 ..< chunks: | ||
assign(dest, srcArray, result, batchSize) | ||
inc(result, batchSize) | ||
|
||
var leftArray = newUint8Array(left) | ||
assign(dest, leftArray, result, left) | ||
|
||
elif defined(windows): | ||
type | ||
PVOID = pointer | ||
BCRYPT_ALG_HANDLE = PVOID | ||
PUCHAR = ptr cuchar | ||
NTSTATUS = clong | ||
ULONG = culong | ||
|
||
const | ||
STATUS_SUCCESS = 0x00000000 | ||
BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002 | ||
|
||
proc bCryptGenRandom( | ||
hAlgorithm: BCRYPT_ALG_HANDLE, | ||
pbBuffer: PUCHAR, | ||
cbBuffer: ULONG, | ||
dwFlags: ULONG | ||
): NTSTATUS {.stdcall, importc: "BCryptGenRandom", dynlib: "Bcrypt.dll".} | ||
|
||
|
||
proc randomBytes(pbBuffer: pointer, cbBuffer: Natural): int {.inline.} = | ||
bCryptGenRandom(nil, cast[PUCHAR](pbBuffer), ULONG(cbBuffer), | ||
BCRYPT_USE_SYSTEM_PREFERRED_RNG) | ||
|
||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
result = randomBytes(addr dest[0], size) | ||
|
||
elif defined(linux): | ||
let SYS_getrandom {.importc: "SYS_getrandom", header: "<sys/syscall.h>".}: clong | ||
const syscallHeader = """#include <unistd.h> | ||
#include <sys/syscall.h>""" | ||
|
||
proc syscall( | ||
n: clong, buf: pointer, bufLen: cint, flags: cuint | ||
): clong {.importc: "syscall", header: syscallHeader.} | ||
# When reading from the urandom source (GRND_RANDOM is not set), | ||
# getrandom() will block until the entropy pool has been | ||
# initialized (unless the GRND_NONBLOCK flag was specified). If a | ||
# request is made to read a large number of bytes (more than 256), | ||
# getrandom() will block until those bytes have been generated and | ||
# transferred from kernel memory to buf. | ||
|
||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
while result < size: | ||
let readBytes = syscall(SYS_getrandom, addr dest[result], cint(size - result), 0).int | ||
if readBytes == 0: | ||
doAssert false | ||
elif readBytes > 0: | ||
inc(result, readBytes) | ||
else: | ||
if osLastError().int in {EINTR, EAGAIN}: | ||
discard | ||
else: | ||
result = -1 | ||
break | ||
|
||
elif defined(openbsd): | ||
proc getentropy(p: pointer, size: cint): cint {.importc: "getentropy", header: "<unistd.h>".} | ||
This comment has been minimized.
Sorry, something went wrong. |
||
# fills a buffer with high-quality entropy, | ||
# which can be used as input for process-context pseudorandom generators like `arc4random`. | ||
# The maximum buffer size permitted is 256 bytes. | ||
|
||
proc getRandomImpl(p: pointer, size: int): int {.inline.} = | ||
result = getentropy(p, cint(size)).int | ||
|
||
elif defined(freebsd): | ||
type cssize_t {.importc: "ssize_t", header: "<sys/types.h>".} = int | ||
|
||
proc getrandom(p: pointer, size: csize_t, flags: cuint): cssize_t {.importc: "getrandom", header: "<sys/random.h>".} | ||
# Upon successful completion, the number of bytes which were actually read | ||
# is returned. For requests larger than 256 bytes, this can be fewer bytes | ||
# than were requested. Otherwise, -1 is returned and the global variable | ||
# errno is set to indicate the error. | ||
|
||
proc getRandomImpl(p: pointer, size: int): int {.inline.} = | ||
result = getrandom(p, csize_t(batchSize), 0) | ||
|
||
elif defined(ios): | ||
{.passL: "-framework Security".} | ||
|
||
const errSecSuccess = 0 ## No error. | ||
|
||
type | ||
SecRandom {.importc: "struct __SecRandom".} = object | ||
|
||
SecRandomRef = ptr SecRandom | ||
## An abstract Core Foundation-type object containing information about a random number generator. | ||
|
||
proc secRandomCopyBytes( | ||
rnd: SecRandomRef, count: csize_t, bytes: pointer | ||
): cint {.importc: "SecRandomCopyBytes", header: "<Security/SecRandom.h>".} | ||
## https://developer.apple.com/documentation/security/1399291-secrandomcopybytes | ||
|
||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
result = secRandomCopyBytes(nil, csize_t(size), addr dest[0]) | ||
|
||
elif defined(macosx): | ||
const sysrandomHeader = """#include <Availability.h> | ||
#include <sys/random.h> | ||
""" | ||
|
||
proc getentropy(p: pointer, size: csize_t): cint {.importc: "getentropy", header: sysrandomHeader.} | ||
# getentropy() fills a buffer with random data, which can be used as input | ||
# for process-context pseudorandom generators like arc4random(3). | ||
# The maximum buffer size permitted is 256 bytes. | ||
|
||
proc getRandomImpl(p: pointer, size: int): int {.inline.} = | ||
result = getentropy(p, csize_t(size)).int | ||
|
||
else: | ||
template urandomImpl(result: var int, dest: var openArray[byte]) = | ||
let size = dest.len | ||
if size == 0: | ||
return | ||
|
||
# see: https://www.2uo.de/myths-about-urandom/ which justifies using urandom instead of random | ||
let fd = posix.open("/dev/urandom", O_RDONLY) | ||
defer: discard posix.close(fd) | ||
|
||
if fd > 0: | ||
var stat: Stat | ||
if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode): | ||
let | ||
chunks = (size - 1) div batchSize | ||
This comment has been minimized.
Sorry, something went wrong.
euantorano
Contributor
|
||
left = size - chunks * batchSize | ||
|
||
for i in 0 ..< chunks: | ||
let readBytes = posix.read(fd, addr dest[result], batchSize) | ||
if readBytes < 0: | ||
return readBytes | ||
inc(result, batchSize) | ||
|
||
result = posix.read(fd, addr dest[result], left) | ||
else: | ||
result = -1 | ||
else: | ||
result = -1 | ||
|
||
proc urandomInternalImpl(dest: var openArray[byte]): int {.inline.} = | ||
when batchImplOS: | ||
batchImpl(result, dest, getRandomImpl) | ||
else: | ||
urandomImpl(result, dest) | ||
|
||
proc urandom*(dest: var openArray[byte]): bool = | ||
## Fills `dest` with random bytes suitable for cryptographic use. | ||
## If succeed, returns `true`. | ||
## | ||
## If `dest` is empty, `urandom` immediately returns success, | ||
## without calling underlying operating system api. | ||
result = true | ||
when defined(js): discard urandomInternalImpl(dest) | ||
else: | ||
let ret = urandomInternalImpl(dest) | ||
when defined(windows): | ||
if ret != STATUS_SUCCESS: | ||
result = false | ||
else: | ||
if ret < 0: | ||
result = false | ||
|
||
proc urandom*(size: Natural): seq[byte] {.inline.} = | ||
## Returns random bytes suitable for cryptographic use. | ||
result = newSeq[byte](size) | ||
when defined(js): discard urandomInternalImpl(result) | ||
else: | ||
if not urandom(result): | ||
raiseOsError(osLastError()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
discard """ | ||
targets: "c cpp js" | ||
""" | ||
|
||
import std/sysrand | ||
|
||
|
||
doAssert urandom(0).len == 0 | ||
doAssert urandom(10).len == 10 | ||
doAssert urandom(20).len == 20 | ||
doAssert urandom(120).len == 120 | ||
doAssert urandom(113).len == 113 | ||
doAssert urandom(1234) != urandom(1234) # unlikely to fail in practice |
OpenBSD advises
arc4random
for normal code, rather thangetentropy
: https://man.openbsd.org/arc4random.3