From d69226a3fc8e8448572d175e8d96ff7e817b1ebd Mon Sep 17 00:00:00 2001 From: Pauli Date: Fri, 29 Mar 2019 17:50:48 +1000 Subject: [PATCH] Add the FIPS related continuous random number generator (CRNG) testing. Refer to FIPS 140-2 section 4.9.2 Conditional Tests for details. The check is fairly simplistic, being for the entropy sources to not feed the DRBG the same block of seed material twice in a row. Only the first DRBG in a chain is subject to this check, latter DRBGs are assumed to be safely seeded via the earlier DRBGs. Reviewed-by: Richard Levitte (Merged from https://github.com/openssl/openssl/pull/8599) --- crypto/include/internal/rand_int.h | 8 ++ crypto/rand/build.info | 2 +- crypto/rand/drbg_lib.c | 7 +- crypto/rand/rand_crng_test.c | 109 ++++++++++++++++++++ crypto/rand/rand_lcl.h | 29 +++++- test/drbgtest.c | 154 ++++++++++++++++++++++++++++- 6 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 crypto/rand/rand_crng_test.c diff --git a/crypto/include/internal/rand_int.h b/crypto/include/internal/rand_int.h index 5098125f5e..b745393483 100644 --- a/crypto/include/internal/rand_int.h +++ b/crypto/include/internal/rand_int.h @@ -49,6 +49,14 @@ size_t rand_drbg_get_additional_data(RAND_POOL *pool, unsigned char **pout); void rand_drbg_cleanup_additional_data(RAND_POOL *pool, unsigned char *out); +/* CRNG test entropy filter callbacks. */ +size_t rand_crngt_get_entropy(RAND_DRBG *drbg, + unsigned char **pout, + int entropy, size_t min_len, size_t max_len, + int prediction_resistance); +void rand_crngt_cleanup_entropy(RAND_DRBG *drbg, + unsigned char *out, size_t outlen); + /* * RAND_POOL functions */ diff --git a/crypto/rand/build.info b/crypto/rand/build.info index d73326b4c2..d9c1fb7e33 100644 --- a/crypto/rand/build.info +++ b/crypto/rand/build.info @@ -1,6 +1,6 @@ LIBS=../../libcrypto SOURCE[../../libcrypto]=\ - randfile.c rand_lib.c rand_err.c rand_egd.c \ + randfile.c rand_lib.c rand_err.c rand_crng_test.c rand_egd.c \ rand_win.c rand_unix.c rand_vms.c drbg_lib.c drbg_ctr.c \ drbg_hash.c drbg_hmac.c diff --git a/crypto/rand/drbg_lib.c b/crypto/rand/drbg_lib.c index bd21797852..4e1e2ea5e0 100644 --- a/crypto/rand/drbg_lib.c +++ b/crypto/rand/drbg_lib.c @@ -67,7 +67,7 @@ static CRYPTO_THREAD_LOCAL private_drbg; /* NIST SP 800-90A DRBG recommends the use of a personalization string. */ -static const char ossl_pers_string[] = "OpenSSL NIST SP 800-90A DRBG"; +static const char ossl_pers_string[] = DRBG_DEFAULT_PERS_STRING; static CRYPTO_ONCE rand_drbg_init = CRYPTO_ONCE_STATIC_INIT; @@ -254,8 +254,13 @@ static RAND_DRBG *rand_drbg_new(int secure, drbg->parent = parent; if (parent == NULL) { +#ifdef FIPS_MODE + drbg->get_entropy = rand_crngt_get_entropy; + drbg->cleanup_entropy = rand_crngt_cleanup_entropy; +#else drbg->get_entropy = rand_drbg_get_entropy; drbg->cleanup_entropy = rand_drbg_cleanup_entropy; +#endif #ifndef RAND_DRBG_GET_RANDOM_NONCE drbg->get_nonce = rand_drbg_get_nonce; drbg->cleanup_nonce = rand_drbg_cleanup_nonce; diff --git a/crypto/rand/rand_crng_test.c b/crypto/rand/rand_crng_test.c new file mode 100644 index 0000000000..74a64ee531 --- /dev/null +++ b/crypto/rand/rand_crng_test.c @@ -0,0 +1,109 @@ +/* + * Copyright 2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Implementation of the FIPS 140-2 section 4.9.2 Conditional Tests. + */ + +#include +#include "internal/rand_int.h" +#include "internal/thread_once.h" +#include "rand_lcl.h" + +static RAND_POOL *crngt_pool; +static unsigned char *crngt_prev; + +int (*crngt_get_entropy)(unsigned char *) = &rand_crngt_get_entropy_cb; + +int rand_crngt_get_entropy_cb(unsigned char *buf) +{ + size_t n; + unsigned char *p; + + while ((n = rand_pool_acquire_entropy(crngt_pool)) != 0) + if (n >= CRNGT_BUFSIZ) { + p = rand_pool_detach(crngt_pool); + memcpy(crngt_prev, p, CRNGT_BUFSIZ); + rand_pool_reattach(crngt_pool, p); + return 1; + } + return 0; + +} +void rand_crngt_cleanup(void) +{ + rand_pool_free(crngt_pool); + OPENSSL_secure_free(crngt_prev); + crngt_pool = NULL; + crngt_prev = NULL; +} + +int rand_crngt_init(void) +{ + if ((crngt_pool = rand_pool_new(0, CRNGT_BUFSIZ, CRNGT_BUFSIZ)) == NULL) + return 0; + if ((crngt_prev = OPENSSL_secure_malloc(CRNGT_BUFSIZ)) != NULL + && crngt_get_entropy(crngt_prev)) + return 1; + rand_crngt_cleanup(); + return 0; +} + +static CRYPTO_ONCE rand_crngt_init_flag = CRYPTO_ONCE_STATIC_INIT; +DEFINE_RUN_ONCE_STATIC(do_rand_crngt_init) +{ + return OPENSSL_init_crypto(0, NULL) + && rand_crngt_init() + && OPENSSL_atexit(&rand_crngt_cleanup); +} + +int rand_crngt_single_init(void) +{ + return RUN_ONCE(&rand_crngt_init_flag, do_rand_crngt_init); +} + +size_t rand_crngt_get_entropy(RAND_DRBG *drbg, + unsigned char **pout, + int entropy, size_t min_len, size_t max_len, + int prediction_resistance) +{ + unsigned char buf[CRNGT_BUFSIZ]; + RAND_POOL *pool; + size_t q, r = 0, s, t = 0; + int attempts = 3; + + if (!RUN_ONCE(&rand_crngt_init_flag, do_rand_crngt_init)) + return 0; + + if ((pool = rand_pool_new(entropy, min_len, max_len)) == NULL) + return 0; + + while ((q = rand_pool_bytes_needed(pool, 1)) > 0 && attempts-- > 0) { + s = q > sizeof(buf) ? sizeof(buf) : q; + if (!crngt_get_entropy(buf) + || memcmp(crngt_prev, buf, CRNGT_BUFSIZ) == 0 + || !rand_pool_add(pool, buf, s, s * 8)) + goto err; + memcpy(crngt_prev, buf, CRNGT_BUFSIZ); + t += s; + attempts++; + } + r = t; + *pout = rand_pool_detach(pool); +err: + rand_pool_free(pool); + return r; +} + +void rand_crngt_cleanup_entropy(RAND_DRBG *drbg, + unsigned char *out, size_t outlen) +{ + OPENSSL_secure_clear_free(out, outlen); +} diff --git a/crypto/rand/rand_lcl.h b/crypto/rand/rand_lcl.h index 1b2bd88807..d793d28213 100644 --- a/crypto/rand/rand_lcl.h +++ b/crypto/rand/rand_lcl.h @@ -33,7 +33,15 @@ # define MASTER_RESEED_TIME_INTERVAL (60*60) /* 1 hour */ # define SLAVE_RESEED_TIME_INTERVAL (7*60) /* 7 minutes */ - +/* + * The number of bytes that constitutes an atomic lump of entropy with respect + * to the FIPS 140-2 section 4.9.2 Conditional Tests. The size is somewhat + * arbitrary, the smaller the value, the less entropy is consumed on first + * read but the higher the probability of the test failing by accident. + * + * The value is in bytes. + */ +#define CRNGT_BUFSIZ 16 /* * Maximum input size for the DRBG (entropy, nonce, personalization string) @@ -44,7 +52,8 @@ */ # define DRBG_MAX_LENGTH INT32_MAX - +/* The default nonce */ +# define DRBG_DEFAULT_PERS_STRING "OpenSSL NIST SP 800-90A DRBG" /* * Maximum allocation size for RANDOM_POOL buffers @@ -321,4 +330,20 @@ int drbg_ctr_init(RAND_DRBG *drbg); int drbg_hash_init(RAND_DRBG *drbg); int drbg_hmac_init(RAND_DRBG *drbg); +/* + * Entropy call back for the FIPS 140-2 section 4.9.2 Conditional Tests. + * These need to be exposed for the unit tests. + */ +int rand_crngt_get_entropy_cb(unsigned char *buf); +extern int (*crngt_get_entropy)(unsigned char *); +int rand_crngt_init(void); +void rand_crngt_cleanup(void); + +/* + * Expose the run once initialisation function for the unit tests because. + * they need to restart from scratch to validate the first block is skipped + * properly. + */ +int rand_crngt_single_init(void); + #endif diff --git a/test/drbgtest.c b/test/drbgtest.c index 362a1d2b49..652b93ad6b 100644 --- a/test/drbgtest.c +++ b/test/drbgtest.c @@ -152,6 +152,31 @@ static size_t kat_nonce(RAND_DRBG *drbg, unsigned char **pout, return t->noncelen; } + /* + * Disable CRNG testing if it is enabled. + * If the DRBG is ready or in an error state, this means an instantiate cycle + * for which the default personalisation string is used. + */ +static int disable_crngt(RAND_DRBG *drbg) +{ + static const char pers[] = DRBG_DEFAULT_PERS_STRING; + const int instantiate = drbg->state != DRBG_UNINITIALISED; + + if (drbg->get_entropy != rand_crngt_get_entropy) + return 1; + + if ((instantiate && !RAND_DRBG_uninstantiate(drbg)) + || !TEST_true(RAND_DRBG_set_callbacks(drbg, &rand_drbg_get_entropy, + &rand_drbg_cleanup_entropy, + &rand_drbg_get_nonce, + &rand_drbg_cleanup_nonce)) + || (instantiate + && !RAND_DRBG_instantiate(drbg, (const unsigned char *)pers, + sizeof(pers) - 1))) + return 0; + return 1; +} + static int uninstantiate(RAND_DRBG *drbg) { int ret = drbg == NULL ? 1 : RAND_DRBG_uninstantiate(drbg); @@ -177,7 +202,8 @@ static int single_kat(DRBG_SELFTEST_DATA *td) if (!TEST_ptr(drbg = RAND_DRBG_new(td->nid, td->flags, NULL))) return 0; if (!TEST_true(RAND_DRBG_set_callbacks(drbg, kat_entropy, NULL, - kat_nonce, NULL))) { + kat_nonce, NULL)) + || !TEST_true(disable_crngt(drbg))) { failures++; goto err; } @@ -295,7 +321,8 @@ static int error_check(DRBG_SELFTEST_DATA *td) unsigned int reseed_counter_tmp; int ret = 0; - if (!TEST_ptr(drbg = RAND_DRBG_new(td->nid, td->flags, NULL))) + if (!TEST_ptr(drbg = RAND_DRBG_new(td->nid, td->flags, NULL)) + || !TEST_true(disable_crngt(drbg))) goto err; /* @@ -708,6 +735,10 @@ static int test_rand_drbg_reseed(void) || !TEST_ptr_eq(private->parent, master)) return 0; + /* Disable CRNG testing for the master DRBG */ + if (!TEST_true(disable_crngt(master))) + return 0; + /* uninstantiate the three global DRBGs */ RAND_DRBG_uninstantiate(private); RAND_DRBG_uninstantiate(public); @@ -928,7 +959,8 @@ static int test_rand_seed(void) size_t rand_buflen; size_t required_seed_buflen = 0; - if (!TEST_ptr(master = RAND_DRBG_get0_master())) + if (!TEST_ptr(master = RAND_DRBG_get0_master()) + || !TEST_true(disable_crngt(master))) return 0; #ifdef OPENSSL_RAND_SEED_NONE @@ -983,7 +1015,8 @@ static int test_multi_set(void) RAND_DRBG *drbg = NULL; /* init drbg with default CTR initializer */ - if (!TEST_ptr(drbg = RAND_DRBG_new(0, 0, NULL))) + if (!TEST_ptr(drbg = RAND_DRBG_new(0, 0, NULL)) + || !TEST_true(disable_crngt(drbg))) goto err; /* change it to use hmac */ if (!TEST_true(RAND_DRBG_set(drbg, NID_sha1, RAND_DRBG_FLAG_HMAC))) @@ -1093,6 +1126,118 @@ static int test_set_defaults(void) && TEST_true(RAND_DRBG_uninstantiate(private)); } +/* + * A list of the FIPS DRGB types. + * Because of the way HMAC DRGBs are implemented, both the NID and flags + * are required. + */ +static const struct s_drgb_types { + int nid; + int flags; +} drgb_types[] = { + { NID_aes_128_ctr, 0 }, + { NID_aes_192_ctr, 0 }, + { NID_aes_256_ctr, 0 }, + { NID_sha1, 0 }, + { NID_sha224, 0 }, + { NID_sha256, 0 }, + { NID_sha384, 0 }, + { NID_sha512, 0 }, + { NID_sha512_224, 0 }, + { NID_sha512_256, 0 }, + { NID_sha3_224, 0 }, + { NID_sha3_256, 0 }, + { NID_sha3_384, 0 }, + { NID_sha3_512, 0 }, + { NID_sha1, RAND_DRBG_FLAG_HMAC }, + { NID_sha224, RAND_DRBG_FLAG_HMAC }, + { NID_sha256, RAND_DRBG_FLAG_HMAC }, + { NID_sha384, RAND_DRBG_FLAG_HMAC }, + { NID_sha512, RAND_DRBG_FLAG_HMAC }, + { NID_sha512_224, RAND_DRBG_FLAG_HMAC }, + { NID_sha512_256, RAND_DRBG_FLAG_HMAC }, + { NID_sha3_224, RAND_DRBG_FLAG_HMAC }, + { NID_sha3_256, RAND_DRBG_FLAG_HMAC }, + { NID_sha3_384, RAND_DRBG_FLAG_HMAC }, + { NID_sha3_512, RAND_DRBG_FLAG_HMAC }, +}; + +/* Six cases for each covers seed sizes up to 32 bytes */ +static const size_t crngt_num_cases = 6; + +static size_t crngt_case, crngt_idx; + +static int crngt_entropy_cb(unsigned char *buf) +{ + size_t i, z; + + if (!TEST_int_lt(crngt_idx, crngt_num_cases)) + return 0; + /* Generate a block of unique data unless this is the duplication point */ + z = crngt_idx++; + if (z > 0 && crngt_case == z) + z--; + for (i = 0; i < CRNGT_BUFSIZ; i++) + buf[i] = (unsigned char)(i + 'A' + z); + return 1; +} + +static int test_crngt(int n) +{ + const struct s_drgb_types *dt = drgb_types + n / crngt_num_cases; + RAND_DRBG *drbg = NULL; + unsigned char buff[100]; + size_t ent; + int res = 0; + int expect; + + if (!TEST_true(rand_crngt_single_init())) + return 0; + rand_crngt_cleanup(); + + if (!TEST_ptr(drbg = RAND_DRBG_new(dt->nid, dt->flags, NULL))) + return 0; + ent = (drbg->min_entropylen + CRNGT_BUFSIZ - 1) / CRNGT_BUFSIZ; + crngt_case = n % crngt_num_cases; + crngt_idx = 0; + crngt_get_entropy = &crngt_entropy_cb; + if (!TEST_true(rand_crngt_init())) + goto err; +#ifndef FIPS_MODE + if (!TEST_true(RAND_DRBG_set_callbacks(drbg, &rand_crngt_get_entropy, + &rand_crngt_cleanup_entropy, + &rand_drbg_get_nonce, + &rand_drbg_cleanup_nonce))) + goto err; +#endif + expect = crngt_case == 0 || crngt_case > ent; + if (!TEST_int_eq(RAND_DRBG_instantiate(drbg, NULL, 0), expect)) + goto err; + if (!expect) + goto fin; + if (!TEST_true(RAND_DRBG_generate(drbg, buff, sizeof(buff), 0, NULL, 0))) + goto err; + + expect = crngt_case == 0 || crngt_case > 2 * ent; + if (!TEST_int_eq(RAND_DRBG_reseed(drbg, NULL, 0, 0), expect)) + goto err; + if (!expect) + goto fin; + if (!TEST_true(RAND_DRBG_generate(drbg, buff, sizeof(buff), 0, NULL, 0))) + goto err; + +fin: + res = 1; +err: + if (!res) + TEST_note("DRBG %zd case %zd block %zd", n / crngt_num_cases, + crngt_case, crngt_idx); + uninstantiate(drbg); + RAND_DRBG_free(drbg); + crngt_get_entropy = &rand_crngt_get_entropy_cb; + return res; +} + int setup_tests(void) { app_data_index = RAND_DRBG_get_ex_new_index(0L, NULL, NULL, NULL, NULL); @@ -1107,5 +1252,6 @@ int setup_tests(void) #if defined(OPENSSL_THREADS) ADD_TEST(test_multi_thread); #endif + ADD_ALL_TESTS(test_crngt, crngt_num_cases * OSSL_NELEM(drgb_types)); return 1; } -- 2.34.1