aboutsummaryrefslogtreecommitdiffstats
path: root/src/randombytes.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/randombytes.c')
-rw-r--r--src/randombytes.c262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/randombytes.c b/src/randombytes.c
new file mode 100644
index 0000000..105aa58
--- /dev/null
+++ b/src/randombytes.c
@@ -0,0 +1,262 @@
+// In the case that are compiling on linux, we need to define _GNU_SOURCE
+// *before* randombytes.h is included. Otherwise SYS_getrandom will not be
+// declared.
+#if defined(__linux__)
+# define _GNU_SOURCE
+#endif /* defined(__linux__) */
+
+#include "randombytes.h"
+
+#if defined(_WIN32)
+/* Windows */
+# include <windows.h>
+# include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */
+#endif /* defined(_WIN32) */
+
+
+#if defined(__linux__)
+/* Linux */
+// We would need to include <linux/random.h>, but not every target has access
+// to the linux headers. We only need RNDGETENTCNT, so we instead inline it.
+// RNDGETENTCNT is originally defined in `include/uapi/linux/random.h` in the
+// linux repo.
+# define RNDGETENTCNT 0x80045200
+
+# include <assert.h>
+# include <errno.h>
+# include <fcntl.h>
+# include <poll.h>
+# include <stdint.h>
+# include <sys/ioctl.h>
+# include <sys/stat.h>
+# include <sys/syscall.h>
+# include <sys/types.h>
+# include <unistd.h>
+
+// We need SSIZE_MAX as the maximum read len from /dev/urandom
+# if !defined(SSIZE_MAX)
+# define SSIZE_MAX (SIZE_MAX / 2 - 1)
+# endif /* defined(SSIZE_MAX) */
+
+#endif /* defined(__linux__) */
+
+
+#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+/* Dragonfly, FreeBSD, NetBSD, OpenBSD (has arc4random) */
+# include <sys/param.h>
+# if defined(BSD)
+# include <stdlib.h>
+# endif
+#endif
+
+#if defined(__EMSCRIPTEN__)
+# include <assert.h>
+# include <emscripten.h>
+# include <errno.h>
+# include <stdbool.h>
+#endif /* defined(__EMSCRIPTEN__) */
+
+
+#if defined(_WIN32)
+static int randombytes_win32_randombytes(void* buf, const size_t n)
+{
+ HCRYPTPROV ctx;
+ BOOL tmp;
+
+ tmp = CryptAcquireContext(&ctx, NULL, NULL, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT);
+ if (tmp == FALSE) return -1;
+
+ tmp = CryptGenRandom(ctx, n, (BYTE*) buf);
+ if (tmp == FALSE) return -1;
+
+ tmp = CryptReleaseContext(ctx, 0);
+ if (tmp == FALSE) return -1;
+
+ return 0;
+}
+#endif /* defined(_WIN32) */
+
+
+#if defined(__linux__) && defined(SYS_getrandom)
+static int randombytes_linux_randombytes_getrandom(void *buf, size_t n)
+{
+ /* I have thought about using a separate PRF, seeded by getrandom, but
+ * it turns out that the performance of getrandom is good enough
+ * (250 MB/s on my laptop).
+ */
+ size_t offset = 0, chunk;
+ int ret;
+ while (n > 0) {
+ /* getrandom does not allow chunks larger than 33554431 */
+ chunk = n <= 33554431 ? n : 33554431;
+ do {
+ ret = syscall(SYS_getrandom, (char *)buf + offset, chunk, 0);
+ } while (ret == -1 && errno == EINTR);
+ if (ret < 0) return ret;
+ offset += ret;
+ n -= ret;
+ }
+ assert(n == 0);
+ return 0;
+}
+#endif /* defined(__linux__) && defined(SYS_getrandom) */
+
+
+#if defined(__linux__) && !defined(SYS_getrandom)
+static int randombytes_linux_wait_for_entropy(int device)
+{
+ /* We will block on /dev/random, because any increase in the OS' entropy
+ * level will unblock the request. I use poll here (as does libsodium),
+ * because we don't *actually* want to read from the device. */
+ const int bits = 128;
+ struct pollfd pfd;
+ int fd;
+ int retcode, retcode_error = 0; // Used as return codes throughout this function
+ int entropy = 0;
+
+ /* If the device has enough entropy already, we will want to return early */
+ retcode = ioctl(device, RNDGETENTCNT, &entropy);
+ if (retcode != 0) {
+ // Unrecoverable ioctl error
+ // TODO(dsprenkels) Use `/proc/sys/kernel/random/entropy_avail`
+ return retcode;
+ }
+ if (entropy >= bits) {
+ return 0;
+ }
+
+ do {
+ fd = open("/dev/random", O_RDONLY);
+ } while (fd == -1 && errno == EINTR); /* EAGAIN will not occur */
+ if (fd == -1) {
+ /* Unrecoverable IO error */
+ return -1;
+ }
+
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ for (;;) {
+ retcode = poll(&pfd, 1, -1);
+ if (retcode == -1 && (errno == EINTR || errno == EAGAIN)) {
+ continue;
+ } else if (retcode == 1) {
+ retcode = ioctl(device, RNDGETENTCNT, &entropy);
+ if (retcode != 0) {
+ // Unrecoverable ioctl error
+ retcode_error = retcode;
+ break;
+ }
+ if (entropy >= bits) {
+ break;
+ }
+ } else {
+ // Unreachable: poll() can should only return -1 or 1
+ retcode_error = -1;
+ break;
+ }
+ }
+ do {
+ retcode = close(fd);
+ } while (retcode == -1 && errno == EINTR);
+ if (retcode_error != 0) {
+ return retcode_error;
+ }
+ return retcode;
+}
+
+
+static int randombytes_linux_randombytes_urandom(void *buf, size_t n)
+{
+ int fd;
+ size_t offset = 0, count;
+ ssize_t tmp;
+ do {
+ fd = open("/dev/urandom", O_RDONLY);
+ } while (fd == -1 && errno == EINTR);
+ if (fd == -1) return -1;
+ if (randombytes_linux_wait_for_entropy(fd) == -1) return -1;
+
+ while (n > 0) {
+ count = n <= SSIZE_MAX ? n : SSIZE_MAX;
+ tmp = read(fd, (char *)buf + offset, count);
+ if (tmp == -1 && (errno == EAGAIN || errno == EINTR)) {
+ continue;
+ }
+ if (tmp == -1) return -1; /* Unrecoverable IO error */
+ offset += tmp;
+ n -= tmp;
+ }
+ assert(n == 0);
+ return 0;
+}
+#endif /* defined(__linux__) && !defined(SYS_getrandom) */
+
+
+#if defined(BSD)
+static int randombytes_bsd_randombytes(void *buf, size_t n)
+{
+ arc4random_buf(buf, n);
+ return 0;
+}
+#endif /* defined(BSD) */
+
+
+#if defined(__EMSCRIPTEN__)
+static int randombytes_js_randombytes_nodejs(void *buf, size_t n) {
+ const int ret = EM_ASM_INT({
+ var crypto;
+ try {
+ crypto = require('crypto');
+ } catch (error) {
+ return -2;
+ }
+ try {
+ writeArrayToMemory(crypto.randomBytes($1), $0);
+ return 0;
+ } catch (error) {
+ return -1;
+ }
+ }, buf, n);
+ switch (ret) {
+ case 0:
+ return 0;
+ case -1:
+ errno = EINVAL;
+ return -1;
+ case -2:
+ errno = ENOSYS;
+ return -1;
+ }
+ assert(false); // Unreachable
+}
+#endif /* defined(__EMSCRIPTEN__) */
+
+
+int randombytes(void *buf, size_t n)
+{
+#if defined(__EMSCRIPTEN__)
+# pragma message("Using crypto api from NodeJS")
+ return randombytes_js_randombytes_nodejs(buf, n);
+#elif defined(__linux__)
+# if defined(SYS_getrandom)
+# pragma message("Using getrandom system call")
+ /* Use getrandom system call */
+ return randombytes_linux_randombytes_getrandom(buf, n);
+# else
+# pragma message("Using /dev/urandom device")
+ /* When we have enough entropy, we can read from /dev/urandom */
+ return randombytes_linux_randombytes_urandom(buf, n);
+# endif
+#elif defined(BSD)
+# pragma message("Using arc4random system call")
+ /* Use arc4random system call */
+ return randombytes_bsd_randombytes(buf, n);
+#elif defined(_WIN32)
+# pragma message("Using Windows cryptographic API")
+ /* Use windows API */
+ return randombytes_win32_randombytes(buf, n);
+#else
+# error "randombytes(...) is not supported on this platform"
+#endif
+}