選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

326 行
9.4 KiB

  1. /*
  2. The MIT License
  3. Copyright (c) 2017 Daan Sprenkels <hello@dsprenkels.com>
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. // In the case that are compiling on linux, we need to define _GNU_SOURCE
  21. // *before* randombytes.h is included. Otherwise SYS_getrandom will not be
  22. // declared.
  23. #if defined(__linux__)
  24. #define _GNU_SOURCE
  25. #endif /* defined(__linux__) */
  26. #include "randombytes.h"
  27. #if defined(_WIN32)
  28. /* Windows */
  29. // NOLINTNEXTLINE(llvm-include-order): Include order required by Windows
  30. #include <windows.h>
  31. #include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */
  32. #endif /* defined(_WIN32) */
  33. #if defined(__linux__)
  34. /* Linux */
  35. // We would need to include <linux/random.h>, but not every target has access
  36. // to the linux headers. We only need RNDGETENTCNT, so we instead inline it.
  37. // RNDGETENTCNT is originally defined in `include/uapi/linux/random.h` in the
  38. // linux repo.
  39. #define RNDGETENTCNT 0x80045200
  40. #include <assert.h>
  41. #include <errno.h>
  42. #include <fcntl.h>
  43. #include <poll.h>
  44. #include <stdint.h>
  45. #include <stdio.h>
  46. #include <sys/ioctl.h>
  47. #include <sys/stat.h>
  48. #include <sys/syscall.h>
  49. #include <sys/types.h>
  50. #include <unistd.h>
  51. // We need SSIZE_MAX as the maximum read len from /dev/urandom
  52. #if !defined(SSIZE_MAX)
  53. #define SSIZE_MAX (SIZE_MAX / 2 - 1)
  54. #endif /* defined(SSIZE_MAX) */
  55. #endif /* defined(__linux__) */
  56. #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
  57. /* Dragonfly, FreeBSD, NetBSD, OpenBSD (has arc4random) */
  58. #include <sys/param.h>
  59. #if defined(BSD)
  60. #include <stdlib.h>
  61. #endif
  62. #endif
  63. #if defined(__EMSCRIPTEN__)
  64. #include <assert.h>
  65. #include <emscripten.h>
  66. #include <errno.h>
  67. #include <stdbool.h>
  68. #endif /* defined(__EMSCRIPTEN__) */
  69. #if defined(_WIN32)
  70. static int randombytes_win32_randombytes(void *buf, const size_t n) {
  71. HCRYPTPROV ctx;
  72. BOOL tmp;
  73. tmp = CryptAcquireContext(&ctx, NULL, NULL, PROV_RSA_FULL,
  74. CRYPT_VERIFYCONTEXT);
  75. if (tmp == FALSE) {
  76. return -1;
  77. }
  78. tmp = CryptGenRandom(ctx, (DWORD)n, (BYTE *)buf);
  79. if (tmp == FALSE) {
  80. return -1;
  81. }
  82. tmp = CryptReleaseContext(ctx, 0);
  83. if (tmp == FALSE) {
  84. return -1;
  85. }
  86. return 0;
  87. }
  88. #endif /* defined(_WIN32) */
  89. #if defined(__linux__) && defined(SYS_getrandom)
  90. static int randombytes_linux_randombytes_getrandom(void *buf, size_t n) {
  91. /* I have thought about using a separate PRF, seeded by getrandom, but
  92. * it turns out that the performance of getrandom is good enough
  93. * (250 MB/s on my laptop).
  94. */
  95. size_t offset = 0, chunk;
  96. long int ret;
  97. while (n > 0) {
  98. /* getrandom does not allow chunks larger than 33554431 */
  99. chunk = n <= 33554431 ? n : 33554431;
  100. do {
  101. ret = syscall(SYS_getrandom, (char *)buf + offset, chunk, 0);
  102. } while (ret == -1 && errno == EINTR);
  103. if (ret < 0) {
  104. return (int) ret;
  105. }
  106. offset += (size_t) ret;
  107. n -= (size_t) ret;
  108. }
  109. assert(n == 0);
  110. return 0;
  111. }
  112. #endif /* defined(__linux__) && defined(SYS_getrandom) */
  113. #if defined(__linux__) && !defined(SYS_getrandom)
  114. static int randombytes_linux_read_entropy_ioctl(int device, int *entropy) {
  115. return ioctl(device, RNDGETENTCNT, entropy);
  116. }
  117. static int randombytes_linux_read_entropy_proc(FILE *stream, int *entropy) {
  118. int retcode;
  119. do {
  120. rewind(stream);
  121. retcode = fscanf(stream, "%d", entropy);
  122. } while (retcode != 1 && errno == EINTR);
  123. if (retcode != 1) {
  124. return -1;
  125. }
  126. return 0;
  127. }
  128. static int randombytes_linux_wait_for_entropy(int device) {
  129. /* We will block on /dev/random, because any increase in the OS' entropy
  130. * level will unblock the request. I use poll here (as does libsodium),
  131. * because we don't *actually* want to read from the device. */
  132. enum { IOCTL,
  133. PROC
  134. } strategy = IOCTL;
  135. const int bits = 128;
  136. struct pollfd pfd;
  137. int fd;
  138. FILE *proc_file;
  139. int retcode,
  140. retcode_error = 0; // Used as return codes throughout this function
  141. int entropy = 0;
  142. /* If the device has enough entropy already, we will want to return early */
  143. retcode = randombytes_linux_read_entropy_ioctl(device, &entropy);
  144. if (retcode != 0 && errno == ENOTTY) {
  145. /* The ioctl call on /dev/urandom has failed due to a ENOTTY (i.e.
  146. * unsupported action). We will fall back to reading from
  147. * `/proc/sys/kernel/random/entropy_avail`. This is obviously less
  148. * ideal, but at this point it seems we have no better option. */
  149. strategy = PROC;
  150. // Open the entropy count file
  151. proc_file = fopen("/proc/sys/kernel/random/entropy_avail", "r");
  152. } else if (retcode != 0) {
  153. // Unrecoverable ioctl error
  154. return -1;
  155. }
  156. if (entropy >= bits) {
  157. return 0;
  158. }
  159. do {
  160. fd = open("/dev/random", O_RDONLY);
  161. } while (fd == -1 && errno == EINTR); /* EAGAIN will not occur */
  162. if (fd == -1) {
  163. /* Unrecoverable IO error */
  164. return -1;
  165. }
  166. pfd.fd = fd;
  167. pfd.events = POLLIN;
  168. for (;;) {
  169. retcode = poll(&pfd, 1, -1);
  170. if (retcode == -1 && (errno == EINTR || errno == EAGAIN)) {
  171. continue;
  172. } else if (retcode == 1) {
  173. if (strategy == IOCTL) {
  174. retcode =
  175. randombytes_linux_read_entropy_ioctl(device, &entropy);
  176. } else if (strategy == PROC) {
  177. retcode =
  178. randombytes_linux_read_entropy_proc(proc_file, &entropy);
  179. } else {
  180. return -1; // Unreachable
  181. }
  182. if (retcode != 0) {
  183. // Unrecoverable I/O error
  184. retcode_error = retcode;
  185. break;
  186. }
  187. if (entropy >= bits) {
  188. break;
  189. }
  190. } else {
  191. // Unreachable: poll() should only return -1 or 1
  192. retcode_error = -1;
  193. break;
  194. }
  195. }
  196. do {
  197. retcode = close(fd);
  198. } while (retcode == -1 && errno == EINTR);
  199. if (strategy == PROC) {
  200. do {
  201. retcode = fclose(proc_file);
  202. } while (retcode == -1 && errno == EINTR);
  203. }
  204. if (retcode_error != 0) {
  205. return retcode_error;
  206. }
  207. return retcode;
  208. }
  209. static int randombytes_linux_randombytes_urandom(void *buf, size_t n) {
  210. int fd;
  211. size_t offset = 0, count;
  212. ssize_t tmp;
  213. do {
  214. fd = open("/dev/urandom", O_RDONLY);
  215. } while (fd == -1 && errno == EINTR);
  216. if (fd == -1) {
  217. return -1;
  218. }
  219. if (randombytes_linux_wait_for_entropy(fd) == -1) {
  220. return -1;
  221. }
  222. while (n > 0) {
  223. count = n <= SSIZE_MAX ? n : SSIZE_MAX;
  224. tmp = read(fd, (char *)buf + offset, count);
  225. if (tmp == -1 && (errno == EAGAIN || errno == EINTR)) {
  226. continue;
  227. }
  228. if (tmp == -1) {
  229. return -1; /* Unrecoverable IO error */
  230. }
  231. offset += tmp;
  232. n -= tmp;
  233. }
  234. assert(n == 0);
  235. return 0;
  236. }
  237. #endif /* defined(__linux__) && !defined(SYS_getrandom) */
  238. #if defined(BSD)
  239. static int randombytes_bsd_randombytes(void *buf, size_t n) {
  240. arc4random_buf(buf, n);
  241. return 0;
  242. }
  243. #endif /* defined(BSD) */
  244. #if defined(__EMSCRIPTEN__)
  245. static int randombytes_js_randombytes_nodejs(void *buf, size_t n) {
  246. const int ret = EM_ASM_INT({
  247. var crypto;
  248. try {
  249. crypto = require('crypto');
  250. } catch (error) {
  251. return -2;
  252. }
  253. try {
  254. writeArrayToMemory(crypto.randomBytes($1), $0);
  255. return 0;
  256. } catch (error) {
  257. return -1;
  258. }
  259. },
  260. buf, n);
  261. switch (ret) {
  262. case 0:
  263. return 0;
  264. case -1:
  265. errno = EINVAL;
  266. return -1;
  267. case -2:
  268. errno = ENOSYS;
  269. return -1;
  270. }
  271. assert(false); // Unreachable
  272. }
  273. #endif /* defined(__EMSCRIPTEN__) */
  274. int randombytes(uint8_t *buf, size_t n) {
  275. #if defined(__EMSCRIPTEN__)
  276. return randombytes_js_randombytes_nodejs(buf, n);
  277. #elif defined(__linux__)
  278. #if defined(SYS_getrandom)
  279. /* Use getrandom system call */
  280. return randombytes_linux_randombytes_getrandom(buf, n);
  281. #else
  282. /* When we have enough entropy, we can read from /dev/urandom */
  283. return randombytes_linux_randombytes_urandom(buf, n);
  284. #endif
  285. #elif defined(BSD)
  286. /* Use arc4random system call */
  287. return randombytes_bsd_randombytes(buf, n);
  288. #elif defined(_WIN32)
  289. /* Use windows API */
  290. return randombytes_win32_randombytes(buf, n);
  291. #else
  292. #error "randombytes(...) is not supported on this platform"
  293. #endif
  294. }