/* SPDX-License-Identifier: MIT OR GPL-3.0-only */ /* rand.c * strophe XMPP client library -- pseudo-random number generator * * Copyright (C) 2014 Dmitry Podgorny * * This software is provided AS-IS with no warranty, either express * or implied. * * This program is dual licensed under the MIT or GPLv3 licenses. */ /** @file * Pseudo-random number generator. * * Implemented Hash_DRBG mechanism according to NIST SP 800-90A. * Hash function is SHA1. */ /** @defgroup Random Pseudo-random number generator */ #include #include /* memeset */ #include /* clock, time */ #if !defined(_WIN32) #include #endif #if !defined(DONT_USE_GETRANDOM) && defined(__linux__) && \ defined(__GLIBC_PREREQ) #if __GLIBC_PREREQ(2, 25) #define USE_GETRANDOM #include #include #endif #endif #include "common.h" /* strophe_alloc, strophe_free */ #include "ostypes.h" /* uint8_t, uint32_t, size_t */ #ifndef USE_GETRANDOM #include "sha1.h" #define outlen SHA1_DIGEST_SIZE #define seedlen (440 / 8) #define reseed_interval 0x7fffffff /* maximum number of bytes that can be generated per call */ #define GENERATE_MAX (outlen * 10) #define ENTROPY_MAX 128 #define NONCE_MAX 8 #define RESEED_NEEDED (-1) struct Hash_DRBG_CTX_struc { uint8_t V[seedlen]; uint8_t C[seedlen]; uint32_t reseed_counter; }; typedef struct Hash_DRBG_CTX_struc Hash_DRBG_CTX; struct _xmpp_rand_t { int inited; unsigned reseed_count; Hash_DRBG_CTX ctx; }; /* returns smallest number mupliple of y that not less than x */ #define round_up(x, y) (((x) + (y)-1) / (y) * (y)) /* returns smallest integer number that not less than x/y */ #define div_round_up(x, y) (((x) + (y)-1) / (y)) /* adds two arrays as numbers in big-endian representation and stores * result in the first one. */ static void arr_add(uint8_t *arr1, size_t arr1_len, uint8_t *arr2, size_t arr2_len) { size_t i; uint32_t acc; uint32_t carry = 0; assert(arr1_len >= arr2_len); for (i = 1; (i <= arr2_len) || (carry != 0 && i <= arr1_len); ++i) { acc = (uint32_t)arr1[arr1_len - i] + carry; if (i <= arr2_len) acc += (uint32_t)arr2[arr2_len - i]; carry = acc >> 8; arr1[arr1_len - i] = (uint8_t)(acc & 0xff); } } /* stores 32-bit number in big-endian representation */ static void store_be32(uint32_t val, uint8_t be[4]) { be[0] = (uint8_t)((val >> 24) & 0xff); be[1] = (uint8_t)((val >> 16) & 0xff); be[2] = (uint8_t)((val >> 8) & 0xff); be[3] = (uint8_t)(val & 0xff); } static void Hash_df(uint8_t *input_string, size_t input_string_len, uint8_t *output_string, size_t no_of_bytes_to_return) { uint8_t counter; uint8_t temp[round_up(seedlen, outlen)]; uint8_t conj[ENTROPY_MAX + NONCE_MAX + seedlen + 6]; size_t len; size_t i; size_t offset; assert(no_of_bytes_to_return <= sizeof(temp)); assert(input_string_len + 5 <= sizeof(conj)); len = div_round_up(no_of_bytes_to_return, outlen); for (i = 1; i <= len; ++i) { offset = (i - 1) * outlen; counter = (uint8_t)i; conj[0] = counter; store_be32((uint32_t)no_of_bytes_to_return * 8, conj + 1); memcpy(conj + 5, input_string, input_string_len); crypto_SHA1(conj, input_string_len + 5, temp + offset); } memcpy(output_string, temp, no_of_bytes_to_return); } /* assume personalization_string is zero length string */ static void Hash_DRBG_Instantiate(Hash_DRBG_CTX *ctx, uint8_t *entropy_input, size_t entropy_input_len, uint8_t *nonce, size_t nonce_len) { uint8_t seed_material[ENTROPY_MAX + NONCE_MAX]; uint8_t seed0[seedlen + 1]; uint8_t *seed = seed0 + 1; assert(entropy_input_len <= ENTROPY_MAX); assert(nonce_len <= NONCE_MAX); assert(nonce != NULL || nonce_len == 0); memcpy(seed_material, entropy_input, entropy_input_len); if (nonce != NULL) memcpy(seed_material + entropy_input_len, nonce, nonce_len); Hash_df(seed_material, entropy_input_len + nonce_len, seed, seedlen); seed0[0] = 0; memcpy(ctx->V, seed, seedlen); Hash_df(seed0, sizeof(seed0), ctx->C, seedlen); ctx->reseed_counter = 1; } /* assume additional_input is zero length string */ static void Hash_DRBG_Reseed(Hash_DRBG_CTX *ctx, uint8_t *entropy_input, size_t entropy_input_len) { uint8_t seed_material[1 + seedlen + ENTROPY_MAX]; uint8_t seed0[seedlen + 1]; uint8_t *seed = seed0 + 1; assert(entropy_input_len <= ENTROPY_MAX); seed_material[0] = 1; memcpy(seed_material + 1, ctx->V, seedlen); memcpy(seed_material + 1 + seedlen, entropy_input, entropy_input_len); Hash_df(seed_material, entropy_input_len + seedlen + 1, seed, seedlen); seed0[0] = 0; memcpy(ctx->V, seed, seedlen); Hash_df(seed0, sizeof(seed0), ctx->C, seedlen); ctx->reseed_counter = 1; } static void Hashgen(uint8_t *V, uint8_t *output, size_t requested_number_of_bytes) { uint8_t data[seedlen]; uint8_t W[GENERATE_MAX]; uint8_t i1 = 1; size_t m; size_t i; size_t offset; assert(requested_number_of_bytes <= sizeof(W)); m = div_round_up(requested_number_of_bytes, outlen); memcpy(data, V, seedlen); for (i = 1; i <= m; ++i) { offset = (i - 1) * outlen; crypto_SHA1(data, seedlen, W + offset); /* increase data by 1 */ arr_add(data, sizeof(data), &i1, 1); } memcpy(output, W, requested_number_of_bytes); } /* assume additional_input is zero length string */ static int Hash_DRBG_Generate(Hash_DRBG_CTX *ctx, uint8_t *output, size_t requested_number_of_bytes) { uint8_t H[outlen]; uint8_t V3[seedlen + 1]; uint8_t reseed_counter[4]; if (ctx->reseed_counter > reseed_interval || ctx->reseed_counter == 0) return RESEED_NEEDED; Hashgen(ctx->V, output, requested_number_of_bytes); V3[0] = 3; memcpy(V3 + 1, ctx->V, seedlen); crypto_SHA1(V3, sizeof(V3), H); arr_add(ctx->V, sizeof(ctx->V), ctx->C, sizeof(ctx->C)); arr_add(ctx->V, sizeof(ctx->V), H, sizeof(H)); store_be32(ctx->reseed_counter, reseed_counter); arr_add(ctx->V, sizeof(ctx->V), reseed_counter, sizeof(reseed_counter)); ++ctx->reseed_counter; return 0; } #define ENTROPY_ACCUMULATE(ptr, last, type, arg) \ do { \ type __arg = (type)(arg); \ if ((char *)ptr + sizeof(__arg) < (char *)last) { \ *(type *)ptr = __arg; \ ptr = (void *)((char *)ptr + sizeof(__arg)); \ } \ } while (0) static void xmpp_rand_reseed(xmpp_rand_t *rand) { uint8_t entropy[ENTROPY_MAX]; uint8_t *ptr = entropy; const uint8_t *last = entropy + sizeof(entropy); size_t len; /* entropy: * 1. time_stamp() * 2. clock(3) * 3. xmpp_rand_t address to make unique seed within one process * 4. counter to make unique seed within one context * 5. stack address * 6. local ports of every connection in list (getsockname) * 7. other non-constant info that can be retieved from socket * * rand(3) can't be used as it isn't thread-safe. * XXX 6 and 7 are not implemented yet. */ ENTROPY_ACCUMULATE(ptr, last, uint64_t, time_stamp()); ENTROPY_ACCUMULATE(ptr, last, clock_t, clock()); ENTROPY_ACCUMULATE(ptr, last, void *, rand); ENTROPY_ACCUMULATE(ptr, last, unsigned, ++rand->reseed_count); ENTROPY_ACCUMULATE(ptr, last, void *, &entropy); len = ptr - entropy; if (rand->inited) { Hash_DRBG_Reseed(&rand->ctx, entropy, len); } else { Hash_DRBG_Instantiate(&rand->ctx, entropy, len, NULL, 0); rand->inited = 1; } } xmpp_rand_t *xmpp_rand_new(xmpp_ctx_t *ctx) { xmpp_rand_t *out = strophe_alloc(ctx, sizeof(*out)); if (out != NULL) { memset(out, 0, sizeof(*out)); } return out; } void xmpp_rand_free(xmpp_ctx_t *ctx, xmpp_rand_t *rand) { strophe_free(ctx, rand); } void xmpp_rand_bytes(xmpp_rand_t *rand, unsigned char *output, size_t len) { int rc; size_t gen, tot = 0; while (tot < len) { gen = len - tot; if (gen > GENERATE_MAX) gen = GENERATE_MAX; rc = Hash_DRBG_Generate(&rand->ctx, (uint8_t *)output + tot, gen); if (rc == RESEED_NEEDED) { xmpp_rand_reseed(rand); rc = Hash_DRBG_Generate(&rand->ctx, (uint8_t *)output + tot, gen); assert(rc == 0); } tot += gen; } } #else static int _read_getrandom(void *p, size_t n) { unsigned char *q = (unsigned char *)p; while (n > 0u) { ssize_t ret = getrandom(q, n, 0); if (ret < 0) { if (errno == EINTR) { continue; } return 1; } q += ret; n -= (size_t)ret; } return 0; } struct _xmpp_rand_t { char nothing; }; static xmpp_rand_t _xmpp_rand; xmpp_rand_t *xmpp_rand_new(xmpp_ctx_t *ctx) { UNUSED(ctx); return &_xmpp_rand; } void xmpp_rand_free(xmpp_ctx_t *ctx, xmpp_rand_t *rand) { UNUSED(ctx); assert(rand == &_xmpp_rand); } void xmpp_rand_bytes(xmpp_rand_t *rand, unsigned char *output, size_t len) { assert(rand == &_xmpp_rand); assert(_read_getrandom(output, len) == 0); } #endif int xmpp_rand(xmpp_rand_t *rand) { int result; xmpp_rand_bytes(rand, (unsigned char *)&result, sizeof(result)); return result; } static void rand_byte2hex(unsigned char byte, char *hex) { static const char hex_tbl[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; hex[0] = hex_tbl[(byte >> 4) & 0x0f]; hex[1] = hex_tbl[byte & 0x0f]; } void xmpp_rand_nonce(xmpp_rand_t *rand, char *output, size_t len) { size_t i; const size_t rand_len = len / 2; /* * We don't want to use any allocation here, because this function * can't fail. Also we want to avoid VLA. * Current implementation uses half of the output buffer for random buffer * generation and then converts it to HEX representation. */ if (rand_len > 0) { xmpp_rand_bytes(rand, (unsigned char *)output, rand_len); for (i = rand_len; i > 0; --i) rand_byte2hex(output[i - 1], &output[(i - 1) * 2]); } if (len > 0) output[len - 1] = '\0'; }