summaryrefslogtreecommitdiffstats
path: root/lib/isc/random.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/isc/random.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/lib/isc/random.c b/lib/isc/random.c
new file mode 100644
index 0000000..b79b689
--- /dev/null
+++ b/lib/isc/random.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*%
+ * ChaCha based random number generator derived from OpenBSD.
+ *
+ * The original copyright follows:
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#include <config.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h> /* Required for time(). */
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/mem.h>
+#include <isc/entropy.h>
+#include <isc/random.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#define RNG_MAGIC ISC_MAGIC('R', 'N', 'G', 'x')
+#define VALID_RNG(r) ISC_MAGIC_VALID(r, RNG_MAGIC)
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
+
+#define CHACHA_KEYSIZE 32U
+#define CHACHA_IVSIZE 8U
+#define CHACHA_BLOCKSIZE 64
+#define CHACHA_BUFFERSIZE (16 * CHACHA_BLOCKSIZE)
+
+/* ChaCha RNG state */
+struct isc_rng {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ chacha_ctx cpctx;
+ uint8_t buffer[CHACHA_BUFFERSIZE];
+ size_t have;
+ unsigned int references;
+ int count;
+ isc_entropy_t *entropy; /*%< entropy source */
+ isc_mutex_t lock;
+};
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static void
+initialize_rand(void) {
+#ifndef HAVE_ARC4RANDOM
+ unsigned int pid = getpid();
+
+ /*
+ * The low bits of pid generally change faster.
+ * Xor them with the high bits of time which change slowly.
+ */
+ pid = ((pid << 16) & 0xffff0000) | ((pid >> 16) & 0xffff);
+
+ srand((unsigned)time(NULL) ^ pid);
+#endif
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_rand) == ISC_R_SUCCESS);
+}
+
+void
+isc_random_seed(uint32_t seed) {
+ initialize();
+
+#ifndef HAVE_ARC4RANDOM
+ srand(seed);
+#elif defined(HAVE_ARC4RANDOM_STIR)
+ /* Formally not necessary... */
+ UNUSED(seed);
+ arc4random_stir();
+#elif defined(HAVE_ARC4RANDOM_ADDRANDOM)
+ arc4random_addrandom((u_char *) &seed, sizeof(uint32_t));
+#else
+ /*
+ * If arc4random() is available and no corresponding seeding
+ * function arc4random_addrandom() is available, no seeding is
+ * done on such platforms (e.g., OpenBSD 5.5). This is because
+ * the OS itself is supposed to seed the RNG and it is assumed
+ * that no explicit seeding is required.
+ */
+ UNUSED(seed);
+#endif
+}
+
+void
+isc_random_get(uint32_t *val) {
+ REQUIRE(val != NULL);
+
+ initialize();
+
+#ifndef HAVE_ARC4RANDOM
+ /*
+ * rand()'s lower bits are not random.
+ * rand()'s upper bit is zero.
+ */
+#if RAND_MAX >= 0xfffff
+ /* We have at least 20 bits. Use lower 16 excluding lower most 4 */
+ *val = ((((unsigned int)rand()) & 0xffff0) >> 4) |
+ ((((unsigned int)rand()) & 0xffff0) << 12);
+#elif RAND_MAX >= 0x7fff
+ /* We have at least 15 bits. Use lower 10/11 excluding lower most 4 */
+ *val = ((rand() >> 4) & 0x000007ff) | ((rand() << 7) & 0x003ff800) |
+ ((rand() << 18) & 0xffc00000);
+#else
+#error RAND_MAX is too small
+#endif
+#else
+ *val = arc4random();
+#endif
+}
+
+uint32_t
+isc_random_jitter(uint32_t max, uint32_t jitter) {
+ uint32_t rnd;
+
+ REQUIRE(jitter < max || (jitter == 0 && max == 0));
+
+ if (jitter == 0)
+ return (max);
+
+ isc_random_get(&rnd);
+ return (max - rnd % jitter);
+}
+
+static void
+chacha_reinit(isc_rng_t *rng, uint8_t *buffer, size_t n) {
+ REQUIRE(rng != NULL);
+
+ if (n < CHACHA_KEYSIZE + CHACHA_IVSIZE)
+ return;
+
+ chacha_keysetup(&rng->cpctx, buffer, CHACHA_KEYSIZE * 8, 0);
+ chacha_ivsetup(&rng->cpctx, buffer + CHACHA_KEYSIZE);
+}
+
+isc_result_t
+isc_rng_create(isc_mem_t *mctx, isc_entropy_t *entropy, isc_rng_t **rngp) {
+ union {
+ unsigned char rnd[128];
+ uint32_t rnd32[32];
+ } rnd;
+ isc_result_t result;
+ isc_rng_t *rng;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(rngp != NULL && *rngp == NULL);
+
+ if (entropy != NULL) {
+ /*
+ * We accept any quality of random data to avoid blocking.
+ */
+ result = isc_entropy_getdata(entropy, rnd.rnd,
+ sizeof(rnd), NULL, 0);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ } else {
+ int i;
+ for (i = 0; i < 32; i++)
+ isc_random_get(&rnd.rnd32[i]);
+ }
+
+ rng = isc_mem_get(mctx, sizeof(*rng));
+ if (rng == NULL)
+ return (ISC_R_NOMEMORY);
+
+ chacha_reinit(rng, rnd.rnd, sizeof(rnd.rnd));
+
+ rng->have = 0;
+ memset(rng->buffer, 0, CHACHA_BUFFERSIZE);
+
+ /* Create lock */
+ result = isc_mutex_init(&rng->lock);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, rng, sizeof(*rng));
+ return (result);
+ }
+
+ /* Attach to memory context */
+ rng->mctx = NULL;
+ isc_mem_attach(mctx, &rng->mctx);
+
+ /* Local non-algorithm initializations. */
+ rng->count = 0;
+ rng->entropy = entropy; /* don't have to attach */
+ rng->references = 1;
+ rng->magic = RNG_MAGIC;
+
+ *rngp = rng;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_rng_attach(isc_rng_t *source, isc_rng_t **targetp) {
+
+ REQUIRE(VALID_RNG(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ LOCK(&source->lock);
+ source->references++;
+ UNLOCK(&source->lock);
+
+ *targetp = (isc_rng_t *)source;
+}
+
+static void
+destroy(isc_rng_t *rng) {
+
+ REQUIRE(VALID_RNG(rng));
+
+ rng->magic = 0;
+ isc_mutex_destroy(&rng->lock);
+ isc_mem_putanddetach(&rng->mctx, rng, sizeof(isc_rng_t));
+}
+
+void
+isc_rng_detach(isc_rng_t **rngp) {
+ isc_rng_t *rng;
+ bool dest = false;
+
+ REQUIRE(rngp != NULL && VALID_RNG(*rngp));
+
+ rng = *rngp;
+ *rngp = NULL;
+
+ LOCK(&rng->lock);
+
+ INSIST(rng->references > 0);
+ rng->references--;
+ if (rng->references == 0)
+ dest = true;
+ UNLOCK(&rng->lock);
+
+ if (dest)
+ destroy(rng);
+}
+
+static void
+chacha_rekey(isc_rng_t *rng, u_char *dat, size_t datlen) {
+ REQUIRE(VALID_RNG(rng));
+
+#ifndef KEYSTREAM_ONLY
+ memset(rng->buffer, 0, CHACHA_BUFFERSIZE);
+#endif
+
+ /* Fill buffer with the keystream. */
+ chacha_encrypt_bytes(&rng->cpctx, rng->buffer, rng->buffer,
+ CHACHA_BUFFERSIZE);
+
+ /* Mix in optional user provided data. */
+ if (dat != NULL) {
+ size_t i, m;
+
+ m = ISC_MIN(datlen, CHACHA_KEYSIZE + CHACHA_IVSIZE);
+ for (i = 0; i < m; i++)
+ rng->buffer[i] ^= dat[i];
+ }
+
+ /* Immediately reinit for backtracking resistance. */
+ chacha_reinit(rng, rng->buffer,
+ CHACHA_KEYSIZE + CHACHA_IVSIZE);
+ memset(rng->buffer, 0, CHACHA_KEYSIZE + CHACHA_IVSIZE);
+ rng->have = CHACHA_BUFFERSIZE - CHACHA_KEYSIZE - CHACHA_IVSIZE;
+}
+
+static inline uint16_t
+chacha_getuint16(isc_rng_t *rng) {
+ uint16_t val;
+
+ REQUIRE(VALID_RNG(rng));
+
+ if (rng->have < sizeof(val))
+ chacha_rekey(rng, NULL, 0);
+
+ memmove(&val, rng->buffer + CHACHA_BUFFERSIZE - rng->have,
+ sizeof(val));
+ /* Clear the copied region. */
+ memset(rng->buffer + CHACHA_BUFFERSIZE - rng->have,
+ 0, sizeof(val));
+ rng->have -= sizeof(val);
+
+ return (val);
+}
+
+static void
+chacha_stir(isc_rng_t *rng) {
+ union {
+ unsigned char rnd[128];
+ uint32_t rnd32[32];
+ } rnd;
+ isc_result_t result;
+
+ REQUIRE(VALID_RNG(rng));
+
+ if (rng->entropy != NULL) {
+ /*
+ * We accept any quality of random data to avoid blocking.
+ */
+ result = isc_entropy_getdata(rng->entropy, rnd.rnd,
+ sizeof(rnd), NULL, 0);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ } else {
+ int i;
+ for (i = 0; i < 32; i++)
+ isc_random_get(&rnd.rnd32[i]);
+ }
+
+ chacha_rekey(rng, rnd.rnd, sizeof(rnd.rnd));
+
+ isc_safe_memwipe(rnd.rnd, sizeof(rnd.rnd));
+
+ /* Invalidate the buffer too. */
+ rng->have = 0;
+ memset(rng->buffer, 0, CHACHA_BUFFERSIZE);
+
+ /*
+ * Derived from OpenBSD's implementation. The rationale is not clear,
+ * but should be conservative enough in safety, and reasonably large
+ * for efficiency.
+ */
+ rng->count = 1600000;
+}
+
+uint16_t
+isc_rng_random(isc_rng_t *rng) {
+ uint16_t result;
+
+ REQUIRE(VALID_RNG(rng));
+
+ LOCK(&rng->lock);
+
+ rng->count -= sizeof(uint16_t);
+ if (rng->count <= 0)
+ chacha_stir(rng);
+ result = chacha_getuint16(rng);
+
+ UNLOCK(&rng->lock);
+
+ return (result);
+}
+
+uint16_t
+isc_rng_uniformrandom(isc_rng_t *rng, uint16_t upper_bound) {
+ uint16_t min, r;
+
+ REQUIRE(VALID_RNG(rng));
+
+ if (upper_bound < 2)
+ return (0);
+
+ /*
+ * Ensure the range of random numbers [min, 0xffff] be a multiple of
+ * upper_bound and contain at least a half of the 16 bit range.
+ */
+
+ if (upper_bound > 0x8000)
+ min = 1 + ~upper_bound; /* 0x8000 - upper_bound */
+ else
+ min = (uint16_t)(0x10000 % (uint32_t)upper_bound);
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = isc_rng_random(rng);
+ if (r >= min)
+ break;
+ }
+
+ return (r % upper_bound);
+}