/* SPDX-License-Identifier: GPL-3.0-only */ /* Pre-radio seeding and serialized access to the device-wide CTR_DRBG. */ #include "secure_random.h" #include #include #include #include "bootloader_random.h" #include "esp_random.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "mbedtls/ctr_drbg.h" static SemaphoreHandle_t s_random_mutex; static mbedtls_ctr_drbg_context s_drbg; static bool s_drbg_ready; static uint32_t s_generate_calls; static int pre_radio_entropy(void *context, unsigned char *output, size_t length) { (void)context; /* This callback is deliberately reachable only from secure_random_init(). */ bootloader_random_enable(); esp_fill_random(output, length); bootloader_random_disable(); return 0; } esp_err_t secure_random_init(void) { static const unsigned char personalization[] = "esp32-serial-swiss-army-knife"; if (s_drbg_ready) { return ESP_OK; } if (s_random_mutex == NULL) { s_random_mutex = xSemaphoreCreateMutex(); if (s_random_mutex == NULL) { return ESP_ERR_NO_MEM; } } xSemaphoreTake(s_random_mutex, portMAX_DELAY); esp_err_t error = ESP_OK; if (!s_drbg_ready) { mbedtls_ctr_drbg_init(&s_drbg); int result = mbedtls_ctr_drbg_seed(&s_drbg, pre_radio_entropy, NULL, personalization, sizeof(personalization) - 1U); if (result == 0) { /* Reseeding would re-enter a pre-radio-only entropy path after RF starts. */ mbedtls_ctr_drbg_set_reseed_interval(&s_drbg, INT_MAX); s_generate_calls = 0U; s_drbg_ready = true; } else { mbedtls_ctr_drbg_free(&s_drbg); error = ESP_FAIL; } } xSemaphoreGive(s_random_mutex); return error; } esp_err_t secure_random_fill(void *output, size_t length) { if (length == 0U) { return ESP_OK; } if (output == NULL) { return ESP_ERR_INVALID_ARG; } if (!s_drbg_ready || s_random_mutex == NULL) { return ESP_ERR_INVALID_STATE; } unsigned char *cursor = (unsigned char *)output; esp_err_t error = ESP_OK; xSemaphoreTake(s_random_mutex, portMAX_DELAY); while (length > 0U) { /* CTR_DRBG limits each request even though the public API need not. */ size_t chunk = length; if (chunk > MBEDTLS_CTR_DRBG_MAX_REQUEST) { chunk = MBEDTLS_CTR_DRBG_MAX_REQUEST; } /* * Mbed TLS stores its reseed counter in a signed int. Fail closed one * call before INT_MAX so it can neither overflow nor invoke the * pre-radio-only entropy callback during the device's lifetime. */ if (s_generate_calls >= (uint32_t)INT_MAX - 1U) { error = ESP_ERR_INVALID_STATE; break; } if (mbedtls_ctr_drbg_random(&s_drbg, cursor, chunk) != 0) { error = ESP_FAIL; break; } ++s_generate_calls; cursor += chunk; length -= chunk; } xSemaphoreGive(s_random_mutex); return error; } int secure_random_mbedtls(void *context, unsigned char *output, size_t length) { (void)context; return secure_random_fill(output, length) == ESP_OK ? 0 : MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED; } void secure_wipe(void *data, size_t size) { volatile uint8_t *byte = (volatile uint8_t *)data; if (byte == NULL) { return; } while (size-- > 0U) { *byte++ = 0U; } }