From 178cda8cc693492f0657200190146732fed9bf40 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 23 Jan 2026 20:15:08 +0100 Subject: [PATCH 01/20] Move entropy-related tests to test_suite_psa_crypto_entropy They were in test_suite_psa_crypto_init, but their only connection to init is that RNG setup is part of init. When testing how the RNG is set up, the fact that it happens during init is incidental, what matters is the difficulties around collecting entropy. Signed-off-by: Gilles Peskine --- .../suites/test_suite_psa_crypto_entropy.data | 48 ++++ .../test_suite_psa_crypto_entropy.function | 217 ++++++++++++++++++ tests/suites/test_suite_psa_crypto_init.data | 49 ---- .../test_suite_psa_crypto_init.function | 214 +---------------- 4 files changed, 266 insertions(+), 262 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto_entropy.data b/tests/suites/test_suite_psa_crypto_entropy.data index 68a7f984e3..6a9f239b6b 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.data +++ b/tests/suites/test_suite_psa_crypto_entropy.data @@ -1,3 +1,49 @@ +Create NV seed file +create_nv_seed: + +Custom entropy sources: all standard +custom_entropy_sources:0x0000ffff:PSA_SUCCESS + +# MBEDTLS_PSA_INJECT_ENTROPY means that a source of entropy (the seed file) +# is effectively always available. +Custom entropy sources: none +depends_on:!MBEDTLS_PSA_INJECT_ENTROPY +custom_entropy_sources:0:PSA_ERROR_INSUFFICIENT_ENTROPY + +Fake entropy: never returns anything +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:0:0:0:PSA_ERROR_INSUFFICIENT_ENTROPY + +Fake entropy: less than the block size +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:-1:-1:-1:PSA_ERROR_INSUFFICIENT_ENTROPY + +Fake entropy: not enough for a nonce +depends_on:ENTROPY_NONCE_LEN != 0 +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:ENTROPY_NONCE_LEN - 1:-1:-1:-1:PSA_ERROR_INSUFFICIENT_ENTROPY + +Fake entropy: one block eventually +depends_on:ENTROPY_NONCE_LEN == 0 +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:0:0:MBEDTLS_ENTROPY_BLOCK_SIZE:PSA_SUCCESS + +Fake entropy: one block in two steps +depends_on:ENTROPY_NONCE_LEN == 0 +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:1:-1:-1:PSA_SUCCESS + +Fake entropy: more than one block in two steps +depends_on:ENTROPY_NONCE_LEN == 0 +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:-1:-1:PSA_SUCCESS + +Fake entropy: two blocks eventually +fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:MBEDTLS_ENTROPY_BLOCK_SIZE:0:MBEDTLS_ENTROPY_BLOCK_SIZE:PSA_SUCCESS + +NV seed only: less than minimum +entropy_from_nv_seed:MBEDTLS_ENTROPY_MIN_PLATFORM - 1:PSA_ERROR_INSUFFICIENT_ENTROPY + +NV seed only: less than one block +entropy_from_nv_seed:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:PSA_ERROR_INSUFFICIENT_ENTROPY + +NV seed only: just enough +entropy_from_nv_seed:ENTROPY_MIN_NV_SEED_SIZE:PSA_SUCCESS + PSA external RNG failure: generate random and key external_rng_failure_generate: @@ -43,3 +89,5 @@ validate_entropy_seed_injection:MBEDTLS_ENTROPY_BLOCK_SIZE-1:PSA_ERROR_INVALID_A PSA validate entropy injection: before and after crypto_init run_entropy_inject_with_crypto_init: +Recreate NV seed file +create_nv_seed: diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index 4d5eda2baf..ce10affa67 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -4,9 +4,117 @@ #include +/* Some tests in this module configure entropy sources. */ +#include "psa_crypto_invasive.h" + #include "mbedtls/entropy.h" #include "entropy_poll.h" +#define ENTROPY_MIN_NV_SEED_SIZE \ + MAX(MBEDTLS_ENTROPY_MIN_PLATFORM, MBEDTLS_ENTROPY_BLOCK_SIZE) + +#include "psa_crypto_random_impl.h" +#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE) +/* PSA crypto uses the HMAC_DRBG module. It reads from the entropy source twice: + * once for the initial entropy and once for a nonce. The nonce length is + * half the entropy length. For SHA-256, SHA-384 or SHA-512, the + * entropy length is 256 per the documentation of mbedtls_hmac_drbg_seed(), + * and PSA crypto doesn't support other hashes for HMAC_DRBG. */ +#define ENTROPY_NONCE_LEN (256 / 2) +#else +/* PSA crypto uses the CTR_DRBG module. In some configurations, it needs + * to read from the entropy source twice: once for the initial entropy + * and once for a nonce. */ +#include "mbedtls/ctr_drbg.h" +#define ENTROPY_NONCE_LEN MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN +#endif + +#if !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + +typedef struct { + size_t threshold; /* Minimum bytes to make mbedtls_entropy_func happy */ + size_t max_steps; + 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) +{ + fake_entropy_state_t *state = state_arg; + size_t i; + + if (state->step >= state->max_steps) { + return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; + } + + *olen = MIN(len, state->length_sequence[state->step]); + for (i = 0; i < *olen; i++) { + output[i] = i; + } + ++state->step; + return 0; +} + +#define ENTROPY_SOURCE_PLATFORM 0x00000001 +#define ENTROPY_SOURCE_TIMING 0x00000002 +#define ENTROPY_SOURCE_HARDWARE 0x00000004 +#define ENTROPY_SOURCE_NV_SEED 0x00000008 +#define ENTROPY_SOURCE_FAKE 0x40000000 + +static uint32_t custom_entropy_sources_mask; +static fake_entropy_state_t fake_entropy_state; + +/* This is a modified version of mbedtls_entropy_init() from entropy.c + * which chooses entropy sources dynamically. */ +static void custom_entropy_init(mbedtls_entropy_context *ctx) +{ + ctx->source_count = 0; + memset(ctx->source, 0, sizeof(ctx->source)); + +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_init(&ctx->mutex); +#endif + + ctx->accumulator_started = 0; + mbedtls_md_init(&ctx->accumulator); + +#if !defined(MBEDTLS_NO_PLATFORM_ENTROPY) + if (custom_entropy_sources_mask & ENTROPY_SOURCE_PLATFORM) { + mbedtls_entropy_add_source(ctx, mbedtls_platform_entropy_poll, NULL, + MBEDTLS_ENTROPY_MIN_PLATFORM, + MBEDTLS_ENTROPY_SOURCE_STRONG); + } +#endif +#if defined(MBEDTLS_ENTROPY_HARDWARE_ALT) + if (custom_entropy_sources_mask & ENTROPY_SOURCE_HARDWARE) { + mbedtls_entropy_add_source(ctx, mbedtls_hardware_poll, NULL, + MBEDTLS_ENTROPY_MIN_HARDWARE, + MBEDTLS_ENTROPY_SOURCE_STRONG); + } +#endif +#if defined(MBEDTLS_ENTROPY_NV_SEED) + if (custom_entropy_sources_mask & ENTROPY_SOURCE_NV_SEED) { + mbedtls_entropy_add_source(ctx, mbedtls_nv_seed_poll, NULL, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_SOURCE_STRONG); + ctx->initial_entropy_run = 0; + } else { + /* Skip the NV seed even though it's compiled in. */ + ctx->initial_entropy_run = 1; + } +#endif + + if (custom_entropy_sources_mask & ENTROPY_SOURCE_FAKE) { + mbedtls_entropy_add_source(ctx, + fake_entropy_source, &fake_entropy_state, + fake_entropy_state.threshold, + MBEDTLS_ENTROPY_SOURCE_STRONG); + } +} + +#endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */ + /* Calculating the minimum allowed entropy size in bytes */ #define MBEDTLS_PSA_INJECT_ENTROPY_MIN_SIZE MAX(MBEDTLS_ENTROPY_MIN_PLATFORM, \ MBEDTLS_ENTROPY_BLOCK_SIZE) @@ -68,6 +176,115 @@ psa_status_t remove_seed_file(void) /* END_HEADER */ +/* BEGIN_DEPENDENCIES + * depends_on:MBEDTLS_PSA_CRYPTO_C + * END_DEPENDENCIES + */ + +/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void create_nv_seed() +{ + static unsigned char seed[ENTROPY_MIN_NV_SEED_SIZE]; + TEST_ASSERT(mbedtls_nv_seed_write(seed, sizeof(seed)) >= 0); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void custom_entropy_sources(int sources_arg, int expected_init_status_arg) +{ + psa_status_t expected_init_status = expected_init_status_arg; + uint8_t random[10] = { 0 }; + + custom_entropy_sources_mask = sources_arg; + PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( + custom_entropy_init, mbedtls_entropy_free)); + + TEST_EQUAL(psa_crypto_init(), expected_init_status); + if (expected_init_status != PSA_SUCCESS) { + goto exit; + } + + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + +exit: + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void fake_entropy_source(int threshold, + int amount1, + int amount2, + int amount3, + int amount4, + int expected_init_status_arg) +{ + psa_status_t expected_init_status = expected_init_status_arg; + uint8_t random[10] = { 0 }; + size_t lengths[4]; + + fake_entropy_state.threshold = threshold; + fake_entropy_state.step = 0; + fake_entropy_state.max_steps = 0; + if (amount1 >= 0) { + lengths[fake_entropy_state.max_steps++] = amount1; + } + if (amount2 >= 0) { + lengths[fake_entropy_state.max_steps++] = amount2; + } + if (amount3 >= 0) { + lengths[fake_entropy_state.max_steps++] = amount3; + } + if (amount4 >= 0) { + lengths[fake_entropy_state.max_steps++] = amount4; + } + fake_entropy_state.length_sequence = lengths; + + custom_entropy_sources_mask = ENTROPY_SOURCE_FAKE; + PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( + custom_entropy_init, mbedtls_entropy_free)); + + TEST_EQUAL(psa_crypto_init(), expected_init_status); + if (expected_init_status != PSA_SUCCESS) { + goto exit; + } + + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + +exit: + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void entropy_from_nv_seed(int seed_size_arg, + int expected_init_status_arg) +{ + psa_status_t expected_init_status = expected_init_status_arg; + uint8_t random[10] = { 0 }; + uint8_t *seed = NULL; + size_t seed_size = seed_size_arg; + + TEST_CALLOC(seed, seed_size); + TEST_ASSERT(mbedtls_nv_seed_write(seed, seed_size) >= 0); + + custom_entropy_sources_mask = ENTROPY_SOURCE_NV_SEED; + PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( + custom_entropy_init, mbedtls_entropy_free)); + + TEST_EQUAL(psa_crypto_init(), expected_init_status); + if (expected_init_status != PSA_SUCCESS) { + goto exit; + } + + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + +exit: + mbedtls_free(seed); + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ void external_rng_failure_generate() { diff --git a/tests/suites/test_suite_psa_crypto_init.data b/tests/suites/test_suite_psa_crypto_init.data index 147d03fbed..1525ab1e5b 100644 --- a/tests/suites/test_suite_psa_crypto_init.data +++ b/tests/suites/test_suite_psa_crypto_init.data @@ -1,6 +1,3 @@ -Create NV seed file -create_nv_seed: - PSA init/deinit init_deinit:2 @@ -24,49 +21,3 @@ validate_module_init_generate_random:1 No key slot access after deinit validate_module_init_key_based:1 - -Custom entropy sources: all standard -custom_entropy_sources:0x0000ffff:PSA_SUCCESS - -# MBEDTLS_PSA_INJECT_ENTROPY means that a source of entropy (the seed file) -# is effectively always available. -Custom entropy sources: none -depends_on:!MBEDTLS_PSA_INJECT_ENTROPY -custom_entropy_sources:0:PSA_ERROR_INSUFFICIENT_ENTROPY - -Fake entropy: never returns anything -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:0:0:0:PSA_ERROR_INSUFFICIENT_ENTROPY - -Fake entropy: less than the block size -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:-1:-1:-1:PSA_ERROR_INSUFFICIENT_ENTROPY - -Fake entropy: not enough for a nonce -depends_on:ENTROPY_NONCE_LEN != 0 -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:ENTROPY_NONCE_LEN - 1:-1:-1:-1:PSA_ERROR_INSUFFICIENT_ENTROPY - -Fake entropy: one block eventually -depends_on:ENTROPY_NONCE_LEN == 0 -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:0:0:MBEDTLS_ENTROPY_BLOCK_SIZE:PSA_SUCCESS - -Fake entropy: one block in two steps -depends_on:ENTROPY_NONCE_LEN == 0 -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:1:-1:-1:PSA_SUCCESS - -Fake entropy: more than one block in two steps -depends_on:ENTROPY_NONCE_LEN == 0 -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:-1:-1:PSA_SUCCESS - -Fake entropy: two blocks eventually -fake_entropy_source:MBEDTLS_ENTROPY_BLOCK_SIZE:0:MBEDTLS_ENTROPY_BLOCK_SIZE:0:MBEDTLS_ENTROPY_BLOCK_SIZE:PSA_SUCCESS - -NV seed only: less than minimum -entropy_from_nv_seed:MBEDTLS_ENTROPY_MIN_PLATFORM - 1:PSA_ERROR_INSUFFICIENT_ENTROPY - -NV seed only: less than one block -entropy_from_nv_seed:MBEDTLS_ENTROPY_BLOCK_SIZE - 1:PSA_ERROR_INSUFFICIENT_ENTROPY - -NV seed only: just enough -entropy_from_nv_seed:ENTROPY_MIN_NV_SEED_SIZE:PSA_SUCCESS - -Recreate NV seed file -create_nv_seed: diff --git a/tests/suites/test_suite_psa_crypto_init.function b/tests/suites/test_suite_psa_crypto_init.function index 954560a24e..6b4b18577b 100644 --- a/tests/suites/test_suite_psa_crypto_init.function +++ b/tests/suites/test_suite_psa_crypto_init.function @@ -2,12 +2,9 @@ #include #include "psa_crypto_core.h" -/* Some tests in this module configure entropy sources. */ +/* For mbedtls_psa_crypto_configure_entropy_sources() */ #include "psa_crypto_invasive.h" -#include "mbedtls/entropy.h" -#include "entropy_poll.h" - static int check_stats(void) { mbedtls_psa_stats_t stats; @@ -25,111 +22,6 @@ exit: return 0; } -#define ENTROPY_MIN_NV_SEED_SIZE \ - MAX(MBEDTLS_ENTROPY_MIN_PLATFORM, MBEDTLS_ENTROPY_BLOCK_SIZE) - -#include "psa_crypto_random_impl.h" -#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE) -/* PSA crypto uses the HMAC_DRBG module. It reads from the entropy source twice: - * once for the initial entropy and once for a nonce. The nonce length is - * half the entropy length. For SHA-256, SHA-384 or SHA-512, the - * entropy length is 256 per the documentation of mbedtls_hmac_drbg_seed(), - * and PSA crypto doesn't support other hashes for HMAC_DRBG. */ -#define ENTROPY_NONCE_LEN (256 / 2) -#else -/* PSA crypto uses the CTR_DRBG module. In some configurations, it needs - * to read from the entropy source twice: once for the initial entropy - * and once for a nonce. */ -#include "mbedtls/ctr_drbg.h" -#define ENTROPY_NONCE_LEN MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN -#endif - -#if !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) - -typedef struct { - size_t threshold; /* Minimum bytes to make mbedtls_entropy_func happy */ - size_t max_steps; - 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) -{ - fake_entropy_state_t *state = state_arg; - size_t i; - - if (state->step >= state->max_steps) { - return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; - } - - *olen = MIN(len, state->length_sequence[state->step]); - for (i = 0; i < *olen; i++) { - output[i] = i; - } - ++state->step; - return 0; -} - -#define ENTROPY_SOURCE_PLATFORM 0x00000001 -#define ENTROPY_SOURCE_TIMING 0x00000002 -#define ENTROPY_SOURCE_HARDWARE 0x00000004 -#define ENTROPY_SOURCE_NV_SEED 0x00000008 -#define ENTROPY_SOURCE_FAKE 0x40000000 - -static uint32_t custom_entropy_sources_mask; -static fake_entropy_state_t fake_entropy_state; - -/* This is a modified version of mbedtls_entropy_init() from entropy.c - * which chooses entropy sources dynamically. */ -static void custom_entropy_init(mbedtls_entropy_context *ctx) -{ - ctx->source_count = 0; - memset(ctx->source, 0, sizeof(ctx->source)); - -#if defined(MBEDTLS_THREADING_C) - mbedtls_mutex_init(&ctx->mutex); -#endif - - ctx->accumulator_started = 0; - mbedtls_md_init(&ctx->accumulator); - -#if !defined(MBEDTLS_NO_PLATFORM_ENTROPY) - if (custom_entropy_sources_mask & ENTROPY_SOURCE_PLATFORM) { - mbedtls_entropy_add_source(ctx, mbedtls_platform_entropy_poll, NULL, - MBEDTLS_ENTROPY_MIN_PLATFORM, - MBEDTLS_ENTROPY_SOURCE_STRONG); - } -#endif -#if defined(MBEDTLS_ENTROPY_HARDWARE_ALT) - if (custom_entropy_sources_mask & ENTROPY_SOURCE_HARDWARE) { - mbedtls_entropy_add_source(ctx, mbedtls_hardware_poll, NULL, - MBEDTLS_ENTROPY_MIN_HARDWARE, - MBEDTLS_ENTROPY_SOURCE_STRONG); - } -#endif -#if defined(MBEDTLS_ENTROPY_NV_SEED) - if (custom_entropy_sources_mask & ENTROPY_SOURCE_NV_SEED) { - mbedtls_entropy_add_source(ctx, mbedtls_nv_seed_poll, NULL, - MBEDTLS_ENTROPY_BLOCK_SIZE, - MBEDTLS_ENTROPY_SOURCE_STRONG); - ctx->initial_entropy_run = 0; - } else { - /* Skip the NV seed even though it's compiled in. */ - ctx->initial_entropy_run = 1; - } -#endif - - if (custom_entropy_sources_mask & ENTROPY_SOURCE_FAKE) { - mbedtls_entropy_add_source(ctx, - fake_entropy_source, &fake_entropy_state, - fake_entropy_state.threshold, - MBEDTLS_ENTROPY_SOURCE_STRONG); - } -} - -#endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */ - #if defined MBEDTLS_THREADING_PTHREAD typedef struct { @@ -190,14 +82,6 @@ exit: * END_DEPENDENCIES */ -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ -void create_nv_seed() -{ - static unsigned char seed[ENTROPY_MIN_NV_SEED_SIZE]; - TEST_ASSERT(mbedtls_nv_seed_write(seed, sizeof(seed)) >= 0); -} -/* END_CASE */ - /* BEGIN_CASE */ void init_deinit(int count) { @@ -333,99 +217,3 @@ void validate_module_init_key_based(int count) TEST_ASSERT(mbedtls_svc_key_id_is_null(key)); } /* END_CASE */ - -/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ -void custom_entropy_sources(int sources_arg, int expected_init_status_arg) -{ - psa_status_t expected_init_status = expected_init_status_arg; - uint8_t random[10] = { 0 }; - - custom_entropy_sources_mask = sources_arg; - PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( - custom_entropy_init, mbedtls_entropy_free)); - - TEST_EQUAL(psa_crypto_init(), expected_init_status); - if (expected_init_status != PSA_SUCCESS) { - goto exit; - } - - PSA_ASSERT(psa_generate_random(random, sizeof(random))); - -exit: - PSA_DONE(); -} -/* END_CASE */ - -/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ -void fake_entropy_source(int threshold, - int amount1, - int amount2, - int amount3, - int amount4, - int expected_init_status_arg) -{ - psa_status_t expected_init_status = expected_init_status_arg; - uint8_t random[10] = { 0 }; - size_t lengths[4]; - - fake_entropy_state.threshold = threshold; - fake_entropy_state.step = 0; - fake_entropy_state.max_steps = 0; - if (amount1 >= 0) { - lengths[fake_entropy_state.max_steps++] = amount1; - } - if (amount2 >= 0) { - lengths[fake_entropy_state.max_steps++] = amount2; - } - if (amount3 >= 0) { - lengths[fake_entropy_state.max_steps++] = amount3; - } - if (amount4 >= 0) { - lengths[fake_entropy_state.max_steps++] = amount4; - } - fake_entropy_state.length_sequence = lengths; - - custom_entropy_sources_mask = ENTROPY_SOURCE_FAKE; - PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( - custom_entropy_init, mbedtls_entropy_free)); - - TEST_EQUAL(psa_crypto_init(), expected_init_status); - if (expected_init_status != PSA_SUCCESS) { - goto exit; - } - - PSA_ASSERT(psa_generate_random(random, sizeof(random))); - -exit: - PSA_DONE(); -} -/* END_CASE */ - -/* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ -void entropy_from_nv_seed(int seed_size_arg, - int expected_init_status_arg) -{ - psa_status_t expected_init_status = expected_init_status_arg; - uint8_t random[10] = { 0 }; - uint8_t *seed = NULL; - size_t seed_size = seed_size_arg; - - TEST_CALLOC(seed, seed_size); - TEST_ASSERT(mbedtls_nv_seed_write(seed, seed_size) >= 0); - - custom_entropy_sources_mask = ENTROPY_SOURCE_NV_SEED; - PSA_ASSERT(mbedtls_psa_crypto_configure_entropy_sources( - custom_entropy_init, mbedtls_entropy_free)); - - TEST_EQUAL(psa_crypto_init(), expected_init_status); - if (expected_init_status != PSA_SUCCESS) { - goto exit; - } - - PSA_ASSERT(psa_generate_random(random, sizeof(random))); - -exit: - mbedtls_free(seed); - PSA_DONE(); -} -/* END_CASE */ From 2a92659034b86dcad8f3327b64038f7711b191d0 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 29 Jan 2026 18:53:04 +0100 Subject: [PATCH 02/20] Define derived config macros for entropy sources Define `MBEDTLS_ENTROPY_TRUE_SOURCES` and `MBEDTLS_ENTROPY_HAVE_SOURCES` similarly to TF-PSA-Crypto 1.0. Also define `MBEDTLS_ENTROPY_HAVE_TRUE_SOURCES` for test function dependencies. Signed-off-by: Gilles Peskine --- include/mbedtls/config_adjust_legacy_crypto.h | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/include/mbedtls/config_adjust_legacy_crypto.h b/include/mbedtls/config_adjust_legacy_crypto.h index 331ac9b2da..66bad0c812 100644 --- a/include/mbedtls/config_adjust_legacy_crypto.h +++ b/include/mbedtls/config_adjust_legacy_crypto.h @@ -48,6 +48,49 @@ #endif #endif /* _MINGW32__ || (_MSC_VER && (_MSC_VER <= 1900)) */ +/* The number of "true" entropy sources (excluding NV seed). + * This must be consistent with mbedtls_entropy_init() in entropy.c. + */ +/* Define auxiliary macros, because in standard C, defined(xxx) is only + * allowed directly on an #if or #elif line, not in recursive expansion. */ +#if defined(MBEDTLS_ENTROPY_HARDWARE_ALT) +#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 1 +#else +#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 0 +#endif +#if defined(MBEDTLS_NO_PLATFORM_ENTROPY) +#define MBEDTLS_PLATFORM_ENTROPY_DEFINED 1 +#else +#define MBEDTLS_PLATFORM_ENTROPY_DEFINED 0 +#endif + +#define MBEDTLS_ENTROPY_TRUE_SOURCES ( \ + MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED + \ + MBEDTLS_PLATFORM_ENTROPY_DEFINED + \ + 0) + +/* Whether there is at least one entropy source for the entropy module. + * + * Note that when MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG is enabled, the entropy + * module is unused and the configuration will typically not include any + * entropy source, so this macro will typically remain undefined. + */ +#if defined(MBEDTLS_ENTROPY_NV_SEED) +#define MBEDTLS_ENTROPY_HAVE_SOURCES (MBEDTLS_ENTROPY_TRUE_SOURCES + 1) +#elif MBEDTLS_ENTROPY_TRUE_SOURCES != 0 +#define MBEDTLS_ENTROPY_HAVE_SOURCES MBEDTLS_ENTROPY_TRUE_SOURCES +#else +#undef MBEDTLS_ENTROPY_HAVE_SOURCES +#endif + +/* Test function dependencies can only check with defined(), + * not other preprocessor expressions. */ +#if MBEDTLS_ENTROPY_TRUE_SOURCES > 0 +#define MBEDTLS_ENTROPY_HAVE_TRUE_SOURCES +#else +#undef MBEDTLS_ENTROPY_HAVE_TRUE_SOURCES +#endif + /* If MBEDTLS_PSA_CRYPTO_C is defined, make sure MBEDTLS_PSA_CRYPTO_CLIENT * is defined as well to include all PSA code. */ From bfaa6a5c810345797451ffffbe87f9c7d9d027f4 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 20 Jan 2026 17:52:25 +0100 Subject: [PATCH 03/20] Allow dependencies on platform features in PSA tests Signed-off-by: Gilles Peskine --- tests/scripts/components-basic-checks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scripts/components-basic-checks.sh b/tests/scripts/components-basic-checks.sh index f34ec411ca..f1e58755ba 100644 --- a/tests/scripts/components-basic-checks.sh +++ b/tests/scripts/components-basic-checks.sh @@ -91,7 +91,7 @@ component_check_test_dependencies () { grep 'depends_on' \ tests/suites/test_suite_psa*.data tests/suites/test_suite_psa*.function | grep -Eo '!?MBEDTLS_[^: ]*' | - grep -v -e MBEDTLS_PSA_ -e MBEDTLS_TEST_ | + grep -v -e MBEDTLS_ENTROPY_HAVE_ -e MBEDTLS_PLATFORM -e MBEDTLS_PSA_ -e MBEDTLS_TEST_ | sort -u > $found # Expected ones with justification - keep in sorted order by ASCII table! From 4de8b1043ac7da643a477cb2260018e8d5197615 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 26 Jan 2026 20:39:51 +0100 Subject: [PATCH 04/20] Move PSA internal RNG functions to a new module Move the PSA internal RNG functions (i.e. the parts of the PSA random generator that are used when `MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` is not enabled) to a separate source file. `mbedtls_psa_crypto_configure_entropy_sources` stays where it is, at least for now, because it accesses global data directly and because I have no immediate reason to move it. Refactoring only, no behavior change. Signed-off-by: Gilles Peskine --- library/CMakeLists.txt | 1 + library/Makefile | 1 + library/psa_crypto.c | 53 +++--------------------- library/psa_crypto_random.c | 82 +++++++++++++++++++++++++++++++++++++ library/psa_crypto_random.h | 72 ++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 47 deletions(-) create mode 100644 library/psa_crypto_random.c create mode 100644 library/psa_crypto_random.h diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 479da96008..2ec0381a49 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -76,6 +76,7 @@ set(src_crypto psa_crypto_mac.c psa_crypto_pake.c psa_crypto_rsa.c + psa_crypto_random.c psa_crypto_se.c psa_crypto_slot_management.c psa_crypto_storage.c diff --git a/library/Makefile b/library/Makefile index 6692a81f61..4e368efb86 100644 --- a/library/Makefile +++ b/library/Makefile @@ -167,6 +167,7 @@ OBJS_CRYPTO= \ psa_crypto_hash.o \ psa_crypto_mac.o \ psa_crypto_pake.o \ + psa_crypto_random.o \ psa_crypto_rsa.o \ psa_crypto_se.o \ psa_crypto_slot_management.o \ diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 9e17e27f2d..ec6b685dd3 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -37,6 +37,7 @@ * stored keys. */ #include "psa_crypto_storage.h" +#include "psa_crypto_random.h" #include "psa_crypto_random_impl.h" #include @@ -4412,25 +4413,8 @@ static psa_status_t psa_generate_random_internal(uint8_t *output, return PSA_SUCCESS; #else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ - - while (output_size > 0) { - int ret = MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; - size_t request_size = - (output_size > MBEDTLS_PSA_RANDOM_MAX_REQUEST ? - MBEDTLS_PSA_RANDOM_MAX_REQUEST : - output_size); -#if defined(MBEDTLS_CTR_DRBG_C) - ret = mbedtls_ctr_drbg_random(&global_data.rng.drbg, output, request_size); -#elif defined(MBEDTLS_HMAC_DRBG_C) - ret = mbedtls_hmac_drbg_random(&global_data.rng.drbg, output, request_size); -#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */ - if (ret != 0) { - return mbedtls_to_psa_error(ret); - } - output_size -= request_size; - output += request_size; - } - return PSA_SUCCESS; + return psa_random_internal_generate(&global_data.rng, + output, output_size); #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } @@ -7986,28 +7970,7 @@ static void mbedtls_psa_random_init(mbedtls_psa_random_context_t *rng) #if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) memset(rng, 0, sizeof(*rng)); #else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ - - /* Set default configuration if - * mbedtls_psa_crypto_configure_entropy_sources() hasn't been called. */ - if (rng->entropy_init == NULL) { - rng->entropy_init = mbedtls_entropy_init; - } - if (rng->entropy_free == NULL) { - rng->entropy_free = mbedtls_entropy_free; - } - - rng->entropy_init(&rng->entropy); -#if defined(MBEDTLS_PSA_INJECT_ENTROPY) && \ - defined(MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES) - /* The PSA entropy injection feature depends on using NV seed as an entropy - * source. Add NV seed as an entropy source for PSA entropy injection. */ - mbedtls_entropy_add_source(&rng->entropy, - mbedtls_nv_seed_poll, NULL, - MBEDTLS_ENTROPY_BLOCK_SIZE, - MBEDTLS_ENTROPY_SOURCE_STRONG); -#endif - - mbedtls_psa_drbg_init(&rng->drbg); + psa_random_internal_init(rng); #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } @@ -8021,8 +7984,7 @@ static void mbedtls_psa_random_free(mbedtls_psa_random_context_t *rng) #if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) memset(rng, 0, sizeof(*rng)); #else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ - mbedtls_psa_drbg_free(&rng->drbg); - rng->entropy_free(&rng->entropy); + psa_random_internal_free(rng); #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } @@ -8035,10 +7997,7 @@ static psa_status_t mbedtls_psa_random_seed(mbedtls_psa_random_context_t *rng) (void) rng; return PSA_SUCCESS; #else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ - const unsigned char drbg_seed[] = "PSA"; - int ret = mbedtls_psa_drbg_seed(&rng->drbg, &rng->entropy, - drbg_seed, sizeof(drbg_seed) - 1); - return mbedtls_to_psa_error(ret); + return psa_random_internal_seed(rng); #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } diff --git a/library/psa_crypto_random.c b/library/psa_crypto_random.c new file mode 100644 index 0000000000..f5eb658ab6 --- /dev/null +++ b/library/psa_crypto_random.c @@ -0,0 +1,82 @@ +/* + * PSA crypto random generator. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#include "common.h" + +#if defined(MBEDTLS_PSA_CRYPTO_C) && !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + +#include "psa_crypto_core.h" +#include "psa_crypto_random.h" +#include "psa_crypto_random_impl.h" + +#if defined(MBEDTLS_PSA_INJECT_ENTROPY) +#include "entropy_poll.h" +#endif + +void psa_random_internal_init(mbedtls_psa_random_context_t *rng) +{ + /* Set default configuration if + * mbedtls_psa_crypto_configure_entropy_sources() hasn't been called. */ + if (rng->entropy_init == NULL) { + rng->entropy_init = mbedtls_entropy_init; + } + if (rng->entropy_free == NULL) { + rng->entropy_free = mbedtls_entropy_free; + } + + rng->entropy_init(&rng->entropy); +#if defined(MBEDTLS_PSA_INJECT_ENTROPY) && \ + defined(MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES) + /* The PSA entropy injection feature depends on using NV seed as an entropy + * source. Add NV seed as an entropy source for PSA entropy injection. */ + mbedtls_entropy_add_source(&rng->entropy, + mbedtls_nv_seed_poll, NULL, + MBEDTLS_ENTROPY_BLOCK_SIZE, + MBEDTLS_ENTROPY_SOURCE_STRONG); +#endif + + mbedtls_psa_drbg_init(&rng->drbg); +} + +void psa_random_internal_free(mbedtls_psa_random_context_t *rng) +{ + mbedtls_psa_drbg_free(&rng->drbg); + rng->entropy_free(&rng->entropy); +} +psa_status_t psa_random_internal_seed(mbedtls_psa_random_context_t *rng) +{ + const unsigned char drbg_seed[] = "PSA"; + int ret = mbedtls_psa_drbg_seed(&rng->drbg, &rng->entropy, + drbg_seed, sizeof(drbg_seed) - 1); + return mbedtls_to_psa_error(ret); +} + +psa_status_t psa_random_internal_generate( + mbedtls_psa_random_context_t *rng, + uint8_t *output, size_t output_size) +{ + while (output_size > 0) { + size_t request_size = + (output_size > MBEDTLS_PSA_RANDOM_MAX_REQUEST ? + MBEDTLS_PSA_RANDOM_MAX_REQUEST : + output_size); +#if defined(MBEDTLS_CTR_DRBG_C) + int ret = mbedtls_ctr_drbg_random(&rng->drbg, output, request_size); +#elif defined(MBEDTLS_HMAC_DRBG_C) + int ret = mbedtls_hmac_drbg_random(&rng->drbg, output, request_size); +#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */ + if (ret != 0) { + return mbedtls_to_psa_error(ret); + } + output_size -= request_size; + output += request_size; + } + return PSA_SUCCESS; +} + +#endif /* MBEDTLS_PSA_CRYPTO_C && !MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ diff --git a/library/psa_crypto_random.h b/library/psa_crypto_random.h new file mode 100644 index 0000000000..167bb235b3 --- /dev/null +++ b/library/psa_crypto_random.h @@ -0,0 +1,72 @@ +/* + * PSA crypto random generator internal functions. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#ifndef PSA_CRYPTO_RANDOM_H +#define PSA_CRYPTO_RANDOM_H + +#include "common.h" + +#if !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + +#include +#include "psa_crypto_random_impl.h" + +/** Initialize the PSA random generator. + * + * \param[out] rng The random generator context to initialize. + */ +void psa_random_internal_init(mbedtls_psa_random_context_t *rng); + +/** Deinitialize the PSA random generator. + * + * \param[in,out] rng The random generator context to deinitialize. + */ +void psa_random_internal_free(mbedtls_psa_random_context_t *rng); + +/** Seed the PSA random generator. + * + * \note This function is not thread-safe. + * + * \param[in,out] rng The random generator context to seed. + * + * \retval #PSA_SUCCESS + * Success. + * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY + * The entropy source failed. + */ +psa_status_t psa_random_internal_seed(mbedtls_psa_random_context_t *rng); + +/** + * \brief Generate random bytes. Like psa_generate_random(), but for use + * inside the library. + * + * This function is thread-safe. + * + * \warning This function **can** fail! Callers MUST check the return status + * and MUST NOT use the content of the output buffer if the return + * status is not #PSA_SUCCESS. + * + * \param[in,out] rng The random generator context to seed. + * \param[out] output Output buffer for the generated data. + * \param output_size Number of bytes to generate and output. + * + * \retval #PSA_SUCCESS + * Success. + * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY + * The random generator needed to reseed, and the entropy + * source failed. + * \retval #PSA_ERROR_HARDWARE_FAILURE + * A hardware accelerator failed. + */ +psa_status_t psa_random_internal_generate( + mbedtls_psa_random_context_t *rng, + uint8_t *output, size_t output_size); + +#endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */ + +#endif /* PSA_CRYPTO_RANDOM_H */ From fb6503bf62ea5df4dbaaf285c4c03fab4bc05940 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 16 Jan 2026 19:01:48 +0100 Subject: [PATCH 05/20] Add internal function to reseed PSA RNG Not applicable to an external RNG. Signed-off-by: Gilles Peskine --- library/psa_crypto_random_impl.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/library/psa_crypto_random_impl.h b/library/psa_crypto_random_impl.h index 5b5163111b..f342845e46 100644 --- a/library/psa_crypto_random_impl.h +++ b/library/psa_crypto_random_impl.h @@ -100,6 +100,8 @@ static inline void mbedtls_psa_drbg_free(mbedtls_psa_drbg_context_t *p_rng) /** Seed the PSA DRBG. * + * \param drbg_ctx The DRBG context to seed. + * It must be initialized but not active. * \param entropy An entropy context to read the seed from. * \param custom The personalization string. * This can be \c NULL, in which case the personalization @@ -121,6 +123,28 @@ static inline int mbedtls_psa_drbg_seed(mbedtls_psa_drbg_context_t *drbg_ctx, #endif } +/** Reseed the PSA DRBG. + * + * \param drbg_ctx The DRBG context to reseed. + * It must be active. + * \param additional Additional data to inject. + * \param len The length of \p additional in bytes. + * This can be 0 to simply reseed from the entropy source. + * + * \return \c 0 on success. + * \return An Mbed TLS error code (\c MBEDTLS_ERR_xxx) on failure. + */ +static inline int mbedtls_psa_drbg_reseed(mbedtls_psa_drbg_context_t *drbg_ctx, + const unsigned char *additional, + size_t len) +{ +#if defined(MBEDTLS_CTR_DRBG_C) + return mbedtls_ctr_drbg_reseed(drbg_ctx, additional, len); +#elif defined(MBEDTLS_HMAC_DRBG_C) + return mbedtls_hmac_drbg_reseed(drbg_ctx, additional, len); +#endif +} + #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ #endif /* PSA_CRYPTO_RANDOM_IMPL_H */ From ccfb7357a30775e4ec02a832d1c3b448680c13ee Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 27 Jan 2026 15:49:54 +0100 Subject: [PATCH 06/20] New function psa_random_reseed() Explicit reseed of the PSA random generator. Signed-off-by: Gilles Peskine --- ChangeLog.d/rng-cloning.txt | 3 + include/psa/crypto_extra.h | 81 ++++++++- library/psa_crypto.c | 22 +++ .../suites/test_suite_psa_crypto_entropy.data | 21 +++ .../test_suite_psa_crypto_entropy.function | 160 ++++++++++++++++++ 5 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 ChangeLog.d/rng-cloning.txt 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))); From 48e37275ecc1895d183a2f85c1c0512a4698cc9e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 27 Jan 2026 17:52:12 +0100 Subject: [PATCH 07/20] Reseed tests: the number of entropy queries depends on the config Signed-off-by: Gilles Peskine --- .../test_suite_psa_crypto_entropy.function | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index 9709ffa9ad..696ee0b7de 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -354,11 +354,21 @@ exit: void reseed_consumption() { uint8_t random[10] = { 0 }; + const size_t max_get_entropy = 4; - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_get_entropy)) { goto exit; } + /* Depending on the DRBG parameters, the initial seeding may + * consume entropy once or twice. Zero would be deeply unsettling + * (how can you initialize the RNG without entropy?). More than 2 would + * be ok, but the test code would need to be adapted. */ + TEST_LE_U(1, fake_entropy_state.step); + TEST_LE_U(fake_entropy_state.step, 2); + /* Arrange to have exactly 2 entropy blocks remaining. */ + fake_entropy_state.step = max_get_entropy - 2; + /* Explicit reseed, consumes 1 entropy block, 1 remaining */ PSA_ASSERT(psa_random_reseed(NULL, 0)); PSA_ASSERT(psa_generate_random(random, sizeof(random))); @@ -386,16 +396,19 @@ void reseed_uniqueness(data_t *perso1, data_t *perso2) uint8_t random1[10] = { 0 }; uint8_t random2[10] = { 0 }; uint8_t random_again[10] = { 0 }; + /* Enough for 2 initial seeding + 2 reseed + 2 getrandom with + * prediction resistance */ + size_t max_entropy_queries = 6; /* Reference: no reseed */ - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_entropy_queries)) { goto exit; } PSA_ASSERT(psa_generate_random(random0, sizeof(random0))); mbedtls_psa_crypto_free(); /* Reference: no reseed, again */ - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_entropy_queries)) { goto exit; } PSA_ASSERT(psa_generate_random(random_again, sizeof(random_again))); @@ -404,7 +417,7 @@ void reseed_uniqueness(data_t *perso1, data_t *perso2) random_again, sizeof(random_again)); /* Reseed with a personalization string */ - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_entropy_queries)) { goto exit; } PSA_ASSERT(psa_random_reseed(perso1->x, perso1->len)); @@ -413,7 +426,7 @@ void reseed_uniqueness(data_t *perso1, data_t *perso2) TEST_ASSERT(memcmp(random0, random1, sizeof(random1)) != 0); /* Reseed with a personalization string (same or different) */ - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_entropy_queries)) { goto exit; } PSA_ASSERT(psa_random_reseed(perso2->x, perso2->len)); @@ -428,7 +441,7 @@ void reseed_uniqueness(data_t *perso1, data_t *perso2) } /* Reseed twice */ - if (!psa_init_deterministic(3)) { + if (!psa_init_deterministic(max_entropy_queries)) { goto exit; } PSA_ASSERT(psa_random_reseed(perso1->x, perso1->len)); From bd57d52490b4d80965af9cc6ace7dd0951e353e6 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 29 Jan 2026 13:01:06 +0100 Subject: [PATCH 08/20] DRBG: make reseed_counter not be off by 1 Change `reseed_counter` to be the number of requests made since the last reseed, rather than this number minus 1. Thus, reseed when `reseed_counter >= reseed_interval` rather than `reseed_counter > reseed_interval`. The field `reseed_counter` is private so this is not an API change. Signed-off-by: Gilles Peskine --- include/mbedtls/ctr_drbg.h | 3 +-- library/ctr_drbg.c | 4 ++-- library/hmac_drbg.c | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/mbedtls/ctr_drbg.h b/include/mbedtls/ctr_drbg.h index 0b7cce1923..c8d64830b5 100644 --- a/include/mbedtls/ctr_drbg.h +++ b/include/mbedtls/ctr_drbg.h @@ -186,8 +186,7 @@ typedef struct mbedtls_ctr_drbg_context { unsigned char MBEDTLS_PRIVATE(counter)[16]; /*!< The counter (V). */ int MBEDTLS_PRIVATE(reseed_counter); /*!< The reseed counter. * This is the number of requests that have - * been made since the last (re)seeding, - * minus one. + * been made since the last (re)seeding. * Before the initial seeding, this field * contains the amount of entropy in bytes * to use as a nonce for the initial seeding, diff --git a/library/ctr_drbg.c b/library/ctr_drbg.c index b82044eb7d..bbbfdacf3d 100644 --- a/library/ctr_drbg.c +++ b/library/ctr_drbg.c @@ -494,7 +494,7 @@ static int mbedtls_ctr_drbg_reseed_internal(mbedtls_ctr_drbg_context *ctx, if ((ret = ctr_drbg_update_internal(ctx, seed)) != 0) { goto exit; } - ctx->reseed_counter = 1; + ctx->reseed_counter = 0; exit: mbedtls_platform_zeroize(seed, sizeof(seed)); @@ -629,7 +629,7 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng, memset(locals.add_input, 0, MBEDTLS_CTR_DRBG_SEEDLEN); - if (ctx->reseed_counter > ctx->reseed_interval || + if (ctx->reseed_counter >= ctx->reseed_interval || ctx->prediction_resistance) { if ((ret = mbedtls_ctr_drbg_reseed(ctx, additional, add_len)) != 0) { return ret; diff --git a/library/hmac_drbg.c b/library/hmac_drbg.c index 90174d5d17..d51962832e 100644 --- a/library/hmac_drbg.c +++ b/library/hmac_drbg.c @@ -196,7 +196,7 @@ static int hmac_drbg_reseed_core(mbedtls_hmac_drbg_context *ctx, } /* 3. Reset reseed_counter */ - ctx->reseed_counter = 1; + ctx->reseed_counter = 0; exit: /* 4. Done */ @@ -326,7 +326,7 @@ int mbedtls_hmac_drbg_random_with_add(void *p_rng, /* 1. (aka VII and IX) Check reseed counter and PR */ if (ctx->f_entropy != NULL && /* For no-reseeding instances */ (ctx->prediction_resistance == MBEDTLS_HMAC_DRBG_PR_ON || - ctx->reseed_counter > ctx->reseed_interval)) { + ctx->reseed_counter >= ctx->reseed_interval)) { if ((ret = mbedtls_hmac_drbg_reseed(ctx, additional, add_len)) != 0) { return ret; } From 5093f0841576ffb0fb20b7d8b0befd268f1f0ed7 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 29 Jan 2026 13:03:42 +0100 Subject: [PATCH 09/20] New API psa_random_deplete(): force a reseed on the next RNG query In some scenarios, application or integration code knows that the random generator should be reseeded, but the reseed cannot or must not happen immediately and there is no way to report errors. In such scenarios, users can call the new function `psa_random_deplete()`, which just marks the DRBG as needing a reseed. This change requires DRBG modules to treat `reseed_counter == reseed_interval` as a condition that requires a reseed. Historically they reseeded when `reseed_counter > reseed_interval`, but that made it impossible to require a reseed when `reseed_interval == MAX_INT`. Note that this edge case is not tested. Signed-off-by: Gilles Peskine --- ChangeLog.d/rng-cloning.txt | 5 ++-- include/psa/crypto_extra.h | 28 +++++++++++++++++ library/psa_crypto.c | 19 ++++++++++++ library/psa_crypto_random_impl.h | 12 ++++++++ .../suites/test_suite_psa_crypto_entropy.data | 3 ++ .../test_suite_psa_crypto_entropy.function | 30 +++++++++++++++++++ 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt index 3c2d63db1a..1539b50889 100644 --- a/ChangeLog.d/rng-cloning.txt +++ b/ChangeLog.d/rng-cloning.txt @@ -1,3 +1,4 @@ Features - * Applications can use the new function psa_random_reseed() to - request an immediate reseed of the PSA random generator. + * Applications can use the new functions psa_random_reseed() to + request an immediate reseed of the PSA random generator, or + psa_random_deplete() to force a reseed on the next random generator call. diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 7d0b01ca4f..ec8b126d32 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -581,6 +581,34 @@ psa_status_t mbedtls_psa_external_get_random( */ psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size); +/** Force a reseed of the PSA random generator the next time it is used. + * + * 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. + * + * This function has a similar purpose as psa_random_reseed(), + * but the reseed will happen the next time the random generator is used. + * This advantage of this function is that it does not fail unless the + * system is an unintended state, so it can be used in contexts where + * propagating errors is difficult. + * + * \note This function has no effect when #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG + * is enabled. + * + * \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. + */ +psa_status_t psa_random_deplete(void); + /**@}*/ /** \defgroup psa_builtin_keys Built-in keys diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 7951268186..cae7a2bd7d 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -8023,6 +8023,25 @@ psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size) #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } +psa_status_t psa_random_deplete(void) +{ + GUARD_MODULE_INITIALIZED; +#if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + 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) */ + mbedtls_psa_drbg_deplete(&global_data.rng.drbg); +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_unlock(&mbedtls_threading_psa_rngdata_mutex); +#endif /* defined(MBEDTLS_THREADING_C) */ + return PSA_SUCCESS; +#endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +} + psa_status_t psa_generate_random(uint8_t *output_external, size_t output_size) { diff --git a/library/psa_crypto_random_impl.h b/library/psa_crypto_random_impl.h index f342845e46..0078df7453 100644 --- a/library/psa_crypto_random_impl.h +++ b/library/psa_crypto_random_impl.h @@ -145,6 +145,18 @@ static inline int mbedtls_psa_drbg_reseed(mbedtls_psa_drbg_context_t *drbg_ctx, #endif } +/** Deplete the PSA DRBG, i.e. cause it to reseed the next time it is used. + * + * \note This function is not thread-safe. + * + * \param drbg_ctx The DRBG context to deplete. + * It must be active. + */ +static inline void mbedtls_psa_drbg_deplete(mbedtls_psa_drbg_context_t *drbg_ctx) +{ + drbg_ctx->reseed_counter = drbg_ctx->reseed_interval; +} + #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ #endif /* PSA_CRYPTO_RANDOM_IMPL_H */ diff --git a/tests/suites/test_suite_psa_crypto_entropy.data b/tests/suites/test_suite_psa_crypto_entropy.data index e5f7243388..3f71f4490e 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.data +++ b/tests/suites/test_suite_psa_crypto_entropy.data @@ -50,6 +50,9 @@ reseed_basic: Explicit reseed: entropy consumption reseed_consumption: +Deplete: entropy consumption +deplete_consumption: + Explicit reseed: uniqueness tests (0 = 0) reseed_uniqueness:"":"" diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index 696ee0b7de..71c6e90d2f 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -326,6 +326,7 @@ void reseed_basic() const uint8_t perso[5] = { 'p', 'e', 'r', 's', 'o' }; TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_random_deplete(), PSA_ERROR_BAD_STATE); TEST_EQUAL(psa_generate_random(random, sizeof(random)), PSA_ERROR_BAD_STATE); PSA_INIT(); @@ -334,9 +335,13 @@ void reseed_basic() PSA_ASSERT(psa_random_reseed(perso, sizeof(perso))); PSA_ASSERT(psa_generate_random(random, sizeof(random))); + PSA_ASSERT(psa_random_deplete()); + 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_random_deplete(), PSA_ERROR_BAD_STATE); TEST_EQUAL(psa_generate_random(random, sizeof(random)), PSA_ERROR_BAD_STATE); exit: @@ -389,6 +394,30 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void deplete_consumption() +{ + uint8_t random[10] = { 0 }; + + if (!psa_init_deterministic(4)) { + goto exit; + } + + /* Depending on the DRBG parameters, the initial seeding may + * consume entropy once or twice. Reset to 1 to keep things simple. */ + fake_entropy_state.step = 1; + + PSA_ASSERT(psa_random_deplete()); + TEST_EQUAL(fake_entropy_state.step, 1); + + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_LE_U(2, fake_entropy_state.step); + +exit: + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ void reseed_uniqueness(data_t *perso1, data_t *perso2) { @@ -472,6 +501,7 @@ void external_rng_failure_generate() PSA_ASSERT(psa_destroy_key(key)); TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_NOT_SUPPORTED); + TEST_EQUAL(psa_random_deplete(), PSA_ERROR_NOT_SUPPORTED); mbedtls_test_disable_insecure_external_rng(); TEST_EQUAL(PSA_ERROR_INSUFFICIENT_ENTROPY, From a86341520578cb701dd8dedecb73abae6af0b569 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 29 Jan 2026 18:54:57 +0100 Subject: [PATCH 10/20] New API psa_random_set_prediction_resistance() Let applications configure prediction resistance at runtime. Prediction resistance is always considered disabled when there is no actual entropy source, only a nonvolatile seed. Signed-off-by: Gilles Peskine --- ChangeLog.d/rng-cloning.txt | 2 + include/psa/crypto_extra.h | 45 +++++++++ library/psa_crypto.c | 36 +++++++ library/psa_crypto_random_impl.h | 21 ++++ .../suites/test_suite_psa_crypto_entropy.data | 9 ++ .../test_suite_psa_crypto_entropy.function | 96 +++++++++++++++++++ 6 files changed, 209 insertions(+) diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt index 1539b50889..71ef04e942 100644 --- a/ChangeLog.d/rng-cloning.txt +++ b/ChangeLog.d/rng-cloning.txt @@ -2,3 +2,5 @@ Features * Applications can use the new functions psa_random_reseed() to request an immediate reseed of the PSA random generator, or psa_random_deplete() to force a reseed on the next random generator call. + * Applications can call psa_random_set_prediction_resistance() to toggle + prediction resistance in the PSA random generator. diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index ec8b126d32..2fe96c6eeb 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -609,6 +609,51 @@ psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size); */ psa_status_t psa_random_deplete(void); +/** Enable or disable prediction resistance in the PSA random generator. + * + * When prediction resistance is enabled, the random generator + * injects extra entropy before each request regardless of its size. + * As a consequence, a temporary compromise of the random generator + * state does not, by itself, compromise future steps. + * Furthermore, duplicating the random generator state (because the + * running application instance is cloned) is safe since it will + * not lead to identical random generator outputs in the clones. + * + * When prediction resistance is disabled, the random generator injects + * extra entropy periodically only as determined by + * #MBEDTLS_CTR_DRBG_RESEED_INTERVAL if #MBEDTLS_CTR_DRBG_C + * is enabled, or #MBEDTLS_HMAC_DRBG_RESEED_INTERVAL otherwise. + * + * Prediction resistance is disabled by default, although setting + * #MBEDTLS_CTR_DRBG_RESEED_INTERVAL or #MBEDTLS_HMAC_DRBG_RESEED_INTERVAL + * to \c 1 satisfies the prediction resistance property even when the + * option is disabled. + * + * \note This function has no effect when #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG + * is enabled. + * + * \note Prediction resistance cannot be enabled when the only entropy source + * is a nonvolatile seed, since prediction resistance is effectively + * impossible to achieve without actual entropy. + * + * \param enabled \c 1 to enable prediction resistance. + * \c 0 to disable prediction resistance. + * + * \retval #PSA_SUCCESS + * The PSA random generator is active, and prediction resistance + * has been changed to the desired option. + * \retval #PSA_ERROR_BAD_STATE + * The PSA random generator is not active. + * \retval #PSA_ERROR_INVALID_ARGUMENT + * \p enabled is not valid. + * \retval #PSA_ERROR_NOT_SUPPORTED + * PSA uses an external random generator because the compilation + * option #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG is enabled. + * Or, the random generator only has a nonvolatile seed but no entropy + * source, and prediction resistance has been requested. + */ +psa_status_t psa_random_set_prediction_resistance(unsigned enabled); + /**@}*/ /** \defgroup psa_builtin_keys Built-in keys diff --git a/library/psa_crypto.c b/library/psa_crypto.c index cae7a2bd7d..bdaa0bac5b 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -8042,6 +8042,42 @@ psa_status_t psa_random_deplete(void) #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ } +psa_status_t psa_random_set_prediction_resistance(unsigned enabled) +{ + GUARD_MODULE_INITIALIZED; + +#if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + (void) enabled; + return PSA_ERROR_NOT_SUPPORTED; +#else /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ + + if (enabled != 0 && enabled != 1) { + return PSA_ERROR_INVALID_ARGUMENT; + } + +#if MBEDTLS_ENTROPY_TRUE_SOURCES > 0 +#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) */ + mbedtls_psa_drbg_set_prediction_resistance(&global_data.rng.drbg, enabled); +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_unlock(&mbedtls_threading_psa_rngdata_mutex); +#endif /* defined(MBEDTLS_THREADING_C) */ + return PSA_SUCCESS; + +#else /* MBEDTLS_ENTROPY_TRUE_SOURCES > 0 */ + if (enabled) { + return PSA_ERROR_NOT_SUPPORTED; + } else { + return PSA_SUCCESS; + } + +#endif /* MBEDTLS_ENTROPY_TRUE_SOURCES > 0 */ +#endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +} + psa_status_t psa_generate_random(uint8_t *output_external, size_t output_size) { diff --git a/library/psa_crypto_random_impl.h b/library/psa_crypto_random_impl.h index 0078df7453..3ed85e5c4e 100644 --- a/library/psa_crypto_random_impl.h +++ b/library/psa_crypto_random_impl.h @@ -157,6 +157,27 @@ static inline void mbedtls_psa_drbg_deplete(mbedtls_psa_drbg_context_t *drbg_ctx drbg_ctx->reseed_counter = drbg_ctx->reseed_interval; } +#if MBEDTLS_ENTROPY_TRUE_SOURCES > 0 +/** Set prediction resistance in the PSA DRBG. + * + * \note This function is not thread-safe. + * + * \param drbg_ctx The DRBG context to reconfigure. + * It must be active. + * \param enabled \c 1 to enable, or \c 0 to disable. + */ +static inline void mbedtls_psa_drbg_set_prediction_resistance( + mbedtls_psa_drbg_context_t *drbg_ctx, + unsigned enabled) +{ +#if defined(MBEDTLS_CTR_DRBG_C) + mbedtls_ctr_drbg_set_prediction_resistance(drbg_ctx, enabled); +#elif defined(MBEDTLS_HMAC_DRBG_C) + mbedtls_hmac_drbg_set_prediction_resistance(drbg_ctx, enabled); +#endif +} +#endif /* MBEDTLS_ENTROPY_TRUE_SOURCES > 0 */ + #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ #endif /* PSA_CRYPTO_RANDOM_IMPL_H */ diff --git a/tests/suites/test_suite_psa_crypto_entropy.data b/tests/suites/test_suite_psa_crypto_entropy.data index 3f71f4490e..03dd6c8222 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.data +++ b/tests/suites/test_suite_psa_crypto_entropy.data @@ -53,6 +53,15 @@ reseed_consumption: Deplete: entropy consumption deplete_consumption: +Prediction resistance: entropy consumption +prediction_resistance_consumption: + +Prediction resistance: bad state +prediction_resistance_bad_state: + +Prediction resistance: bad arguments +prediction_resistance_bad_arguments: + Explicit reseed: uniqueness tests (0 = 0) reseed_uniqueness:"":"" diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index 71c6e90d2f..413e1ea5d8 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -349,6 +349,60 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ +void prediction_resistance_bad_state() +{ + uint8_t random[10]; + + /* RNG inactive before initialization */ + TEST_EQUAL(psa_random_set_prediction_resistance(0), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_random_set_prediction_resistance(1), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_generate_random(random, sizeof(random)), PSA_ERROR_BAD_STATE); + + PSA_INIT(); + + /* Good cases, as controls */ + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + PSA_ASSERT(psa_random_set_prediction_resistance(0)); +#if MBEDTLS_ENTROPY_TRUE_SOURCES > 0 + PSA_ASSERT(psa_random_set_prediction_resistance(1)); +#endif + + /* RNG inactive after shutdown */ + mbedtls_psa_crypto_free(); + TEST_EQUAL(psa_random_set_prediction_resistance(0), PSA_ERROR_BAD_STATE); + TEST_EQUAL(psa_random_set_prediction_resistance(1), 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 */ +void prediction_resistance_bad_arguments() +{ + uint8_t random[10]; + + PSA_INIT(); + + TEST_EQUAL(psa_random_set_prediction_resistance(2), PSA_ERROR_INVALID_ARGUMENT); + TEST_EQUAL(psa_random_set_prediction_resistance(-1), PSA_ERROR_INVALID_ARGUMENT); + + /* Good cases, as controls */ + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + PSA_ASSERT(psa_random_set_prediction_resistance(0)); +#if MBEDTLS_ENTROPY_TRUE_SOURCES > 0 + PSA_ASSERT(psa_random_set_prediction_resistance(1)); +#else + TEST_EQUAL(psa_random_set_prediction_resistance(1), PSA_ERROR_NOT_SUPPORTED); +#endif + +exit: + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ /* Check that reseeding consumes entropy. * @@ -418,6 +472,46 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG:MBEDTLS_ENTROPY_HAVE_TRUE_SOURCES */ +void prediction_resistance_consumption() +{ + uint8_t random[10] = { 0 }; + + if (!psa_init_deterministic(4)) { + goto exit; + } + + /* Depending on the DRBG parameters, the initial seeding may + * consume entropy once or twice. Reset to 1 to keep things simple. */ + fake_entropy_state.step = 1; + + /* Default: no prediction resistance */ + /* (Note, we assume that prediction resistance is not effectively enabled + * at compile time by setting a very low reseed interval.) */ + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_EQUAL(fake_entropy_state.step, 1); + + /* Explicitly enable prediction resistance */ + PSA_ASSERT(psa_random_set_prediction_resistance(1)); + TEST_EQUAL(fake_entropy_state.step, 1); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_EQUAL(fake_entropy_state.step, 2); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_EQUAL(fake_entropy_state.step, 3); + + /* Explicitly disable prediction resistance */ + PSA_ASSERT(psa_random_set_prediction_resistance(0)); + TEST_EQUAL(fake_entropy_state.step, 3); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_EQUAL(fake_entropy_state.step, 3); + PSA_ASSERT(psa_generate_random(random, sizeof(random))); + TEST_EQUAL(fake_entropy_state.step, 3); + +exit: + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ void reseed_uniqueness(data_t *perso1, data_t *perso2) { @@ -502,6 +596,8 @@ void external_rng_failure_generate() TEST_EQUAL(psa_random_reseed(NULL, 0), PSA_ERROR_NOT_SUPPORTED); TEST_EQUAL(psa_random_deplete(), PSA_ERROR_NOT_SUPPORTED); + TEST_EQUAL(psa_random_set_prediction_resistance(0), PSA_ERROR_NOT_SUPPORTED); + TEST_EQUAL(psa_random_set_prediction_resistance(1), PSA_ERROR_NOT_SUPPORTED); mbedtls_test_disable_insecure_external_rng(); TEST_EQUAL(PSA_ERROR_INSUFFICIENT_ENTROPY, From de1a7f2fbba2d0837a602e41e02a307668d7cb9d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 4 Mar 2026 14:37:04 +0100 Subject: [PATCH 11/20] Remove documentation about fork protection It's coming, but not here yet. Signed-off-by: Gilles Peskine --- include/psa/crypto_extra.h | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 2fe96c6eeb..aa558b30d6 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -524,23 +524,8 @@ psa_status_t mbedtls_psa_external_get_random( * 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. + * child process should call this function before using the random + * generator. * * An additional consideration applies in configurations where there is no * actual entropy source, only a nonvolatile seed (i.e. From fc38b65561d00399ab4fed9c921cf09a721e3012 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 9 Mar 2026 18:05:22 +0100 Subject: [PATCH 12/20] Add advice to reseed the RNG if the application is cloned Signed-off-by: Gilles Peskine --- ChangeLog.d/rng-cloning.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt index 71ef04e942..e285159f44 100644 --- a/ChangeLog.d/rng-cloning.txt +++ b/ChangeLog.d/rng-cloning.txt @@ -4,3 +4,10 @@ Features psa_random_deplete() to force a reseed on the next random generator call. * Applications can call psa_random_set_prediction_resistance() to toggle prediction resistance in the PSA random generator. + +Security + * Applications running in environments where the application state is + cloned () should arrange to reseed the random generator using one + of the new functions psa_random_reseed() or psa_random_deplete(). + CVE-2026-25835 + From 7cf7a8500889b8aeb763db91003be2e32a23495b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 11 Mar 2026 10:29:40 +0100 Subject: [PATCH 13/20] Add missing parenthetical remark Signed-off-by: Gilles Peskine --- ChangeLog.d/rng-cloning.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt index e285159f44..fc7d4db7f5 100644 --- a/ChangeLog.d/rng-cloning.txt +++ b/ChangeLog.d/rng-cloning.txt @@ -7,7 +7,8 @@ Features Security * Applications running in environments where the application state is - cloned () should arrange to reseed the random generator using one - of the new functions psa_random_reseed() or psa_random_deplete(). - CVE-2026-25835 + cloned (for example due to resuming a frozen system state multiple + times, or due to cloning a virtual machine image) should arrange to + reseed the random generator using one of the new functions + psa_random_reseed() or psa_random_deplete(). CVE-2026-25835 From 409427eac40cc9632928a4bac9572e0ef523e6fb Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 11 Mar 2026 10:30:44 +0100 Subject: [PATCH 14/20] Fix grammar Signed-off-by: Gilles Peskine --- include/psa/crypto_extra.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index aa558b30d6..e5fddaa0d0 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -576,8 +576,8 @@ psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size); * * This function has a similar purpose as psa_random_reseed(), * but the reseed will happen the next time the random generator is used. - * This advantage of this function is that it does not fail unless the - * system is an unintended state, so it can be used in contexts where + * The advantage of this function is that it does not fail unless the + * system is in an unintended state, so it can be used in contexts where * propagating errors is difficult. * * \note This function has no effect when #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG From f05a711011ec9d02b927a2ff485367a3810ff2a7 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 11 Mar 2026 12:56:38 +0100 Subject: [PATCH 15/20] Minor documentation improvements Signed-off-by: Gilles Peskine --- include/psa/crypto_extra.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index e5fddaa0d0..5f04352128 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -514,8 +514,9 @@ psa_status_t mbedtls_psa_external_get_random( * 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: + * that uses randomness. (Note that any operation that uses a private or + * secret key may use randomness internally even if the result is not + * randomized, but hashing and signature verification are ok.) 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 @@ -583,6 +584,10 @@ psa_status_t psa_random_reseed(const uint8_t *perso, size_t perso_size); * \note This function has no effect when #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG * is enabled. * + * \note If prediction resistance is enabled (either explicitly, or because + * the reseed interval is set to 1), calling this function is + * unnecessary since the random generator will always reseed anyway. + * * \retval #PSA_SUCCESS * The reseed succeeded. * \retval #PSA_ERROR_BAD_STATE From 9d17d28dda68e432ca3c92966ae76d536de6829e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 12 Mar 2026 12:35:05 +0100 Subject: [PATCH 16/20] More documentation improvements Signed-off-by: Gilles Peskine --- include/psa/crypto_extra.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 5f04352128..83404846f7 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -502,7 +502,7 @@ 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. +/** Force an immediate reseed of the PSA random generator. * * The entropy source(s) are the ones configured at compile time. * @@ -544,6 +544,13 @@ psa_status_t mbedtls_psa_external_get_random( * from clients, since the decision to reseed is generally based * on the server state. * + * \note If the entropy source fails, the random generator remains usable: + * and subsequent calls to generate random data will succeed until + * the random generator itself decides to reseed. If you want to + * force a reseed, either treat the failure as a fatal error, + * or call psa_random_deplete() instead of this function (or in + * addition). + * * \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). From 909055d7609f8ffa89a4435f2048e8d130332e17 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 15 Mar 2026 19:32:39 +0100 Subject: [PATCH 17/20] Fix negation that broke psa_random_set_prediction_resistance Platform entropy is available when `MBEDTLS_NO_PLATFORM_ENTROPY` is _not_ defined. This caused the ok/not-supported behavior of `broke psa_random_set_prediction_resistance() to be inverted, and the unit tests checking that behavior to be similarly inverted, so the unit tests didn't catch it. Signed-off-by: Gilles Peskine --- include/mbedtls/config_adjust_legacy_crypto.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mbedtls/config_adjust_legacy_crypto.h b/include/mbedtls/config_adjust_legacy_crypto.h index 66bad0c812..8f70a27188 100644 --- a/include/mbedtls/config_adjust_legacy_crypto.h +++ b/include/mbedtls/config_adjust_legacy_crypto.h @@ -59,14 +59,14 @@ #define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 0 #endif #if defined(MBEDTLS_NO_PLATFORM_ENTROPY) -#define MBEDTLS_PLATFORM_ENTROPY_DEFINED 1 +#define MBEDTLS_PLATFORM_ENTROPY_ENABLED 0 #else -#define MBEDTLS_PLATFORM_ENTROPY_DEFINED 0 +#define MBEDTLS_PLATFORM_ENTROPY_ENABLED 1 #endif #define MBEDTLS_ENTROPY_TRUE_SOURCES ( \ MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED + \ - MBEDTLS_PLATFORM_ENTROPY_DEFINED + \ + MBEDTLS_PLATFORM_ENTROPY_ENABLED + \ 0) /* Whether there is at least one entropy source for the entropy module. From 900b7dc5acc3bb34b2f5333912f25071c4e21b54 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 15 Mar 2026 19:43:44 +0100 Subject: [PATCH 18/20] Test that the compile-time and run-time entropy source counts match Signed-off-by: Gilles Peskine --- tests/suites/test_suite_entropy.data | 3 +++ tests/suites/test_suite_entropy.function | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/suites/test_suite_entropy.data b/tests/suites/test_suite_entropy.data index 514fced49e..eff261401d 100644 --- a/tests/suites/test_suite_entropy.data +++ b/tests/suites/test_suite_entropy.data @@ -4,6 +4,9 @@ entropy_init_free:0 Entropy init-free-init-free entropy_init_free:1 +Entropy: count default sources +entropy_count_sources: + Create NV seed_file nv_seed_file_create: diff --git a/tests/suites/test_suite_entropy.function b/tests/suites/test_suite_entropy.function index a4f3b1bd7c..0e719f6f06 100644 --- a/tests/suites/test_suite_entropy.function +++ b/tests/suites/test_suite_entropy.function @@ -171,6 +171,29 @@ void entropy_init_free(int reinit) } /* END_CASE */ +/* BEGIN_CASE */ +void entropy_count_sources() +{ + mbedtls_entropy_context ctx; + mbedtls_entropy_init(&ctx); + +#if defined(MBEDTLS_ENTROPY_HAVE_SOURCES) + TEST_EQUAL(MBEDTLS_ENTROPY_HAVE_SOURCES, ctx.source_count); +#else + TEST_EQUAL(0, ctx.source_count); +#endif + +#if defined(MBEDTLS_ENTROPY_NV_SEED) + TEST_EQUAL(MBEDTLS_ENTROPY_TRUE_SOURCES + 1, ctx.source_count); +#else + TEST_EQUAL(MBEDTLS_ENTROPY_TRUE_SOURCES, ctx.source_count); +#endif + +exit: + mbedtls_entropy_free(&ctx); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_ENTROPY_NV_SEED:MBEDTLS_FS_IO */ void entropy_seed_file(char *path, int ret) { From e6efd3e4063b1d2362a666d68ba9dfe69b794837 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 15 Mar 2026 19:50:06 +0100 Subject: [PATCH 19/20] Match macro definition order with order in mbedtls_entropy_init Signed-off-by: Gilles Peskine --- include/mbedtls/config_adjust_legacy_crypto.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/mbedtls/config_adjust_legacy_crypto.h b/include/mbedtls/config_adjust_legacy_crypto.h index 8f70a27188..db684ad20d 100644 --- a/include/mbedtls/config_adjust_legacy_crypto.h +++ b/include/mbedtls/config_adjust_legacy_crypto.h @@ -53,16 +53,16 @@ */ /* Define auxiliary macros, because in standard C, defined(xxx) is only * allowed directly on an #if or #elif line, not in recursive expansion. */ -#if defined(MBEDTLS_ENTROPY_HARDWARE_ALT) -#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 1 -#else -#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 0 -#endif #if defined(MBEDTLS_NO_PLATFORM_ENTROPY) #define MBEDTLS_PLATFORM_ENTROPY_ENABLED 0 #else #define MBEDTLS_PLATFORM_ENTROPY_ENABLED 1 #endif +#if defined(MBEDTLS_ENTROPY_HARDWARE_ALT) +#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 1 +#else +#define MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED 0 +#endif #define MBEDTLS_ENTROPY_TRUE_SOURCES ( \ MBEDTLS_ENTROPY_HARDWARE_ALT_DEFINED + \ From d05d789316d4797335e3951065c29be4e1c7bb09 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 15 Mar 2026 19:53:25 +0100 Subject: [PATCH 20/20] grammar Signed-off-by: Gilles Peskine --- include/psa/crypto_extra.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 83404846f7..e800f3787d 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -545,7 +545,7 @@ psa_status_t mbedtls_psa_external_get_random( * on the server state. * * \note If the entropy source fails, the random generator remains usable: - * and subsequent calls to generate random data will succeed until + * subsequent calls to generate random data will succeed until * the random generator itself decides to reseed. If you want to * force a reseed, either treat the failure as a fatal error, * or call psa_random_deplete() instead of this function (or in