summary refs log tree commit diff stats
path: root/lib/std
diff options
context:
space:
mode:
authorflywind <43030857+xflywind@users.noreply.github.com>2021-02-11 18:00:01 -0600
committerGitHub <noreply@github.com>2021-02-11 16:00:01 -0800
commit18c24eb4d0694beb971581d59a4570826c5fa01f (patch)
treedf0a6a3404b20712befe354cda4b13597d45e62f /lib/std
parent81533a0014a8372207426dc9d067aff4b59db8af (diff)
downloadNim-18c24eb4d0694beb971581d59a4570826c5fa01f.tar.gz
add system random to stdlib: std/sysrand (#16459)
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/private/jsutils.nim6
-rw-r--r--lib/std/sysrand.nim308
2 files changed, 314 insertions, 0 deletions
diff --git a/lib/std/private/jsutils.nim b/lib/std/private/jsutils.nim
index 47a6caa2b..aa2e984ff 100644
--- a/lib/std/private/jsutils.nim
+++ b/lib/std/private/jsutils.nim
@@ -5,14 +5,20 @@ when defined(js):
     ArrayBuffer* = ref object of JsRoot
     Float64Array* = ref object of JsRoot
     Uint32Array* = ref object of JsRoot
+    Uint8Array* = ref object of JsRoot
     BigUint64Array* = ref object of JsRoot
 
+
   func newArrayBuffer*(n: int): ArrayBuffer {.importjs: "new ArrayBuffer(#)".}
   func newFloat64Array*(buffer: ArrayBuffer): Float64Array {.importjs: "new Float64Array(#)".}
   func newUint32Array*(buffer: ArrayBuffer): Uint32Array {.importjs: "new Uint32Array(#)".}
   func newBigUint64Array*(buffer: ArrayBuffer): BigUint64Array {.importjs: "new BigUint64Array(#)".}
 
+
+  func newUint8Array*(n: int): Uint8Array {.importjs: "new Uint8Array(#)".}
+
   func `[]`*(arr: Uint32Array, i: int): uint32 {.importjs: "#[#]".}
+  func `[]`*(arr: Uint8Array, i: int): uint8 {.importjs: "#[#]".}
   func `[]`*(arr: BigUint64Array, i: int): JsBigInt {.importjs: "#[#]".}
   func `[]=`*(arr: Float64Array, i: int, v: float) {.importjs: "#[#] = #".}
 
diff --git a/lib/std/sysrand.nim b/lib/std/sysrand.nim
new file mode 100644
index 000000000..9a143adb3
--- /dev/null
+++ b/lib/std/sysrand.nim
@@ -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>".}
+    # 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
+          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())