diff options
Diffstat (limited to '')
-rw-r--r-- | libc-top-half/sources/arc4random.c | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/libc-top-half/sources/arc4random.c b/libc-top-half/sources/arc4random.c new file mode 100644 index 0000000..9898206 --- /dev/null +++ b/libc-top-half/sources/arc4random.c @@ -0,0 +1,160 @@ +#include <assert.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#define RNG_RESERVE_LEN 512 + +#define CHACHA20_KEYBYTES 32 +#define CHACHA20_BLOCKBYTES 64 + +#define ROTL32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b)))) + +#define CHACHA20_QUARTERROUND(A, B, C, D) \ + A += B; \ + D = ROTL32(D ^ A, 16); \ + C += D; \ + B = ROTL32(B ^ C, 12); \ + A += B; \ + D = ROTL32(D ^ A, 8); \ + C += D; \ + B = ROTL32(B ^ C, 7) + +static void CHACHA20_ROUNDS(uint32_t st[16]) +{ + int i; + + for (i = 0; i < 20; i += 2) { + CHACHA20_QUARTERROUND(st[0], st[4], st[8], st[12]); + CHACHA20_QUARTERROUND(st[1], st[5], st[9], st[13]); + CHACHA20_QUARTERROUND(st[2], st[6], st[10], st[14]); + CHACHA20_QUARTERROUND(st[3], st[7], st[11], st[15]); + CHACHA20_QUARTERROUND(st[0], st[5], st[10], st[15]); + CHACHA20_QUARTERROUND(st[1], st[6], st[11], st[12]); + CHACHA20_QUARTERROUND(st[2], st[7], st[8], st[13]); + CHACHA20_QUARTERROUND(st[3], st[4], st[9], st[14]); + } +} + +static void chacha20_update(uint8_t out[CHACHA20_BLOCKBYTES], uint32_t st[16]) +{ + uint32_t ks[16]; + int i; + + memcpy(ks, st, 4 * 16); + CHACHA20_ROUNDS(st); + for (i = 0; i < 16; i++) { + ks[i] += st[i]; + } + memcpy(out, ks, CHACHA20_BLOCKBYTES); + st[12]++; +} + +static void chacha20_init(uint32_t st[16], const uint8_t key[CHACHA20_KEYBYTES]) +{ + static const uint32_t constants[4] = { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 }; + memcpy(&st[0], constants, 4 * 4); + memcpy(&st[4], key, CHACHA20_KEYBYTES); + memset(&st[12], 0, 4 * 4); +} + +static int chacha20_rng(uint8_t* out, size_t len, uint8_t key[CHACHA20_KEYBYTES]) +{ + uint32_t st[16]; + size_t off; + + chacha20_init(st, key); + chacha20_update(&out[0], st); + memcpy(key, out, CHACHA20_KEYBYTES); + off = 0; + while (len >= CHACHA20_BLOCKBYTES) { + chacha20_update(&out[off], st); + len -= CHACHA20_BLOCKBYTES; + off += CHACHA20_BLOCKBYTES; + } + if (len > 0) { + uint8_t tmp[CHACHA20_BLOCKBYTES]; + chacha20_update(tmp, st); + memcpy(&out[off], tmp, len); + } + return 0; +} + +struct rng_state { + int initialized; + size_t off; + uint8_t key[CHACHA20_KEYBYTES]; + uint8_t reserve[RNG_RESERVE_LEN]; +}; + +void arc4random_buf(void* buffer, size_t len) +{ + static _Thread_local struct rng_state rng_state; + + unsigned char* buffer_ = (unsigned char*)buffer; + size_t off; + size_t remaining; + size_t partial; + + if (!rng_state.initialized) { + if (getentropy(rng_state.key, sizeof rng_state.key) != 0) { + assert(0); + } + rng_state.off = RNG_RESERVE_LEN; + rng_state.initialized = 1; + } + off = 0; + remaining = len; + while (remaining > 0) { + if (rng_state.off == RNG_RESERVE_LEN) { + while (remaining >= RNG_RESERVE_LEN) { + chacha20_rng(&buffer_[off], RNG_RESERVE_LEN, rng_state.key); + off += RNG_RESERVE_LEN; + remaining -= RNG_RESERVE_LEN; + } + if (remaining == 0) { + break; + } + chacha20_rng(&rng_state.reserve[0], RNG_RESERVE_LEN, rng_state.key); + rng_state.off = 0; + } + partial = RNG_RESERVE_LEN - rng_state.off; + if (remaining < partial) { + partial = remaining; + } + memcpy(&buffer_[off], &rng_state.reserve[rng_state.off], partial); + memset(&rng_state.reserve[rng_state.off], 0, partial); + rng_state.off += partial; + remaining -= partial; + off += partial; + } +} + +uint32_t arc4random(void) +{ + uint32_t v; + + arc4random_buf(&v, sizeof v); + + return v; +} + +uint32_t arc4random_uniform(const uint32_t upper_bound) +{ + uint32_t min; + uint32_t r; + + if (upper_bound < 2U) { + return 0; + } + min = (1U + ~upper_bound) % upper_bound; // = 2**32 mod upper_bound + do { + r = arc4random(); + } while (r < min); + + // r is now clamped to a set whose size mod upper_bound == 0. + // The worst case (2**31+1) requires 2 attempts on average. + + return r % upper_bound; +} |