sysrand.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. # TODO using let, pending bootstrap >= 1.4.0
  140. var SYS_getrandom {.importc: "SYS_getrandom", header: "<sys/syscall.h>".}: clong
  141. const syscallHeader = """#include <unistd.h>
  142. #include <sys/syscall.h>"""
  143. proc syscall(n: clong): clong {.
  144. importc: "syscall", varargs, header: syscallHeader.}
  145. # When reading from the urandom source (GRND_RANDOM is not set),
  146. # getrandom() will block until the entropy pool has been
  147. # initialized (unless the GRND_NONBLOCK flag was specified). If a
  148. # request is made to read a large number of bytes (more than 256),
  149. # getrandom() will block until those bytes have been generated and
  150. # transferred from kernel memory to buf.
  151. template urandomImpl(result: var int, dest: var openArray[byte]) =
  152. let size = dest.len
  153. if size == 0:
  154. return
  155. while result < size:
  156. let readBytes = syscall(SYS_getrandom, addr dest[result], cint(size - result), 0).int
  157. if readBytes == 0:
  158. doAssert false
  159. elif readBytes > 0:
  160. inc(result, readBytes)
  161. else:
  162. if osLastError().int in {EINTR, EAGAIN}:
  163. discard
  164. else:
  165. result = -1
  166. break
  167. elif defined(openbsd):
  168. proc getentropy(p: pointer, size: cint): cint {.importc: "getentropy", header: "<unistd.h>".}
  169. # Fills a buffer with high-quality entropy,
  170. # which can be used as input for process-context pseudorandom generators like `arc4random`.
  171. # The maximum buffer size permitted is 256 bytes.
  172. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  173. result = getentropy(p, cint(size)).int
  174. elif defined(zephyr):
  175. proc sys_csrand_get(dst: pointer, length: csize_t): cint {.importc: "sys_csrand_get", header: "<random/rand32.h>".}
  176. # Fill the destination buffer with cryptographically secure
  177. # random data values
  178. #
  179. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  180. # 0 if success, -EIO if entropy reseed error
  181. result = sys_csrand_get(p, csize_t(size)).int
  182. elif defined(freebsd):
  183. type cssize_t {.importc: "ssize_t", header: "<sys/types.h>".} = int
  184. proc getrandom(p: pointer, size: csize_t, flags: cuint): cssize_t {.importc: "getrandom", header: "<sys/random.h>".}
  185. # Upon successful completion, the number of bytes which were actually read
  186. # is returned. For requests larger than 256 bytes, this can be fewer bytes
  187. # than were requested. Otherwise, -1 is returned and the global variable
  188. # errno is set to indicate the error.
  189. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  190. result = getrandom(p, csize_t(size), 0)
  191. elif defined(ios) or defined(macosx):
  192. {.passl: "-framework Security".}
  193. const errSecSuccess = 0 ## No error.
  194. type
  195. SecRandom {.importc: "struct __SecRandom".} = object
  196. SecRandomRef = ptr SecRandom
  197. ## An abstract Core Foundation-type object containing information about a random number generator.
  198. proc secRandomCopyBytes(
  199. rnd: SecRandomRef, count: csize_t, bytes: pointer
  200. ): cint {.importc: "SecRandomCopyBytes", header: "<Security/SecRandom.h>".}
  201. ## https://developer.apple.com/documentation/security/1399291-secrandomcopybytes
  202. template urandomImpl(result: var int, dest: var openArray[byte]) =
  203. let size = dest.len
  204. if size == 0:
  205. return
  206. result = secRandomCopyBytes(nil, csize_t(size), addr dest[0])
  207. else:
  208. template urandomImpl(result: var int, dest: var openArray[byte]) =
  209. let size = dest.len
  210. if size == 0:
  211. return
  212. # see: https://www.2uo.de/myths-about-urandom/ which justifies using urandom instead of random
  213. let fd = posix.open("/dev/urandom", O_RDONLY)
  214. if fd < 0:
  215. result = -1
  216. else:
  217. try:
  218. var stat: Stat
  219. if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode):
  220. let
  221. chunks = (size - 1) div batchSize
  222. left = size - chunks * batchSize
  223. for i in 0 ..< chunks:
  224. let readBytes = posix.read(fd, addr dest[result], batchSize)
  225. if readBytes < 0:
  226. return readBytes
  227. inc(result, batchSize)
  228. result = posix.read(fd, addr dest[result], left)
  229. else:
  230. result = -1
  231. finally:
  232. discard posix.close(fd)
  233. proc urandomInternalImpl(dest: var openArray[byte]): int {.inline.} =
  234. when batchImplOS:
  235. batchImpl(result, dest, getRandomImpl)
  236. else:
  237. urandomImpl(result, dest)
  238. proc urandom*(dest: var openArray[byte]): bool =
  239. ## Fills `dest` with random bytes suitable for cryptographic use.
  240. ## If the call succeeds, returns `true`.
  241. ##
  242. ## If `dest` is empty, `urandom` immediately returns success,
  243. ## without calling the underlying operating system API.
  244. ##
  245. ## .. warning:: The code hasn't been audited by cryptography experts and
  246. ## is provided as-is without guarantees. Use at your own risks. For production
  247. ## systems we advise you to request an external audit.
  248. result = true
  249. when defined(js): discard urandomInternalImpl(dest)
  250. else:
  251. let ret = urandomInternalImpl(dest)
  252. when defined(windows):
  253. if ret != STATUS_SUCCESS:
  254. result = false
  255. else:
  256. if ret < 0:
  257. result = false
  258. proc urandom*(size: Natural): seq[byte] {.inline.} =
  259. ## Returns random bytes suitable for cryptographic use.
  260. ##
  261. ## .. warning:: The code hasn't been audited by cryptography experts and
  262. ## is provided as-is without guarantees. Use at your own risks. For production
  263. ## systems we advise you to request an external audit.
  264. result = newSeq[byte](size)
  265. when defined(js): discard urandomInternalImpl(result)
  266. else:
  267. if not urandom(result):
  268. raiseOSError(osLastError())