New function psa_random_reseed()

Explicit reseed of the PSA random generator.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine
2026-01-27 15:49:54 +01:00
parent fb6503bf62
commit ccfb7357a3
5 changed files with 286 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
Features
* Applications can use the new function psa_random_reseed() to
request an immediate reseed of the PSA random generator.

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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:

View File

@@ -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)));