diff --git a/CHANGES b/CHANGES index c8ac23797a..36d398f228 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +3870. [func] Updated the random number generator used in + the resolver to use the updated ChaCha based one + (similar to OpenBSD's changes). Also moved the + RNG to libisc and added unit tests for it. + [RT #35942] + 3869. [doc] Document that in-view zones cannot be used for response policy zones. [RT #35941] diff --git a/configure b/configure index a4015796cf..c14d3decd7 100755 --- a/configure +++ b/configure @@ -20269,6 +20269,51 @@ $as_echo "#define ATF_TEST 1" >>confdefs.h STD_CINCLUDES="$STD_CINCLUDES -I$atf/include" ATFBIN="$atf/bin" ATFLIBS="-L$atf/lib -latf-c" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for exp in -lm" >&5 +$as_echo_n "checking for exp in -lm... " >&6; } +if ${ac_cv_lib_m_exp+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lm $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exp (); +int +main () +{ +return exp (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_m_exp=yes +else + ac_cv_lib_m_exp=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_exp" >&5 +$as_echo "$ac_cv_lib_m_exp" >&6; } +if test "x$ac_cv_lib_m_exp" = xyes; then : + libm=yes +else + libm=no +fi + + if test "$libm" = "yes"; then + ATFLIBS="$ATFLIBS -lm" + fi UNITTESTS=tests fi diff --git a/configure.in b/configure.in index 01e0f8b2d8..050943ecd6 100644 --- a/configure.in +++ b/configure.in @@ -4000,6 +4000,10 @@ if test "$atf" != no; then STD_CINCLUDES="$STD_CINCLUDES -I$atf/include" ATFBIN="$atf/bin" ATFLIBS="-L$atf/lib -latf-c" + AC_CHECK_LIB(m, exp, libm=yes, libm=no) + if test "$libm" = "yes"; then + ATFLIBS="$ATFLIBS -lm" + fi UNITTESTS=tests fi AC_SUBST(ATFBIN) diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index c687d87723..bae423eec3 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -98,7 +98,7 @@ DSTSRCS = @DST_EXTRA_SRCS@ @OPENSSLLINKSRCS@ @PKCS11LINKSRCS@ \ dst_result.c gssapi_link.c gssapictx.c \ hmac_link.c key.c -GEOIOLINKSRCS = geoip.c +GEOIPLINKSRCS = geoip.c DNSSRCS = acache.c acl.c adb.c byaddr.c \ cache.c callbacks.c clientinfo.c compress.c \ diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c index e510dc27d8..b1d93788d1 100644 --- a/lib/dns/dispatch.c +++ b/lib/dns/dispatch.c @@ -57,16 +57,6 @@ typedef ISC_LIST(dispsocket_t) dispsocketlist_t; typedef struct dispportentry dispportentry_t; typedef ISC_LIST(dispportentry_t) dispportlist_t; -/* ARC4 Random generator state */ -typedef struct arc4ctx { - isc_uint8_t i; - isc_uint8_t j; - isc_uint8_t s[256]; - int count; - isc_entropy_t *entropy; /*%< entropy source for ARC4 */ - isc_mutex_t *lock; -} arc4ctx_t; - typedef struct dns_qid { unsigned int magic; unsigned int qid_nbuckets; /*%< hash table size */ @@ -90,11 +80,11 @@ struct dns_dispatchmgr { unsigned int state; ISC_LIST(dns_dispatch_t) list; - /* Locked by arc4_lock. */ - isc_mutex_t arc4_lock; - arc4ctx_t arc4ctx; /*%< ARC4 context for QID */ + /* Locked by rng_lock. */ + isc_mutex_t rng_lock; + isc_rng_t *rngctx; /*%< RNG context for QID */ - /* locked by buffer lock */ + /* locked by buffer_lock */ dns_qid_t *qid; isc_mutex_t buffer_lock; unsigned int buffers; /*%< allocated buffers */ @@ -257,7 +247,7 @@ struct dns_dispatch { unsigned int tcpbuffers; /*%< allocated buffers */ dns_tcpmsg_t tcpmsg; /*%< for tcp streams */ dns_qid_t *qid; - arc4ctx_t arc4ctx; /*%< for QID/UDP port num */ + isc_rng_t *rngctx; /*%< for QID/UDP port num */ dispportlist_t *port_table; /*%< hold ports 'owned' by us */ isc_mempool_t *portpool; /*%< port table entries */ }; @@ -279,8 +269,8 @@ struct dns_dispatch { #define DNS_QID(disp) ((disp)->socktype == isc_sockettype_tcp) ? \ (disp)->qid : (disp)->mgr->qid -#define DISP_ARC4CTX(disp) ((disp)->socktype == isc_sockettype_udp) ? \ - (&(disp)->arc4ctx) : (&(disp)->mgr->arc4ctx) +#define DISP_RNGCTX(disp) ((disp)->socktype == isc_sockettype_udp) ? \ + ((disp)->rngctx) : ((disp)->mgr->rngctx) /*% * Locking a query port buffer is a bit tricky. We access the buffer without @@ -433,169 +423,6 @@ request_log(dns_dispatch_t *disp, dns_dispentry_t *resp, } } -/*% - * ARC4 random number generator derived from OpenBSD. - * Only dispatch_random() and dispatch_uniformrandom() are expected - * to be called from general dispatch routines; the rest of them are subroutines - * for these two. - * - * The original copyright follows: - * Copyright (c) 1996, David Mazieres - * Copyright (c) 2008, Damien Miller - * - * 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. - */ -static void -dispatch_initrandom(arc4ctx_t *actx, isc_entropy_t *entropy, - isc_mutex_t *lock) -{ - int n; - for (n = 0; n < 256; n++) - actx->s[n] = n; - actx->i = 0; - actx->j = 0; - actx->count = 0; - actx->entropy = entropy; /* don't have to attach */ - actx->lock = lock; -} - -static void -dispatch_arc4addrandom(arc4ctx_t *actx, unsigned char *dat, int datlen) { - int n; - isc_uint8_t si; - - actx->i--; - for (n = 0; n < 256; n++) { - actx->i = (actx->i + 1); - si = actx->s[actx->i]; - actx->j = (actx->j + si + dat[n % datlen]); - actx->s[actx->i] = actx->s[actx->j]; - actx->s[actx->j] = si; - } - actx->j = actx->i; -} - -static inline isc_uint8_t -dispatch_arc4get8(arc4ctx_t *actx) { - isc_uint8_t si, sj; - - actx->i = (actx->i + 1); - si = actx->s[actx->i]; - actx->j = (actx->j + si); - sj = actx->s[actx->j]; - actx->s[actx->i] = sj; - actx->s[actx->j] = si; - - return (actx->s[(si + sj) & 0xff]); -} - -static inline isc_uint16_t -dispatch_arc4get16(arc4ctx_t *actx) { - isc_uint16_t val; - - val = dispatch_arc4get8(actx) << 8; - val |= dispatch_arc4get8(actx); - - return (val); -} - -static void -dispatch_arc4stir(arc4ctx_t *actx) { - int i; - union { - unsigned char rnd[128]; - isc_uint32_t rnd32[32]; - } rnd; - isc_result_t result; - - if (actx->entropy != NULL) { - /* - * We accept any quality of random data to avoid blocking. - */ - result = isc_entropy_getdata(actx->entropy, rnd.rnd, - sizeof(rnd), NULL, 0); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - } else { - for (i = 0; i < 32; i++) - isc_random_get(&rnd.rnd32[i]); - } - dispatch_arc4addrandom(actx, rnd.rnd, sizeof(rnd.rnd)); - - /* - * Discard early keystream, as per recommendations in: - * http://www.wisdom.weizmann.ac.il/~itsik/RC4/Papers/Rc4_ksa.ps - */ - for (i = 0; i < 256; i++) - (void)dispatch_arc4get8(actx); - - /* - * Derived from OpenBSD's implementation. The rationale is not clear, - * but should be conservative enough in safety, and reasonably large - * for efficiency. - */ - actx->count = 1600000; -} - -static isc_uint16_t -dispatch_random(arc4ctx_t *actx) { - isc_uint16_t result; - - if (actx->lock != NULL) - LOCK(actx->lock); - - actx->count -= sizeof(isc_uint16_t); - if (actx->count <= 0) - dispatch_arc4stir(actx); - result = dispatch_arc4get16(actx); - - if (actx->lock != NULL) - UNLOCK(actx->lock); - - return (result); -} - -static isc_uint16_t -dispatch_uniformrandom(arc4ctx_t *actx, isc_uint16_t upper_bound) { - isc_uint16_t min, r; - - 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 = (isc_uint16_t)(0x10000 % (isc_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 = dispatch_random(actx); - if (r >= min) - break; - } - - return (r % upper_bound); -} - /* * Return a hash of the destination and message id. */ @@ -898,8 +725,7 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest, qid = DNS_QID(disp); for (i = 0; i < 64; i++) { - port = ports[dispatch_uniformrandom(DISP_ARC4CTX(disp), - nports)]; + port = ports[isc_rng_uniformrandom(DISP_RNGCTX(disp), nports)]; isc_sockaddr_setport(&localaddr, port); LOCK(&qid->lock); @@ -1817,7 +1643,9 @@ destroy_mgr(dns_dispatchmgr_t **mgrp) { DESTROYLOCK(&mgr->lock); mgr->state = 0; - DESTROYLOCK(&mgr->arc4_lock); + if (mgr->rngctx != NULL) + isc_rng_detach(&mgr->rngctx); + DESTROYLOCK(&mgr->rng_lock); isc_mempool_destroy(&mgr->depool); isc_mempool_destroy(&mgr->rpool); @@ -1948,18 +1776,19 @@ dns_dispatchmgr_create(isc_mem_t *mctx, isc_entropy_t *entropy, mgr->blackhole = NULL; mgr->stats = NULL; + mgr->rngctx = NULL; result = isc_mutex_init(&mgr->lock); if (result != ISC_R_SUCCESS) goto deallocate; - result = isc_mutex_init(&mgr->arc4_lock); + result = isc_mutex_init(&mgr->rng_lock); if (result != ISC_R_SUCCESS) goto kill_lock; result = isc_mutex_init(&mgr->buffer_lock); if (result != ISC_R_SUCCESS) - goto kill_arc4_lock; + goto kill_rng_lock; result = isc_mutex_init(&mgr->depool_lock); if (result != ISC_R_SUCCESS) @@ -2054,7 +1883,9 @@ dns_dispatchmgr_create(isc_mem_t *mctx, isc_entropy_t *entropy, if (entropy != NULL) isc_entropy_attach(entropy, &mgr->entropy); - dispatch_initrandom(&mgr->arc4ctx, mgr->entropy, &mgr->arc4_lock); + result = isc_rng_create(mctx, mgr->entropy, &mgr->rngctx); + if (result != ISC_R_SUCCESS) + goto kill_dpool; *mgrp = mgr; return (ISC_R_SUCCESS); @@ -2077,8 +1908,8 @@ dns_dispatchmgr_create(isc_mem_t *mctx, isc_entropy_t *entropy, DESTROYLOCK(&mgr->depool_lock); kill_buffer_lock: DESTROYLOCK(&mgr->buffer_lock); - kill_arc4_lock: - DESTROYLOCK(&mgr->arc4_lock); + kill_rng_lock: + DESTROYLOCK(&mgr->rng_lock); kill_lock: DESTROYLOCK(&mgr->lock); deallocate: @@ -2583,7 +2414,8 @@ dispatch_allocate(dns_dispatchmgr_t *mgr, unsigned int maxrequests, ISC_LIST_INIT(disp->activesockets); ISC_LIST_INIT(disp->inactivesockets); disp->nsockets = 0; - dispatch_initrandom(&disp->arc4ctx, mgr->entropy, NULL); + disp->rngctx = NULL; + isc_rng_attach(mgr->rngctx, &disp->rngctx); disp->port_table = NULL; disp->portpool = NULL; disp->dscp = -1; @@ -2609,6 +2441,8 @@ dispatch_allocate(dns_dispatchmgr_t *mgr, unsigned int maxrequests, kill_lock: DESTROYLOCK(&disp->lock); deallocate: + if (disp->rngctx != NULL) + isc_rng_detach(&disp->rngctx); isc_mempool_put(mgr->dpool, disp); return (result); @@ -2659,6 +2493,9 @@ dispatch_free(dns_dispatch_t **dispp) { if (disp->portpool != NULL) isc_mempool_destroy(&disp->portpool); + if (disp->rngctx != NULL) + isc_rng_detach(&disp->rngctx); + disp->mgr = NULL; DESTROYLOCK(&disp->lock); disp->magic = 0; @@ -2905,9 +2742,8 @@ get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp, for (i = 0; i < 1024; i++) { in_port_t prt; - prt = ports[dispatch_uniformrandom( - DISP_ARC4CTX(disp), - nports)]; + prt = ports[isc_rng_uniformrandom(DISP_RNGCTX(disp), + nports)]; isc_sockaddr_setport(&localaddr_bound, prt); result = open_socket(sockmgr, &localaddr_bound, 0, &sock, NULL); @@ -3281,7 +3117,7 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest, * Try somewhat hard to find an unique ID. */ LOCK(&qid->lock); - id = (dns_messageid_t)dispatch_random(DISP_ARC4CTX(disp)); + id = (dns_messageid_t)isc_rng_random(DISP_RNGCTX(disp)); ok = ISC_FALSE; i = 0; do { diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in index ba0838f65f..c6649ae945 100644 --- a/lib/isc/Makefile.in +++ b/lib/isc/Makefile.in @@ -72,6 +72,8 @@ OBJS = @ISC_EXTRA_OBJS@ @ISC_PK11_O@ @ISC_PK11_RESULT_O@ \ ${UNIXOBJS} ${NLSOBJS} ${THREADOBJS} SYMTBLOBJS = backtrace-emptytbl.@O@ +CHACHASRCS = chacha_private.h + # Alphabetically SRCS = @ISC_EXTRA_SRCS@ @ISC_PK11_C@ @ISC_PK11_RESULT_C@ \ aes.c assertions.c backtrace.c base32.c base64.c bind9.c \ @@ -81,7 +83,7 @@ SRCS = @ISC_EXTRA_SRCS@ @ISC_PK11_C@ @ISC_PK11_RESULT_C@ \ lex.c lfsr.c lib.c log.c \ md5.c mem.c mutexblock.c \ netaddr.c netscope.c pool.c ondestroy.c \ - parseint.c portset.c quota.c radix.c random.c \ + parseint.c portset.c quota.c radix.c random.c ${CHACHASRCS} \ ratelimiter.c refcount.c region.c regex.c result.c rwlock.c \ safe.c serial.c sha1.c sha2.c sockaddr.c stats.c string.c \ strtoul.c symtab.c task.c taskpool.c timer.c \ diff --git a/lib/isc/chacha_private.h b/lib/isc/chacha_private.h new file mode 100644 index 0000000000..d79329dbbc --- /dev/null +++ b/lib/isc/chacha_private.h @@ -0,0 +1,227 @@ +/* + * Taken from OpenBSD CVS src/lib/libc/crypt/chacha_private.h on + * May 12, 2014. + */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +typedef unsigned char u8; +typedef unsigned int u32; + +typedef struct +{ + u32 input[16]; /* could be compressed */ +} chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((u8)(v) & U8C(0xFF)) +#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((u32)((p)[0]) ) | \ + ((u32)((p)[1]) << 8) | \ + ((u32)((p)[2]) << 16) | \ + ((u32)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +static void +chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits) +{ + const char *constants; + + UNUSED(ivbits); + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +static void +chacha_ivsetup(chacha_ctx *x,const u8 *iv) +{ + x->input[12] = 0; + x->input[13] = 0; + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +static void +chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes) +{ + u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + u8 *ctarget = NULL; + u8 tmp[64]; + u_int i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + +#ifndef KEYSTREAM_ONLY + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); +#endif + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; +#ifndef KEYSTREAM_ONLY + m += 64; +#endif + } +} diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h index 1f9572d30e..8d74f25cae 100644 --- a/lib/isc/include/isc/random.h +++ b/lib/isc/include/isc/random.h @@ -22,6 +22,9 @@ #include #include +#include +#include +#include /*! \file isc/random.h * \brief Implements a random state pool which will let the caller return a @@ -35,6 +38,11 @@ ISC_LANG_BEGINDECLS +typedef struct isc_rng isc_rng_t; +/*%< + * Opaque type + */ + void isc_random_seed(isc_uint32_t seed); /*%< @@ -57,6 +65,66 @@ isc_random_jitter(isc_uint32_t max, isc_uint32_t jitter); * This is useful for jittering timer values. */ +isc_result_t +isc_rng_create(isc_mem_t *mctx, isc_entropy_t *entropy, isc_rng_t **rngp); +/*%< + * Creates and initializes a pseudo random number generator. The + * returned RNG can be used to generate pseudo random numbers. + * + * The reference count of the returned RNG is set to 1. + * + * Requires: + * \li mctx is a pointer to a valid memory context. + * \li entropy is an optional entopy source (can be NULL) + * \li rngp != NULL && *rngp == NULL is where a pointer to the RNG is + * returned. + * + * Ensures: + *\li If result is ISC_R_SUCCESS: + * *rngp points to a valid RNG. + * + *\li If result is failure: + * *rngp does not point to a valid RNG. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of Memory + */ + +void +isc_rng_attach(isc_rng_t *source, isc_rng_t **targetp); +/*%< + * Increments a reference count on the passed RNG. + * + * Requires: + * \li source the RNG struct to attach to (is refcount is incremented) + * \li targetp != NULL && *targetp == NULL where a pointer to the + * reference incremented RNG is returned. + */ + +void +isc_rng_detach(isc_rng_t **rngp); +/*%< + * Decrements a reference count on the passed RNG. If the reference + * count reaches 0, the RNG is destroyed. + * + * Requires: + * \li rngp != NULL the RNG struct to decrement reference for + */ + +isc_uint16_t +isc_rng_random(isc_rng_t *rngctx); +/*%< + * Returns a pseudo random 16-bit unsigned integer. + */ + +isc_uint16_t +isc_rng_uniformrandom(isc_rng_t *rngctx, isc_uint16_t upper_bound); +/*%< + * Returns a uniformly distributed pseudo random 16-bit unsigned + * integer. + */ + ISC_LANG_ENDDECLS #endif /* ISC_RANDOM_H */ diff --git a/lib/isc/random.c b/lib/isc/random.c index d18204d6e3..d0cbdc872f 100644 --- a/lib/isc/random.c +++ b/lib/isc/random.c @@ -14,8 +14,26 @@ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ - -/* $Id: random.c,v 1.28 2009/07/16 05:52:46 marka Exp $ */ +/*% + * ChaCha based random number generator derived from OpenBSD. + * + * The original copyright follows: + * Copyright (c) 1996, David Mazieres + * Copyright (c) 2008, Damien Miller + * Copyright (c) 2013, Markus Friedl + * + * 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 */ @@ -30,17 +48,43 @@ #include #endif +#include #include #include +#include +#include #include #include #include +#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 32 +#define CHACHA_IVSIZE 8 +#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; + isc_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) -{ +initialize_rand(void) { #ifndef HAVE_ARC4RANDOM unsigned int pid = getpid(); @@ -55,14 +99,12 @@ initialize_rand(void) } static void -initialize(void) -{ +initialize(void) { RUNTIME_CHECK(isc_once_do(&once, initialize_rand) == ISC_R_SUCCESS); } void -isc_random_seed(isc_uint32_t seed) -{ +isc_random_seed(isc_uint32_t seed) { initialize(); #ifndef HAVE_ARC4RANDOM @@ -81,8 +123,7 @@ isc_random_seed(isc_uint32_t seed) } void -isc_random_get(isc_uint32_t *val) -{ +isc_random_get(isc_uint32_t *val) { REQUIRE(val != NULL); initialize(); @@ -119,3 +160,260 @@ isc_random_jitter(isc_uint32_t max, isc_uint32_t jitter) { isc_random_get(&rnd); return (max - rnd % jitter); } + +static void +chacha_reinit(isc_rng_t *rng, isc_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]; + isc_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 **rngp) { + isc_rng_t *rng = *rngp; + + REQUIRE(VALID_RNG(rng)); + + isc_mutex_destroy(&rng->lock); + rng->magic = 0; + isc_mem_putanddetach(&rng->mctx, rng, sizeof(isc_rng_t)); + *rngp = NULL; +} + +void +isc_rng_detach(isc_rng_t **rngp) { + isc_rng_t *rng = *rngp; + isc_boolean_t dest = ISC_FALSE; + + REQUIRE(VALID_RNG(rng)); + + LOCK(&rng->lock); + + INSIST(rng->references > 0); + rng->references--; + if (rng->references == 0) + dest = ISC_TRUE; + + if (dest) + destroy(rngp); + else { + UNLOCK(&rng->lock); + *rngp = NULL; + } +} + +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 isc_uint16_t +chacha_getuint16(isc_rng_t *rng) { + isc_uint16_t val; + + REQUIRE(VALID_RNG(rng)); + + if (rng->have < sizeof(val)) + chacha_rekey(rng, NULL, 0); + + memcpy(&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]; + isc_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)); + + /* + * The OpenBSD implementation explicit_bzero()s the random seed + * rnd.rnd at this point, but it may not be required here. This + * memset() may also be optimized away by the compiler as + * rnd.rnd is not used further. + */ + memset(rnd.rnd, 0, 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; +} + +isc_uint16_t +isc_rng_random(isc_rng_t *rng) { + isc_uint16_t result; + + REQUIRE(VALID_RNG(rng)); + + LOCK(&rng->lock); + + rng->count -= sizeof(isc_uint16_t); + if (rng->count <= 0) + chacha_stir(rng); + result = chacha_getuint16(rng); + + UNLOCK(&rng->lock); + + return (result); +} + +isc_uint16_t +isc_rng_uniformrandom(isc_rng_t *rng, isc_uint16_t upper_bound) { + isc_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 = (isc_uint16_t)(0x10000 % (isc_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); +} diff --git a/lib/isc/tests/Makefile.in b/lib/isc/tests/Makefile.in index 1233420b75..e16e592977 100644 --- a/lib/isc/tests/Makefile.in +++ b/lib/isc/tests/Makefile.in @@ -36,14 +36,14 @@ LIBS = @LIBS@ @ATFLIBS@ OBJS = isctest.@O@ SRCS = isctest.c taskpool_test.c socket_test.c hash_test.c \ - lex_test.c \ + lex_test.c random_test.c \ sockaddr_test.c symtab_test.c task_test.c queue_test.c \ parse_test.c pool_test.c regex_test.c socket_test.c \ safe_test.c time_test.c aes_test.c SUBDIRS = TARGETS = taskpool_test@EXEEXT@ socket_test@EXEEXT@ hash_test@EXEEXT@ \ - lex_test@EXEEXT@ \ + lex_test@EXEEXT@ random_test@EXEEXT@ \ sockaddr_test@EXEEXT@ symtab_test@EXEEXT@ task_test@EXEEXT@ \ queue_test@EXEEXT@ parse_test@EXEEXT@ pool_test@EXEEXT@ \ regex_test@EXEEXT@ socket_test@EXEEXT@ safe_test@EXEEXT@ \ @@ -75,6 +75,10 @@ queue_test@EXEEXT@: queue_test.@O@ isctest.@O@ ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ queue_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} +random_test@EXEEXT@: random_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + random_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} + symtab_test@EXEEXT@: symtab_test.@O@ isctest.@O@ ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ symtab_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} diff --git a/lib/isc/tests/random_test.c b/lib/isc/tests/random_test.c new file mode 100644 index 0000000000..5a51605b8d --- /dev/null +++ b/lib/isc/tests/random_test.c @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +typedef double (pvalue_func_t)(isc_mem_t *mctx, + uint16_t *values, size_t length); + +/* igamc(), igam(), etc. were adapted (and cleaned up) from the Cephes + * math library: + * + * Cephes Math Library Release 2.8: June, 2000 + * Copyright 1985, 1987, 2000 by Stephen L. Moshier + * + * The Cephes math library was released into the public domain as part + * of netlib. +*/ + +static double MACHEP = 1.11022302462515654042E-16; +static double MAXLOG = 7.09782712893383996843E2; +static double big = 4.503599627370496e15; +static double biginv = 2.22044604925031308085e-16; + +static double igamc(double a, double x); +static double igam(double a, double x); + +static double +igamc(double a, double x) { + double ans, ax, c, yc, r, t, y, z; + double pk, pkm1, pkm2, qk, qkm1, qkm2; + + if ((x <= 0) || (a <= 0)) + return (1.0); + + if ((x < 1.0) || (x < a)) + return (1.0 - igam(a, x)); + + ax = a * log(x) - x - lgamma(a); + if (ax < -MAXLOG) { + fprintf(stderr, "igamc: UNDERFLOW, ax=%f\n", ax); + return (0.0); + } + ax = exp(ax); + + /* continued fraction */ + y = 1.0 - a; + z = x + y + 1.0; + c = 0.0; + pkm2 = 1.0; + qkm2 = x; + pkm1 = x + 1.0; + qkm1 = z * x; + ans = pkm1 / qkm1; + + do { + c += 1.0; + y += 1.0; + z += 2.0; + yc = y * c; + pk = pkm1 * z - pkm2 * yc; + qk = qkm1 * z - qkm2 * yc; + if (qk != 0) { + r = pk / qk; + t = fabs((ans - r) / r); + ans = r; + } else + t = 1.0; + + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + if (fabs(pk) > big) { + pkm2 *= biginv; + pkm1 *= biginv; + qkm2 *= biginv; + qkm1 *= biginv; + } + } while (t > MACHEP); + + return (ans * ax); +} + +static double +igam(double a, double x) { + double ans, ax, c, r; + + if ((x <= 0) || (a <= 0)) + return (0.0); + + if ((x > 1.0) && (x > a)) + return (1.0 - igamc(a, x)); + + /* Compute x**a * exp(-x) / md_gamma(a) */ + ax = a * log(x) - x - lgamma(a); + if( ax < -MAXLOG ) { + fprintf(stderr, "igam: UNDERFLOW, ax=%f\n", ax); + return (0.0); + } + ax = exp(ax); + + /* power series */ + r = a; + c = 1.0; + ans = 1.0; + + do { + r += 1.0; + c *= x / r; + ans += c; + } while (c / ans > MACHEP); + + return (ans * ax / a); +} + +static int8_t scounts_table[65536]; +static uint8_t bitcounts_table[65536]; + +static int8_t +scount_calculate(uint16_t n) { + int i; + int8_t sc; + + sc = 0; + for (i = 0; i < 16; i++) { + uint16_t lsb; + + lsb = n & 1; + if (lsb != 0) + sc += 1; + else + sc -= 1; + + n >>= 1; + } + + return (sc); +} + +static uint8_t +bitcount_calculate(uint16_t n) { + int i; + uint8_t bc; + + bc = 0; + for (i = 0; i < 16; i++) { + uint16_t lsb; + + lsb = n & 1; + if (lsb != 0) + bc += 1; + + n >>= 1; + } + + return (bc); +} + +static void +tables_init(void) { + uint32_t i; + + for (i = 0; i < 65536; i++) { + scounts_table[i] = scount_calculate(i); + bitcounts_table[i] = bitcount_calculate(i); + } +} + +/* + * The following code for computing Marsaglia's rank is based on the + * implementation in cdbinrnk.c from the diehard tests by George + * Marsaglia. + * + * This function destroys (modifies) the data passed in bits. + */ +static uint32_t +matrix_binaryrank(uint32_t *bits, ssize_t rows, ssize_t cols) { + ssize_t i, j, k; + int rt = 0; + uint32_t rank = 0; + uint32_t tmp; + + for (k = 0; k < rows; k++) { + i = k; + + while (((bits[i] >> rt) & 1) == 0) { + i++; + + if (i < rows) + continue; + else { + rt++; + if (rt < cols) { + i = k; + continue; + } + } + + return (rank); + } + + rank++; + if (i != k) { + tmp = bits[i]; + bits[i] = bits[k]; + bits[k] = tmp; + } + + for (j = i + 1; j < rows; j++) { + if (((bits[j] >> rt) & 1) == 0) + continue; + else + bits[j] ^= bits[k]; + } + + rt++; + } + + return (rank); +} + +static void +random_test(pvalue_func_t *func) { + isc_mem_t *mctx = NULL; + isc_result_t result; + isc_rng_t *rng; + uint32_t m; + uint32_t j; + uint32_t histogram[11]; + uint32_t passed; + double proportion; + double p_hat; + double lower_confidence; + double chi_square; + double p_value_t; + + tables_init(); + + result = isc_mem_create(0, 0, &mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + rng = NULL; + result = isc_rng_create(mctx, NULL, &rng); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + m = 1000; + passed = 0; + + for (j = 0; j < 11; j++) + histogram[j] = 0; + + for (j = 0; j < m; j++) { + uint32_t i; + uint16_t values[128000]; + double p_value; + + for (i = 0; i < 128000; i++) + values[i] = isc_rng_random(rng); + + p_value = (*func)(mctx, values, 128000); + if (p_value >= 0.01) + passed++; + + ATF_REQUIRE(p_value >= 0.0); + ATF_REQUIRE(p_value <= 1.0); + + i = (int) floor(p_value * 10); + histogram[i]++; + } + + isc_rng_detach(&rng); + + /* Fold histogram[10] (p_value = 1.0) into histogram[9] for + * interval [0.9, 1.0] + */ + histogram[9] += histogram[10]; + histogram[10] = 0; + + /* + * Check proportion of sequences passing a test (see section + * 4.2.1 in NIST SP 800-22). + */ + proportion = (double) passed / (double) m; + p_hat = 1 - 0.01; /* alpha is 0.01 in the NIST tests */ + lower_confidence = p_hat - (3.0 * sqrt((p_hat * (1 - p_hat)) / m)); + + /* Debug message, not displayed when running via atf-run */ + printf("passed=%u/1000\n", passed); + printf("lower_confidence=%f, proportion=%f\n", + lower_confidence, proportion); + + ATF_REQUIRE(proportion >= lower_confidence); + + /* + * Check uniform distribution of p-values (see section 4.2.2 in + * NIST SP 800-22). + */ + + /* Pre-requisite that at least 55 sequences are processed. */ + ATF_REQUIRE(m >= 55); + + chi_square = 0.0; + for (j = 0; j < 10; j++) { + double numer; + double denom; + + /* Debug message, not displayed when running via atf-run */ + printf("hist%u=%u ", j, histogram[j]); + + numer = (histogram[j] - (m / 10.0)) * + (histogram[j] - (m / 10.0)); + denom = m / 10.0; + chi_square += numer / denom; + } + + printf("\n"); + + p_value_t = igamc(9 / 2.0, chi_square / 2.0); + + ATF_REQUIRE(p_value_t >= 0.0001); +} + +/* + * This is a frequency (monobits) test taken from the NIST SP 800-22 + * RNG test suite. + */ +static double +monobit(isc_mem_t *mctx, uint16_t *values, size_t length) { + size_t i; + int32_t scount; + uint32_t numbits; + double s_obs; + double p_value; + + UNUSED(mctx); + + numbits = length * 16; + scount = 0; + + for (i = 0; i < length; i++) + scount += scounts_table[values[i]]; + + /* Preconditions (section 2.1.7 in NIST SP 800-22) */ + ATF_REQUIRE(numbits >= 100); + + /* Debug message, not displayed when running via atf-run */ + printf("numbits=%u, scount=%d\n", numbits, scount); + + s_obs = fabs(scount) / sqrt(numbits); + p_value = erfc(s_obs / sqrt(2.0)); + + return (p_value); +} + +/* + * This is the runs test taken from the NIST SP 800-22 RNG test suite. + */ +static double +runs(isc_mem_t *mctx, uint16_t *values, size_t length) { + size_t i; + uint32_t bcount; + uint32_t numbits; + double pi; + double tau; + uint32_t j; + uint32_t b; + uint8_t bit_this; + uint8_t bit_prev; + uint32_t v_obs; + double numer; + double denom; + double p_value; + + UNUSED(mctx); + + numbits = length * 16; + bcount = 0; + + for (i = 0; i < 128000; i++) + bcount += bitcounts_table[values[i]]; + + /* Debug message, not displayed when running via atf-run */ + printf("numbits=%u, bcount=%u\n", numbits, bcount); + + pi = (double) bcount / (double) numbits; + tau = 2.0 / sqrt(numbits); + + /* Preconditions (section 2.3.7 in NIST SP 800-22) */ + ATF_REQUIRE(numbits >= 100); + + /* + * Pre-condition implied from the monobit test. This can fail + * for some sequences, and the p-value is taken as 0 in these + * cases. + */ + if (fabs(pi - 0.5) >= tau) + return (0.0); + + /* Compute v_obs */ + j = 0; + b = 14; + bit_prev = (values[j] & (1U << 15)) == 0 ? 0 : 1; + + v_obs = 0; + + for (i = 1; i < numbits; i++) { + bit_this = (values[j] & (1U << b)) == 0 ? 0 : 1; + if (b == 0) { + b = 15; + j++; + } else { + b--; + } + + v_obs += bit_this ^ bit_prev; + + bit_prev = bit_this; + } + + v_obs += 1; + + numer = fabs(v_obs - (2.0 * numbits * pi * (1.0 - pi))); + denom = 2.0 * sqrt(2.0 * numbits) * pi * (1.0 - pi); + + p_value = erfc(numer / denom); + + return (p_value); +} + +/* + * This is the block frequency test taken from the NIST SP 800-22 RNG + * test suite. + */ +static double +blockfrequency(isc_mem_t *mctx, uint16_t *values, size_t length) { + uint32_t i; + uint32_t numbits; + uint32_t mbits; + uint32_t mwords; + uint32_t numblocks; + double *pi; + uint32_t cur_word; + double chi_square; + double p_value; + + numbits = length * 16; + mbits = 32000; + mwords = mbits / 16; + numblocks = numbits / mbits; + + /* Debug message, not displayed when running via atf-run */ + printf("numblocks=%u\n", numblocks); + + /* Preconditions (section 2.2.7 in NIST SP 800-22) */ + ATF_REQUIRE(numbits >= 100); + ATF_REQUIRE(mbits >= 20); + ATF_REQUIRE((double) mbits > (0.01 * numbits)); + ATF_REQUIRE(numblocks < 100); + ATF_REQUIRE(numbits >= (mbits * numblocks)); + + pi = isc_mem_get(mctx, numblocks * sizeof(double)); + + cur_word = 0; + for (i = 0; i < numblocks; i++) { + uint32_t j; + pi[i] = 0.0; + for (j = 0; j < mwords; j++) { + uint32_t idx; + + idx = i * mwords + j; + pi[i] += bitcounts_table[values[idx]]; + cur_word++; + } + pi[i] /= mbits; + } + + /* Compute chi_square */ + chi_square = 0.0; + for (i = 0; i < numblocks; i++) + chi_square += (pi[i] - 0.5) * (pi[i] - 0.5); + + chi_square *= 4 * mbits; + + isc_mem_put(mctx, pi, numblocks * sizeof(double)); + + /* Debug message, not displayed when running via atf-run */ + printf("chi_square=%f\n", chi_square); + + p_value = igamc(numblocks * 0.5, chi_square * 0.5); + + return (p_value); +} + +/* + * This is the binary matrix rank test taken from the NIST SP 800-22 RNG + * test suite. + */ +static double +binarymatrixrank(isc_mem_t *mctx, uint16_t *values, size_t length) { + uint32_t i; + size_t matrix_m; + size_t matrix_q; + uint32_t num_matrices; + size_t numbits; + uint32_t fm_0; + uint32_t fm_1; + uint32_t fm_rest; + double term1; + double term2; + double term3; + double chi_square; + double p_value; + + UNUSED(mctx); + + matrix_m = 32; + matrix_q = 32; + num_matrices = length / ((matrix_m * matrix_q) / 16); + numbits = num_matrices * matrix_m * matrix_q; + + /* Preconditions (section 2.5.7 in NIST SP 800-22) */ + ATF_REQUIRE(matrix_m == 32); + ATF_REQUIRE(matrix_q == 32); + ATF_REQUIRE(numbits >= (38 * matrix_m * matrix_q)); + + fm_0 = 0; + fm_1 = 0; + fm_rest = 0; + for (i = 0; i < num_matrices; i++) { + /* + * Each uint32_t supplies 32 bits, so a 32x32 bit matrix + * takes up uint32_t array of size 32. + */ + uint32_t bits[32]; + int j; + uint32_t rank; + + for (j = 0; j < 32; j++) { + size_t idx; + uint32_t r1; + uint32_t r2; + + idx = i * ((matrix_m * matrix_q) / 16); + idx += j * 2; + + r1 = values[idx]; + r2 = values[idx + 1]; + bits[j] = (r1 << 16) | r2; + } + + rank = matrix_binaryrank(bits, matrix_m, matrix_q); + + if (rank == matrix_m) + fm_0++; + else if (rank == (matrix_m - 1)) + fm_1++; + else + fm_rest++; + } + + /* Compute chi_square */ + term1 = ((fm_0 - (0.2888 * num_matrices)) * + (fm_0 - (0.2888 * num_matrices))) / (0.2888 * num_matrices); + term2 = ((fm_1 - (0.5776 * num_matrices)) * + (fm_1 - (0.5776 * num_matrices))) / (0.5776 * num_matrices); + term3 = ((fm_rest - (0.1336 * num_matrices)) * + (fm_rest - (0.1336 * num_matrices))) / (0.1336 * num_matrices); + + chi_square = term1 + term2 + term3; + + /* Debug message, not displayed when running via atf-run */ + printf("fm_0=%u, fm_1=%u, fm_rest=%u, chi_square=%f\n", + fm_0, fm_1, fm_rest, chi_square); + + p_value = exp(-chi_square * 0.5); + + return (p_value); +} + +ATF_TC(isc_rng_monobit); +ATF_TC_HEAD(isc_rng_monobit, tc) { + atf_tc_set_md_var(tc, "descr", "Monobit test for the RNG"); +} + +ATF_TC_BODY(isc_rng_monobit, tc) { + UNUSED(tc); + + random_test(monobit); +} + +ATF_TC(isc_rng_runs); +ATF_TC_HEAD(isc_rng_runs, tc) { + atf_tc_set_md_var(tc, "descr", "Runs test for the RNG"); +} + +ATF_TC_BODY(isc_rng_runs, tc) { + UNUSED(tc); + + random_test(runs); +} + +ATF_TC(isc_rng_blockfrequency); +ATF_TC_HEAD(isc_rng_blockfrequency, tc) { + atf_tc_set_md_var(tc, "descr", "Block frequency test for the RNG"); +} + +ATF_TC_BODY(isc_rng_blockfrequency, tc) { + UNUSED(tc); + + random_test(blockfrequency); +} + +ATF_TC(isc_rng_binarymatrixrank); +ATF_TC_HEAD(isc_rng_binarymatrixrank, tc) { + atf_tc_set_md_var(tc, "descr", "Binary matrix rank test for the RNG"); +} + +/* + * This is the binary matrix rank test taken from the NIST SP 800-22 RNG + * test suite. + */ +ATF_TC_BODY(isc_rng_binarymatrixrank, tc) { + UNUSED(tc); + + random_test(binarymatrixrank); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, isc_rng_monobit); + ATF_TP_ADD_TC(tp, isc_rng_runs); + ATF_TP_ADD_TC(tp, isc_rng_blockfrequency); + ATF_TP_ADD_TC(tp, isc_rng_binarymatrixrank); + + return (atf_no_error()); +}