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