From 10656ead460822dd895249cac6aee6df4e382b42 Mon Sep 17 00:00:00 2001 From: Dmitry Podgorny Date: Mon, 8 Sep 2014 15:05:51 +0300 Subject: [PATCH] Introduced PRNG based on Hash_DRBG (NIST SP 800-90A) This pseudo-random number generator solves problem with platform-independent generation of randomized nonces. Current implementation uses weak entropy, especially when kernel.randomize_va_space = 0. But it can be improved by adding new sources to xmpp_rand_reseed(). New internal API introduced: xmpp_rand_new xmpp_rand_free xmpp_rand xmpp_rand_bytes xmpp_rand_nonce --- Makefile.am | 4 +- src/auth.c | 27 +---- src/common.h | 2 + src/ctx.c | 6 + src/rand.c | 306 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/rand.h | 43 ++++++++ src/sasl.c | 6 +- 7 files changed, 366 insertions(+), 28 deletions(-) create mode 100644 src/rand.c create mode 100644 src/rand.h diff --git a/Makefile.am b/Makefile.am index dadce55..983fee1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,10 +23,10 @@ libstrophe_la_SOURCES = src/auth.c src/conn.c src/ctx.c \ src/event.c src/handler.c src/hash.c \ src/jid.c src/md5.c src/sasl.c src/scram.c src/sha1.c \ src/snprintf.c src/sock.c src/stanza.c src/thread.c \ - src/tls_openssl.c src/util.c \ + src/tls_openssl.c src/util.c src/rand.c \ src/common.h src/hash.h src/md5.h src/ostypes.h src/parser.h \ src/sasl.h src/scram.h src/sha1.h src/sock.h src/thread.h src/tls.h \ - src/util.h + src/util.h src/rand.h if PARSER_EXPAT libstrophe_la_SOURCES += src/parser_expat.c diff --git a/src/auth.c b/src/auth.c index b3056a2..c9608e1 100644 --- a/src/auth.c +++ b/src/auth.c @@ -16,12 +16,12 @@ #include #include #include -#include #include "strophe.h" #include "common.h" #include "sasl.h" #include "sha1.h" +#include "rand.h" #ifdef _MSC_VER #define strcasecmp stricmp @@ -501,46 +501,25 @@ err: return 0; } -static char *_get_nonce(xmpp_ctx_t *ctx) -{ - unsigned char buffer[sizeof(clock_t) + sizeof(time_t)] = {0}; - clock_t ticks = clock(); - time_t t; - - if (ticks != (clock_t)-1) { - *(clock_t *)buffer = ticks; - } - t = time((time_t *)(buffer + sizeof(clock_t))); - if (t == (time_t)-1) { - *(time_t *)(buffer + sizeof(clock_t)) = (time_t)rand(); - } - - return base64_encode(ctx, buffer, sizeof(buffer)); -} - static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn) { size_t message_len; char *node; char *message; - char *nonce; + char nonce[32]; node = xmpp_jid_node(conn->ctx, conn->jid); if (!node) { return NULL; } - nonce = _get_nonce(conn->ctx); - if (!nonce) { - return NULL; - } + xmpp_rand_nonce(conn->ctx, nonce, sizeof(nonce)); message_len = strlen(node) + strlen(nonce) + 8 + 1; message = xmpp_alloc(conn->ctx, message_len); if (message) { xmpp_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce); xmpp_free(conn->ctx, node); } - xmpp_free(conn->ctx, nonce); return message; } diff --git a/src/common.h b/src/common.h index fb6b6eb..bdf744b 100644 --- a/src/common.h +++ b/src/common.h @@ -27,6 +27,7 @@ #include "hash.h" #include "util.h" #include "parser.h" +#include "rand.h" /** run-time context **/ @@ -45,6 +46,7 @@ struct _xmpp_ctx_t { const xmpp_mem_t *mem; const xmpp_log_t *log; + xmpp_rand_t *rand; xmpp_loop_status_t loop_status; xmpp_connlist_t *connlist; }; diff --git a/src/ctx.c b/src/ctx.c index 03a0d72..9c82ef2 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -404,6 +404,11 @@ xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, ctx->connlist = NULL; ctx->loop_status = XMPP_LOOP_NOTSTARTED; + ctx->rand = xmpp_rand_new(ctx); + if (ctx->rand == NULL) { + xmpp_free(ctx, ctx); + ctx = NULL; + } } return ctx; @@ -418,6 +423,7 @@ xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, void xmpp_ctx_free(xmpp_ctx_t * const ctx) { /* mem and log are owned by their suppliers */ + xmpp_rand_free(ctx, ctx->rand); xmpp_free(ctx, ctx); /* pull the hole in after us */ } diff --git a/src/rand.c b/src/rand.c new file mode 100644 index 0000000..6777fe8 --- /dev/null +++ b/src/rand.c @@ -0,0 +1,306 @@ +/* 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 software is distributed under license and may not be copied, + * modified or distributed except as expressly authorized under the + * terms of the license contained in the file LICENSE.txt in this + * distribution. + */ + +/** @file + * Pseudo-random number generator. + * + * Implemented Hash_DRBG mechanism according to NIST SP 800-90A. + * Hash function is SHA1. + */ + +#include +#include +#include + +#include "common.h" +#include "ostypes.h" +#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); + 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); + + memcpy(seed_material, entropy_input, entropy_input_len); + 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; + 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); + 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 && \ + __arg != (type)-1) \ + { \ + *(type*)ptr = __arg; \ + ptr = (void*)((char*)ptr + sizeof(__arg)); \ + } \ +} while (0) + +static void xmpp_rand_reseed(xmpp_ctx_t *ctx) +{ + uint8_t entropy[ENTROPY_MAX]; + uint8_t *ptr = entropy; + const uint8_t *last = entropy + sizeof(entropy); + size_t len; + xmpp_rand_t *rand = ctx->rand; + + /* entropy: + * 1. time(2) + * 2. clock(3) if != -1 + * 3. xmpp_ctx_t address to make unique seed within one process + * 4. counter to make unique seed within one context + * 5. local ports of every connection in list (getsockname) + * 6. other non-constant info that can be retieved from socket + * + * rand(3) can't be used as it isn't thread-safe. + * XXX 5 and 6 not implemented yet. + */ + + ENTROPY_ACCUMULATE(ptr, last, time_t, time(NULL)); + ENTROPY_ACCUMULATE(ptr, last, clock_t, clock()); + ENTROPY_ACCUMULATE(ptr, last, void *, ctx); + ENTROPY_ACCUMULATE(ptr, last, unsigned, ++rand->reseed_count); + 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 = xmpp_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) +{ + xmpp_free(ctx, rand); +} + +void xmpp_rand_bytes(xmpp_ctx_t *ctx, uint8_t *output, size_t len) +{ + int rc; + xmpp_rand_t *rand = ctx->rand; + + rc = Hash_DRBG_Generate(&rand->ctx, output, len); + if (rc == RESEED_NEEDED) { + xmpp_rand_reseed(ctx); + rc = Hash_DRBG_Generate(&rand->ctx, output, len); + assert(rc == 0); + } +} + +int xmpp_rand(xmpp_ctx_t *ctx) +{ + int result; + + xmpp_rand_bytes(ctx, (uint8_t *)&result, sizeof(result)); + return result; +} + +void xmpp_rand_nonce(xmpp_ctx_t *ctx, char *output, size_t len) +{ + size_t i; + size_t rand_len = len / 2; + uint8_t rand_buf[rand_len]; + + /* current implementation returns printable HEX representation of + * a random buffer, however base64 encoding can be used instead; + * the only problem is that base64_encode() allocates memory and + * as result can fail. + */ + + xmpp_rand_bytes(ctx, rand_buf, rand_len); + for (i = 0; i < rand_len; ++i) { + xmpp_snprintf(output + i * 2, len, "%02x", (unsigned char)rand_buf[i]); + len -= 2; + } +} diff --git a/src/rand.h b/src/rand.h new file mode 100644 index 0000000..2f09dc8 --- /dev/null +++ b/src/rand.h @@ -0,0 +1,43 @@ +/* rand.h + * 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 software is distributed under license and may not be copied, + * modified or distributed except as expressly authorized under the + * terms of the license contained in the file LICENSE.txt in this + * distribution. + */ + +/** @file + * Pseudo-random number generator. + */ + +#ifndef __LIBSTROPHE_RAND_H__ +#define __LIBSTROPHE_RAND_H__ + +#include "strophe.h" +#include "ostypes.h" + +typedef struct _xmpp_rand_t xmpp_rand_t; + +xmpp_rand_t *xmpp_rand_new(xmpp_ctx_t *ctx); +void xmpp_rand_free(xmpp_ctx_t *ctx, xmpp_rand_t *rand); + +/** Analogue of rand(3). */ +int xmpp_rand(xmpp_ctx_t *ctx); + +/** Generates random bytes. */ +void xmpp_rand_bytes(xmpp_ctx_t *ctx, uint8_t *output, size_t len); + +/** Generates a nonce that is printable randomized string. + * + * @param len Number of bytes reserved for the output string, including + * end of line '\0'. + */ +void xmpp_rand_nonce(xmpp_ctx_t *ctx, char *output, size_t len); + +#endif /* __LIBSTROPHE_RAND_H__ */ diff --git a/src/sasl.c b/src/sasl.c index 3d83fd0..b580e54 100644 --- a/src/sasl.c +++ b/src/sasl.c @@ -23,6 +23,7 @@ #include "md5.h" #include "sha1.h" #include "scram.h" +#include "rand.h" #ifdef _WIN32 #define strtok_r strtok_s @@ -218,6 +219,7 @@ char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, struct MD5Context MD5; unsigned char digest[16], HA1[16], HA2[16]; char hex[32]; + char cnonce[13]; /* our digest response is Hex( KD( HEX(MD5(A1)), @@ -250,8 +252,8 @@ char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, /* add our response fields */ hash_add(table, "username", xmpp_strdup(ctx, node)); - /* TODO: generate a random cnonce */ - hash_add(table, "cnonce", xmpp_strdup(ctx, "00DEADBEEF00")); + xmpp_rand_nonce(ctx, cnonce, sizeof(cnonce)); + hash_add(table, "cnonce", xmpp_strdup(ctx, cnonce)); hash_add(table, "nc", xmpp_strdup(ctx, "00000001")); hash_add(table, "qop", xmpp_strdup(ctx, "auth")); value = xmpp_alloc(ctx, 5 + strlen(domain) + 1);