sysrand.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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 | `getentropy`_ |
  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. runnableExamples:
  41. doAssert urandom(0).len == 0
  42. doAssert urandom(113).len == 113
  43. doAssert urandom(1234) != urandom(1234) # unlikely to fail in practice
  44. ##
  45. ## See also
  46. ## ========
  47. ## * `random module <random.html>`_
  48. ##
  49. when not defined(js):
  50. import std/os
  51. when defined(posix):
  52. import std/posix
  53. const
  54. batchImplOS = defined(freebsd) or defined(openbsd) or (defined(macosx) and not defined(ios))
  55. batchSize {.used.} = 256
  56. when batchImplOS:
  57. template batchImpl(result: var int, dest: var openArray[byte], getRandomImpl) =
  58. let size = dest.len
  59. if size == 0:
  60. return
  61. let
  62. chunks = (size - 1) div batchSize
  63. left = size - chunks * batchSize
  64. for i in 0 ..< chunks:
  65. let readBytes = getRandomImpl(addr dest[result], batchSize)
  66. if readBytes < 0:
  67. return readBytes
  68. inc(result, batchSize)
  69. result = getRandomImpl(addr dest[result], left)
  70. when defined(js):
  71. import std/private/jsutils
  72. when defined(nodejs):
  73. {.emit: "const _nim_nodejs_crypto = require('crypto');".}
  74. proc randomFillSync(p: Uint8Array) {.importjs: "_nim_nodejs_crypto.randomFillSync(#)".}
  75. template urandomImpl(result: var int, dest: var openArray[byte]) =
  76. let size = dest.len
  77. if size == 0:
  78. return
  79. var src = newUint8Array(size)
  80. randomFillSync(src)
  81. for i in 0 ..< size:
  82. dest[i] = src[i]
  83. else:
  84. proc getRandomValues(p: Uint8Array) {.importjs: "window.crypto.getRandomValues(#)".}
  85. # The requested length of `p` must not be more than 65536.
  86. proc assign(dest: var openArray[byte], src: Uint8Array, base: int, size: int) =
  87. getRandomValues(src)
  88. for j in 0 ..< size:
  89. dest[base + j] = src[j]
  90. template urandomImpl(result: var int, dest: var openArray[byte]) =
  91. let size = dest.len
  92. if size == 0:
  93. return
  94. if size <= batchSize:
  95. var src = newUint8Array(size)
  96. assign(dest, src, 0, size)
  97. return
  98. let
  99. chunks = (size - 1) div batchSize
  100. left = size - chunks * batchSize
  101. var srcArray = newUint8Array(batchSize)
  102. for i in 0 ..< chunks:
  103. assign(dest, srcArray, result, batchSize)
  104. inc(result, batchSize)
  105. var leftArray = newUint8Array(left)
  106. assign(dest, leftArray, result, left)
  107. elif defined(windows):
  108. type
  109. PVOID = pointer
  110. BCRYPT_ALG_HANDLE = PVOID
  111. PUCHAR = ptr cuchar
  112. NTSTATUS = clong
  113. ULONG = culong
  114. const
  115. STATUS_SUCCESS = 0x00000000
  116. BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002
  117. proc bCryptGenRandom(
  118. hAlgorithm: BCRYPT_ALG_HANDLE,
  119. pbBuffer: PUCHAR,
  120. cbBuffer: ULONG,
  121. dwFlags: ULONG
  122. ): NTSTATUS {.stdcall, importc: "BCryptGenRandom", dynlib: "Bcrypt.dll".}
  123. proc randomBytes(pbBuffer: pointer, cbBuffer: Natural): int {.inline.} =
  124. bCryptGenRandom(nil, cast[PUCHAR](pbBuffer), ULONG(cbBuffer),
  125. BCRYPT_USE_SYSTEM_PREFERRED_RNG)
  126. template urandomImpl(result: var int, dest: var openArray[byte]) =
  127. let size = dest.len
  128. if size == 0:
  129. return
  130. result = randomBytes(addr dest[0], size)
  131. elif defined(linux):
  132. # TODO using let, pending bootstrap >= 1.4.0
  133. var SYS_getrandom {.importc: "SYS_getrandom", header: "<sys/syscall.h>".}: clong
  134. const syscallHeader = """#include <unistd.h>
  135. #include <sys/syscall.h>"""
  136. proc syscall(
  137. n: clong, buf: pointer, bufLen: cint, flags: cuint
  138. ): clong {.importc: "syscall", header: syscallHeader.}
  139. # When reading from the urandom source (GRND_RANDOM is not set),
  140. # getrandom() will block until the entropy pool has been
  141. # initialized (unless the GRND_NONBLOCK flag was specified). If a
  142. # request is made to read a large number of bytes (more than 256),
  143. # getrandom() will block until those bytes have been generated and
  144. # transferred from kernel memory to buf.
  145. template urandomImpl(result: var int, dest: var openArray[byte]) =
  146. let size = dest.len
  147. if size == 0:
  148. return
  149. while result < size:
  150. let readBytes = syscall(SYS_getrandom, addr dest[result], cint(size - result), 0).int
  151. if readBytes == 0:
  152. doAssert false
  153. elif readBytes > 0:
  154. inc(result, readBytes)
  155. else:
  156. if osLastError().int in {EINTR, EAGAIN}:
  157. discard
  158. else:
  159. result = -1
  160. break
  161. elif defined(openbsd):
  162. proc getentropy(p: pointer, size: cint): cint {.importc: "getentropy", header: "<unistd.h>".}
  163. # Fills a buffer with high-quality entropy,
  164. # which can be used as input for process-context pseudorandom generators like `arc4random`.
  165. # The maximum buffer size permitted is 256 bytes.
  166. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  167. result = getentropy(p, cint(size)).int
  168. elif defined(freebsd):
  169. type cssize_t {.importc: "ssize_t", header: "<sys/types.h>".} = int
  170. proc getrandom(p: pointer, size: csize_t, flags: cuint): cssize_t {.importc: "getrandom", header: "<sys/random.h>".}
  171. # Upon successful completion, the number of bytes which were actually read
  172. # is returned. For requests larger than 256 bytes, this can be fewer bytes
  173. # than were requested. Otherwise, -1 is returned and the global variable
  174. # errno is set to indicate the error.
  175. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  176. result = getrandom(p, csize_t(size), 0)
  177. elif defined(ios):
  178. {.passL: "-framework Security".}
  179. const errSecSuccess = 0 ## No error.
  180. type
  181. SecRandom {.importc: "struct __SecRandom".} = object
  182. SecRandomRef = ptr SecRandom
  183. ## An abstract Core Foundation-type object containing information about a random number generator.
  184. proc secRandomCopyBytes(
  185. rnd: SecRandomRef, count: csize_t, bytes: pointer
  186. ): cint {.importc: "SecRandomCopyBytes", header: "<Security/SecRandom.h>".}
  187. ## https://developer.apple.com/documentation/security/1399291-secrandomcopybytes
  188. template urandomImpl(result: var int, dest: var openArray[byte]) =
  189. let size = dest.len
  190. if size == 0:
  191. return
  192. result = secRandomCopyBytes(nil, csize_t(size), addr dest[0])
  193. elif defined(macosx):
  194. const sysrandomHeader = """#include <Availability.h>
  195. #include <sys/random.h>
  196. """
  197. proc getentropy(p: pointer, size: csize_t): cint {.importc: "getentropy", header: sysrandomHeader.}
  198. # getentropy() fills a buffer with random data, which can be used as input
  199. # for process-context pseudorandom generators like arc4random(3).
  200. # The maximum buffer size permitted is 256 bytes.
  201. proc getRandomImpl(p: pointer, size: int): int {.inline.} =
  202. result = getentropy(p, csize_t(size)).int
  203. else:
  204. template urandomImpl(result: var int, dest: var openArray[byte]) =
  205. let size = dest.len
  206. if size == 0:
  207. return
  208. # see: https://www.2uo.de/myths-about-urandom/ which justifies using urandom instead of random
  209. let fd = posix.open("/dev/urandom", O_RDONLY)
  210. if fd < 0:
  211. result = -1
  212. else:
  213. try:
  214. var stat: Stat
  215. if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode):
  216. let
  217. chunks = (size - 1) div batchSize
  218. left = size - chunks * batchSize
  219. for i in 0 ..< chunks:
  220. let readBytes = posix.read(fd, addr dest[result], batchSize)
  221. if readBytes < 0:
  222. return readBytes
  223. inc(result, batchSize)
  224. result = posix.read(fd, addr dest[result], left)
  225. else:
  226. result = -1
  227. finally:
  228. discard posix.close(fd)
  229. proc urandomInternalImpl(dest: var openArray[byte]): int {.inline.} =
  230. when batchImplOS:
  231. batchImpl(result, dest, getRandomImpl)
  232. else:
  233. urandomImpl(result, dest)
  234. proc urandom*(dest: var openArray[byte]): bool =
  235. ## Fills `dest` with random bytes suitable for cryptographic use.
  236. ## If the call succeeds, returns `true`.
  237. ##
  238. ## If `dest` is empty, `urandom` immediately returns success,
  239. ## without calling the underlying operating system API.
  240. ##
  241. ## .. warning:: The code hasn't been audited by cryptography experts and
  242. ## is provided as-is without guarantees. Use at your own risks. For production
  243. ## systems we advise you to request an external audit.
  244. result = true
  245. when defined(js): discard urandomInternalImpl(dest)
  246. else:
  247. let ret = urandomInternalImpl(dest)
  248. when defined(windows):
  249. if ret != STATUS_SUCCESS:
  250. result = false
  251. else:
  252. if ret < 0:
  253. result = false
  254. proc urandom*(size: Natural): seq[byte] {.inline.} =
  255. ## Returns random bytes suitable for cryptographic use.
  256. ##
  257. ## .. warning:: The code hasn't been audited by cryptography experts and
  258. ## is provided as-is without guarantees. Use at your own risks. For production
  259. ## systems we advise you to request an external audit.
  260. result = newSeq[byte](size)
  261. when defined(js): discard urandomInternalImpl(result)
  262. else:
  263. if not urandom(result):
  264. raiseOSError(osLastError())