sysrand.nim 11 KB

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