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 <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine
2026-01-29 13:03:42 +01:00
parent bd57d52490
commit 5093f08415
6 changed files with 95 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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