123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2021 Nim contributors
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## .. warning:: This module was added in Nim 1.6. If you are using it for cryptographic purposes,
- ## keep in mind that so far this has not been audited by any security professionals,
- ## therefore may not be secure.
- ##
- ## `std/sysrand` generates random numbers from a secure source provided by the operating system.
- ## It is a cryptographically secure pseudorandom number generator
- ## and 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`_ |
- ## | Node.js | `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))
- batchSize {.used.} = 256
- when batchImplOS:
- 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:
- 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):
- # TODO using let, pending bootstrap >= 1.4.0
- var 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>".}
- # 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(size), 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)
- if fd < 0:
- result = -1
- else:
- try:
- var stat: Stat
- if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode):
- let
- chunks = (size - 1) div batchSize
- 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
- finally:
- discard posix.close(fd)
- 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 the call succeeds, returns `true`.
- ##
- ## If `dest` is empty, `urandom` immediately returns success,
- ## without calling the underlying operating system API.
- ##
- ## .. warning:: The code hasn't been audited by cryptography experts and
- ## is provided as-is without guarantees. Use at your own risks. For production
- ## systems we advise you to request an external audit.
- 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.
- ##
- ## .. warning:: The code hasn't been audited by cryptography experts and
- ## is provided as-is without guarantees. Use at your own risks. For production
- ## systems we advise you to request an external audit.
- result = newSeq[byte](size)
- when defined(js): discard urandomInternalImpl(result)
- else:
- if not urandom(result):
- raiseOSError(osLastError())
|