132 lines
3.7 KiB
C
132 lines
3.7 KiB
C
/* SPDX-License-Identifier: GPL-3.0-only */
|
|
/* Pre-radio seeding and serialized access to the device-wide CTR_DRBG. */
|
|
|
|
#include "secure_random.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#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;
|
|
}
|
|
}
|