diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt new file mode 100644 index 0000000000..3c2d63db1a --- /dev/null +++ b/ChangeLog.d/rng-cloning.txt @@ -0,0 +1,3 @@ +Features + * Applications can use the new function psa_random_reseed() to + request an immediate reseed of the PSA random generator. diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 89a38a8054..7d0b01ca4f 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -453,7 +453,7 @@ psa_status_t mbedtls_psa_inject_entropy(const uint8_t *seed, /**@}*/ -/** \defgroup psa_external_rng External random generator +/** \defgroup psa_rng Random generator * @{ */ @@ -502,6 +502,85 @@ psa_status_t mbedtls_psa_external_get_random( uint8_t *output, size_t output_size, size_t *output_length); #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +/** Force a reseed of the PSA random generator. + * + * The entropy source(s) are the ones configured at compile time. + * + * The random generator is always seeded automatically before use, and + * it is reseeded as needed based on the configured policy, so most + * applications do not need to call this function. + * + * The main reason to call this function is in scenarios where the process + * state is cloned (i.e. duplicated) while the random generator is active. + * In such scenarios, you must call this function in every clone of + * the original process before performing any cryptographic operation + * other than ones that do not use randomness (e.g. hash calculation, + * signature verification). For example: + * + * - If the process is part of a live virtual machine that is cloned, + * call this function after cloning so that the new instance has a + * distinct random generator state. + * - If the process is part of a hibernated image that may be resumed + * multiple times, call this function after resuming so that each + * resumed instance has a distinct random generator state. + * - If the process is cloned through the fork() system call, the + * library will detect it in most circumstances, so you generally do + * not need to call this function. This detection is based on a + * process ID (PID) change. You need to call this function in at least + * the parent or the child process in cases where the library might not + * observe a process ID change, such as: + * - If the child forks another process before invoking the random + * generator, but after the original process has died. In this case, + * it is rare but possible for the grandchild to have the same PID + * as the original process. + * - When using the Linux clone() system call with the `CLONE_NEWPID` + * flag to put the child process in its own PID namespace, and the + * original process has PID 1. + * - When the child is moved to a new or existing PID namespace before + * any call to the PSA random generator, and the PID in the child's + * namespace might match the PID of the original process. + * - When using the Linux clone3() system call with a `set_tid` array + * to force the PID of the new process. + * + * An additional consideration applies in configurations where there is no + * actual entropy source, only a nonvolatile seed (i.e. + * #MBEDTLS_ENTROPY_NV_SEED is enabled, #MBEDTLS_NO_PLATFORM_ENTROPY is + * enabled and #MBEDTLS_ENTROPY_HARDWARE_ALT is disabled). + * In such configurations, simply calling psa_random_reseed() in multiple + * cloned processes would result in the same random generator state in + * all the clones. To avoid this, in such configurations, you must pass + * a unique \p perso string in every clone. + * + * \note This function has no effect when the compilation option + * #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG is enabled. + * + * \note In client-server builds, this function may not be available + * from clients, since the decision to reseed is generally based + * on the server state. + * + * \param[in] perso A personalization string, i.e. a byte string to + * inject into the random generator state in addition + * to entropy obtained from the normal source(s). + * In most cases, it is fine for \c perso to be + * empty. The main use case for a personalization + * string is when the random generator state is cloned, + * as described above, and there is no actual entropy + * source. + * \param perso_size Length of \c perso in bytes. + * + * \retval #PSA_SUCCESS + * The reseed succeeded. + * \retval #PSA_ERROR_BAD_STATE + * The PSA random generator is not active. + * \retval #PSA_ERROR_NOT_SUPPORTED + * PSA uses an external random generator because the compilation + * option #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG is enabled. This + * configuration does not support explicit reseeding. + * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY + * The entropy source failed. + */ +psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size); + /**@}*/ /** \defgroup psa_builtin_keys Built-in keys diff --git a/library/psa_crypto.c b/library/psa_crypto.c index ec6b685dd3..7951268186 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -8001,6 +8001,28 @@ static psa_status_t mbedtls_psa_random_seed(mbedtls_psa_random_context_t *rng) #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } +psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size) +{ + GUARD_MODULE_INITIALIZED; +#if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + (void) perso; + (void) perso_size; + return PSA_ERROR_NOT_SUPPORTED; +#else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +#if defined(MBEDTLS_THREADING_C) + if (mbedtls_mutex_lock(&mbedtls_threading_psa_rngdata_mutex) != 0) { + return PSA_ERROR_SERVICE_FAILURE; + } +#endif /* defined(MBEDTLS_THREADING_C) */ + int ret = mbedtls_psa_drbg_reseed(&global_data.rng.drbg, + perso, perso_size); +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_unlock(&mbedtls_threading_psa_rngdata_mutex); +#endif /* defined(MBEDTLS_THREADING_C) */ + return mbedtls_to_psa_error(ret); +#endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +} + psa_status_t psa_generate_random(uint8_t *output_external, size_t output_size) { diff --git a/tests/suites/test_suite_psa_crypto_entropy.data b/tests/suites/test_suite_psa_crypto_entropy.data index 6a9f239b6b..e5f7243388 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.data +++ b/tests/suites/test_suite_psa_crypto_entropy.data @@ -44,6 +44,27 @@ entropy_from_nv_seed:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:PSA_ERROR_INSUFFICIENT_ENTRO NV seed only: just enough entropy_from_nv_seed:ENTROPY_MIN_NV_SEED_SIZE:PSA_SUCCESS +Explicit reseed: basic tests +reseed_basic: + +Explicit reseed: entropy consumption +reseed_consumption: + +Explicit reseed: uniqueness tests (0 = 0) +reseed_uniqueness:"":"" + +Explicit reseed: uniqueness tests (0 != 5) +reseed_uniqueness:"":"706572736f" + +Explicit reseed: uniqueness tests (5 = 5) +reseed_uniqueness:"706572736f":"706572736f" + +Explicit reseed: uniqueness tests (5 != 5) +reseed_uniqueness:"706572736f":"706572736e" + +Explicit reseed: uniqueness tests (5 != 10) +reseed_uniqueness:"706572736f":"706572736f706572736f" + PSA external RNG failure: generate random and key external_rng_failure_generate: diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index ce10affa67..9709ffa9ad 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -37,6 +37,7 @@ typedef struct { size_t *length_sequence; size_t step; } fake_entropy_state_t; + static int fake_entropy_source(void *state_arg, unsigned char *output, size_t len, size_t *olen) @@ -113,6 +114,39 @@ static void custom_entropy_init(mbedtls_entropy_context *ctx) } } +static size_t fake_entropy_lengths[] = { + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_BLOCK_SIZE, +}; + +/** Initialize PSA with a deterministic RNG seed. + * + * \param max_entropy_queries Maximum number of queries to the entropy source. + * Once this number has been reached, the + * entropy source will fail. + */ +static int psa_init_deterministic(size_t max_entropy_queries) +{ + TEST_LE_U(max_entropy_queries, ARRAY_LENGTH(fake_entropy_lengths)); + + fake_entropy_state.threshold = MBEDTLS_ENTROPY_BLOCK_SIZE; + fake_entropy_state.step = 0; + fake_entropy_state.max_steps = max_entropy_queries; + fake_entropy_state.length_sequence = fake_entropy_lengths; + + custom_entropy_sources_mask = ENTROPY_SOURCE_FAKE; + PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( + custom_entropy_init, mbedtls_entropy_free)); + PSA_INIT(); + return 1; + +exit: + return 0; +} #endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */ /* Calculating the minimum allowed entropy size in bytes */ @@ -285,6 +319,130 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void reseed_basic() +{ + uint8_t random[10]; + const uint8_t perso[5] = { 'p', 'e', 'r', 's', 'o' }; + + TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_generate_random(random, sizeof(random)), PSA_ERROR_BAD_STATE); + + PSA_INIT(); + + PSA_ASSERT(psa_random_reseed(NULL, 0)); + PSA_ASSERT(psa_random_reseed(perso, sizeof(perso))); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + + mbedtls_psa_crypto_free(); + + TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_generate_random(random, sizeof(random)), PSA_ERROR_BAD_STATE); + +exit: + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +/* Check that reseeding consumes entropy. + * + * For simplicity, this test function assumes that the DRBG has prediction + * resistance turned off, so the few RNG queries in this function don't + * trigger a reseed. + */ +void reseed_consumption() +{ + uint8_t random[10] = { 0 }; + + if (!psa_init_deterministic(3)) { + goto exit; + } + + /* Explicit reseed, consumes 1 entropy block, 1 remaining */ + PSA_ASSERT(psa_random_reseed(NULL, 0)); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + + /* Explicit reseed, consumes 1 entropy block, 0 remaining */ + PSA_ASSERT(psa_random_reseed(NULL, 0)); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + + /* All entropy blocks are now consumed */ + TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_INSUFFICIENT_ENTROPY); + + /* The random generator is still fine after failing to reseed + * explicitly. Should it be? */ + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + +exit: + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void reseed_uniqueness(data_t *perso1, data_t *perso2) +{ + uint8_t random0[10] = { 0 }; + uint8_t random1[10] = { 0 }; + uint8_t random2[10] = { 0 }; + uint8_t random_again[10] = { 0 }; + + /* Reference: no reseed */ + if (!psa_init_deterministic(3)) { + goto exit; + } + PSA_ASSERT(psa_generate_random(random0, sizeof(random0))); + mbedtls_psa_crypto_free(); + + /* Reference: no reseed, again */ + if (!psa_init_deterministic(3)) { + goto exit; + } + PSA_ASSERT(psa_generate_random(random_again, sizeof(random_again))); + mbedtls_psa_crypto_free(); + TEST_MEMORY_COMPARE(random0, sizeof(random0), + random_again, sizeof(random_again)); + + /* Reseed with a personalization string */ + if (!psa_init_deterministic(3)) { + goto exit; + } + PSA_ASSERT(psa_random_reseed(perso1->x, perso1->len)); + PSA_ASSERT(psa_generate_random(random1, sizeof(random1))); + mbedtls_psa_crypto_free(); + TEST_ASSERT(memcmp(random0, random1, sizeof(random1)) != 0); + + /* Reseed with a personalization string (same or different) */ + if (!psa_init_deterministic(3)) { + goto exit; + } + PSA_ASSERT(psa_random_reseed(perso2->x, perso2->len)); + PSA_ASSERT(psa_generate_random(random2, sizeof(random2))); + mbedtls_psa_crypto_free(); + if (perso1->len == perso2->len && + memcmp(perso1->x, perso2->x, perso1->len) == 0) { + TEST_MEMORY_COMPARE(random1, sizeof(random1), + random2, sizeof(random2)); + } else { + TEST_ASSERT(memcmp(random1, random2, sizeof(random2)) != 0); + } + + /* Reseed twice */ + if (!psa_init_deterministic(3)) { + goto exit; + } + PSA_ASSERT(psa_random_reseed(perso1->x, perso1->len)); + PSA_ASSERT(psa_random_reseed(perso1->x, perso1->len)); + PSA_ASSERT(psa_generate_random(random2, sizeof(random2))); + mbedtls_psa_crypto_free(); + TEST_ASSERT(memcmp(random0, random2, sizeof(random2)) != 0); + TEST_ASSERT(memcmp(random1, random2, sizeof(random2)) != 0); + +exit: + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ void external_rng_failure_generate() { @@ -300,6 +458,8 @@ void external_rng_failure_generate() PSA_ASSERT(psa_generate_key(&attributes, &key)); PSA_ASSERT(psa_destroy_key(key)); + TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_NOT_SUPPORTED); + mbedtls_test_disable_insecure_external_rng(); TEST_EQUAL(PSA_ERROR_INSUFFICIENT_ENTROPY, psa_generate_random(output, sizeof(output)));