1#
2#
3#            Nim's Runtime Library
4#        (c) Copyright 2021 Nim contributors
5#
6#    See the file "copying.txt", included in this
7#    distribution, for details about the copyright.
8#
9
10## .. warning:: This module was added in Nim 1.6. If you are using it for cryptographic purposes,
11##   keep in mind that so far this has not been audited by any security professionals,
12##   therefore may not be secure.
13##
14## `std/sysrand` generates random numbers from a secure source provided by the operating system.
15## It is a cryptographically secure pseudorandom number generator
16## and should be unpredictable enough for cryptographic applications,
17## though its exact quality depends on the OS implementation.
18##
19## | Targets              | Implementation        |
20## | :---                 | ----:                 |
21## | Windows              | `BCryptGenRandom`_    |
22## | Linux                | `getrandom`_          |
23## | MacOSX               | `getentropy`_         |
24## | iOS                  | `SecRandomCopyBytes`_ |
25## | OpenBSD              | `getentropy openbsd`_ |
26## | FreeBSD              | `getrandom freebsd`_  |
27## | JS (Web Browser)     | `getRandomValues`_    |
28## | Node.js              | `randomFillSync`_     |
29## | Other Unix platforms | `/dev/urandom`_       |
30##
31## .. _BCryptGenRandom: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
32## .. _getrandom: https://man7.org/linux/man-pages/man2/getrandom.2.html
33## .. _getentropy: https://www.unix.com/man-page/mojave/2/getentropy
34## .. _SecRandomCopyBytes: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc
35## .. _getentropy openbsd: https://man.openbsd.org/getentropy.2
36## .. _getrandom freebsd: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable
37## .. _getRandomValues: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
38## .. _randomFillSync: https://nodejs.org/api/crypto.html#crypto_crypto_randomfillsync_buffer_offset_size
39## .. _/dev/urandom: https://en.wikipedia.org/wiki//dev/random
40##
41## On a Linux target, a call to the `getrandom` syscall can be avoided (e.g.
42## for targets running kernel version < 3.17) by passing a compile flag of
43## `-d:nimNoGetRandom`. If this flag is passed, sysrand will use `/dev/urandom`
44## as with any other POSIX compliant OS.
45##
46
47runnableExamples:
48  doAssert urandom(0).len == 0
49  doAssert urandom(113).len == 113
50  doAssert urandom(1234) != urandom(1234) # unlikely to fail in practice
51
52##
53## See also
54## ========
55## * `random module <random.html>`_
56##
57
58
59when not defined(js):
60  import os
61
62when defined(posix):
63  import posix
64
65const
66  batchImplOS = defined(freebsd) or defined(openbsd) or (defined(macosx) and not defined(ios))
67  batchSize {.used.} = 256
68
69when batchImplOS:
70  template batchImpl(result: var int, dest: var openArray[byte], getRandomImpl) =
71    let size = dest.len
72    if size == 0:
73      return
74
75    let
76      chunks = (size - 1) div batchSize
77      left = size - chunks * batchSize
78
79    for i in 0 ..< chunks:
80      let readBytes = getRandomImpl(addr dest[result], batchSize)
81      if readBytes < 0:
82        return readBytes
83      inc(result, batchSize)
84
85    result = getRandomImpl(addr dest[result], left)
86
87when defined(js):
88  import std/private/jsutils
89
90  when defined(nodejs):
91    {.emit: "const _nim_nodejs_crypto = require('crypto');".}
92
93    proc randomFillSync(p: Uint8Array) {.importjs: "_nim_nodejs_crypto.randomFillSync(#)".}
94
95    template urandomImpl(result: var int, dest: var openArray[byte]) =
96      let size = dest.len
97      if size == 0:
98        return
99
100      var src = newUint8Array(size)
101      randomFillSync(src)
102      for i in 0 ..< size:
103        dest[i] = src[i]
104
105  else:
106    proc getRandomValues(p: Uint8Array) {.importjs: "window.crypto.getRandomValues(#)".}
107      # The requested length of `p` must not be more than 65536.
108
109    proc assign(dest: var openArray[byte], src: Uint8Array, base: int, size: int) =
110      getRandomValues(src)
111      for j in 0 ..< size:
112        dest[base + j] = src[j]
113
114    template urandomImpl(result: var int, dest: var openArray[byte]) =
115      let size = dest.len
116      if size == 0:
117        return
118
119      if size <= batchSize:
120        var src = newUint8Array(size)
121        assign(dest, src, 0, size)
122        return
123
124      let
125        chunks = (size - 1) div batchSize
126        left = size - chunks * batchSize
127
128      var srcArray = newUint8Array(batchSize)
129      for i in 0 ..< chunks:
130        assign(dest, srcArray, result, batchSize)
131        inc(result, batchSize)
132
133      var leftArray = newUint8Array(left)
134      assign(dest, leftArray, result, left)
135
136elif defined(windows):
137  type
138    PVOID = pointer
139    BCRYPT_ALG_HANDLE = PVOID
140    PUCHAR = ptr uint8
141    NTSTATUS = clong
142    ULONG = culong
143
144  const
145    STATUS_SUCCESS = 0x00000000
146    BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002
147
148  proc bCryptGenRandom(
149    hAlgorithm: BCRYPT_ALG_HANDLE,
150    pbBuffer: PUCHAR,
151    cbBuffer: ULONG,
152    dwFlags: ULONG
153  ): NTSTATUS {.stdcall, importc: "BCryptGenRandom", dynlib: "Bcrypt.dll".}
154
155
156  proc randomBytes(pbBuffer: pointer, cbBuffer: Natural): int {.inline.} =
157    bCryptGenRandom(nil, cast[PUCHAR](pbBuffer), ULONG(cbBuffer),
158                            BCRYPT_USE_SYSTEM_PREFERRED_RNG)
159
160  template urandomImpl(result: var int, dest: var openArray[byte]) =
161    let size = dest.len
162    if size == 0:
163      return
164
165    result = randomBytes(addr dest[0], size)
166
167elif defined(linux) and not defined(nimNoGetRandom) and not defined(emscripten):
168  # TODO using let, pending bootstrap >= 1.4.0
169  var SYS_getrandom {.importc: "SYS_getrandom", header: "<sys/syscall.h>".}: clong
170  const syscallHeader = """#include <unistd.h>
171#include <sys/syscall.h>"""
172
173  proc syscall(
174    n: clong, buf: pointer, bufLen: cint, flags: cuint
175  ): clong {.importc: "syscall", header: syscallHeader.}
176    #  When reading from the urandom source (GRND_RANDOM is not set),
177    #  getrandom() will block until the entropy pool has been
178    #  initialized (unless the GRND_NONBLOCK flag was specified).  If a
179    #  request is made to read a large number of bytes (more than 256),
180    #  getrandom() will block until those bytes have been generated and
181    #  transferred from kernel memory to buf.
182
183  template urandomImpl(result: var int, dest: var openArray[byte]) =
184    let size = dest.len
185    if size == 0:
186      return
187
188    while result < size:
189      let readBytes = syscall(SYS_getrandom, addr dest[result], cint(size - result), 0).int
190      if readBytes == 0:
191        doAssert false
192      elif readBytes > 0:
193        inc(result, readBytes)
194      else:
195        if osLastError().int in {EINTR, EAGAIN}:
196          discard
197        else:
198          result = -1
199          break
200
201elif defined(openbsd):
202  proc getentropy(p: pointer, size: cint): cint {.importc: "getentropy", header: "<unistd.h>".}
203    # Fills a buffer with high-quality entropy,
204    # which can be used as input for process-context pseudorandom generators like `arc4random`.
205    # The maximum buffer size permitted is 256 bytes.
206
207  proc getRandomImpl(p: pointer, size: int): int {.inline.} =
208    result = getentropy(p, cint(size)).int
209
210elif defined(freebsd):
211  type cssize_t {.importc: "ssize_t", header: "<sys/types.h>".} = int
212
213  proc getrandom(p: pointer, size: csize_t, flags: cuint): cssize_t {.importc: "getrandom", header: "<sys/random.h>".}
214    # Upon successful completion, the number of bytes which were actually read
215    # is returned. For requests larger than 256 bytes, this can be fewer bytes
216    # than were requested. Otherwise, -1 is returned and the global variable
217    # errno is set to indicate the error.
218
219  proc getRandomImpl(p: pointer, size: int): int {.inline.} =
220    result = getrandom(p, csize_t(size), 0)
221
222elif defined(ios):
223  {.passL: "-framework Security".}
224
225  const errSecSuccess = 0 ## No error.
226
227  type
228    SecRandom {.importc: "struct __SecRandom".} = object
229
230    SecRandomRef = ptr SecRandom
231      ## An abstract Core Foundation-type object containing information about a random number generator.
232
233  proc secRandomCopyBytes(
234    rnd: SecRandomRef, count: csize_t, bytes: pointer
235  ): cint {.importc: "SecRandomCopyBytes", header: "<Security/SecRandom.h>".}
236    ## https://developer.apple.com/documentation/security/1399291-secrandomcopybytes
237
238  template urandomImpl(result: var int, dest: var openArray[byte]) =
239    let size = dest.len
240    if size == 0:
241      return
242
243    result = secRandomCopyBytes(nil, csize_t(size), addr dest[0])
244
245elif defined(macosx):
246  const sysrandomHeader = """#include <Availability.h>
247#include <sys/random.h>
248"""
249
250  proc getentropy(p: pointer, size: csize_t): cint {.importc: "getentropy", header: sysrandomHeader.}
251    # getentropy() fills a buffer with random data, which can be used as input
252    # for process-context pseudorandom generators like arc4random(3).
253    # The maximum buffer size permitted is 256 bytes.
254
255  proc getRandomImpl(p: pointer, size: int): int {.inline.} =
256    result = getentropy(p, csize_t(size)).int
257
258else:
259  template urandomImpl(result: var int, dest: var openArray[byte]) =
260    let size = dest.len
261    if size == 0:
262      return
263
264    # see: https://www.2uo.de/myths-about-urandom/ which justifies using urandom instead of random
265    let fd = posix.open("/dev/urandom", O_RDONLY)
266
267    if fd < 0:
268      result = -1
269    else:
270      try:
271        var stat: Stat
272        if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode):
273          let
274            chunks = (size - 1) div batchSize
275            left = size - chunks * batchSize
276
277          for i in 0 ..< chunks:
278            let readBytes = posix.read(fd, addr dest[result], batchSize)
279            if readBytes < 0:
280              return readBytes
281            inc(result, batchSize)
282
283          result = posix.read(fd, addr dest[result], left)
284        else:
285          result = -1
286      finally:
287        discard posix.close(fd)
288
289proc urandomInternalImpl(dest: var openArray[byte]): int {.inline.} =
290  when batchImplOS:
291    batchImpl(result, dest, getRandomImpl)
292  else:
293    urandomImpl(result, dest)
294
295proc urandom*(dest: var openArray[byte]): bool =
296  ## Fills `dest` with random bytes suitable for cryptographic use.
297  ## If the call succeeds, returns `true`.
298  ##
299  ## If `dest` is empty, `urandom` immediately returns success,
300  ## without calling the underlying operating system API.
301  ##
302  ## .. warning:: The code hasn't been audited by cryptography experts and
303  ##   is provided as-is without guarantees. Use at your own risks. For production
304  ##   systems we advise you to request an external audit.
305  result = true
306  when defined(js): discard urandomInternalImpl(dest)
307  else:
308    let ret = urandomInternalImpl(dest)
309    when defined(windows):
310      if ret != STATUS_SUCCESS:
311        result = false
312    else:
313      if ret < 0:
314        result = false
315
316proc urandom*(size: Natural): seq[byte] {.inline.} =
317  ## Returns random bytes suitable for cryptographic use.
318  ##
319  ## .. warning:: The code hasn't been audited by cryptography experts and
320  ##   is provided as-is without guarantees. Use at your own risks. For production
321  ##   systems we advise you to request an external audit.
322  result = newSeq[byte](size)
323  when defined(js): discard urandomInternalImpl(result)
324  else:
325    if not urandom(result):
326      raiseOSError(osLastError())
327