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