Add HTTPS identity rotation support

This commit is contained in:
2026-09-13 18:21:37 +02:00
parent 36e80811e8
commit aa4bbc2c8c
24 changed files with 826 additions and 170 deletions
+80 -56
View File
@@ -51,6 +51,8 @@ _Static_assert(sizeof(web_security_blob_t) == WEB_SECURITY_BLOB_SIZE,
static SemaphoreHandle_t s_security_mutex;
static web_security_blob_t s_material;
static bool s_material_ready;
static uint32_t s_identity_token, s_next_identity_token;
static bool s_identity_used;
static web_security_load_result_t s_load_result;
static bool bytes_are_zero(const uint8_t *data, size_t size)
@@ -649,6 +651,10 @@ esp_err_t web_security_init(web_security_load_result_t *load_result)
}
xSemaphoreTake(s_security_mutex, portMAX_DELAY);
if (s_identity_token) {
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
if (s_material_ready) {
if (load_result != NULL) {
*load_result = s_load_result;
@@ -763,14 +769,6 @@ esp_err_t web_security_get_certificate_metadata(
return error;
}
static esp_err_t increment_generation(web_security_blob_t *blob)
{
if (blob->generation == UINT32_MAX) {
return ESP_ERR_INVALID_STATE;
}
++blob->generation;
return ESP_OK;
}
static void install_committed_blob(const web_security_blob_t *candidate)
{
@@ -781,64 +779,90 @@ static void install_committed_blob(const web_security_blob_t *candidate)
s_load_result = WEB_SECURITY_LOAD_STORED;
}
esp_err_t web_security_rotate_certificate(void)
esp_err_t web_security_get_identity_snapshot(web_security_identity_snapshot_t *snapshot)
{
if (s_security_mutex == NULL) {
if (!snapshot) return ESP_ERR_INVALID_ARG;
memset(snapshot, 0, sizeof(*snapshot));
if (!s_security_mutex) return ESP_ERR_INVALID_STATE;
if (xSemaphoreTake(s_security_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
esp_err_t error = s_material_ready ? ESP_OK : ESP_ERR_INVALID_STATE;
if (error == ESP_OK) {
snapshot->generation = s_material.generation;
memcpy(snapshot->fingerprint, s_material.certificate_fingerprint, sizeof(snapshot->fingerprint));
snapshot->busy = s_identity_token != 0 || s_next_identity_token == UINT32_MAX;
}
xSemaphoreGive(s_security_mutex);
return error;
}
esp_err_t web_security_reserve_identity(uint32_t expected_generation, bool reset, uint32_t *token)
{
if (!token || (reset && expected_generation)) return ESP_ERR_INVALID_ARG;
*token = 0;
if (!reset) {
if (!s_security_mutex) return ESP_ERR_INVALID_STATE;
if (xSemaphoreTake(s_security_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
bool ready = s_material_ready;
xSemaphoreGive(s_security_mutex);
if (!ready) return ESP_ERR_INVALID_STATE;
}
esp_err_t error = secure_random_init();
if (error == ESP_OK) error = ensure_security_mutex();
if (error != ESP_OK) return error;
if (xSemaphoreTake(s_security_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
if (s_identity_token || s_next_identity_token == UINT32_MAX ||
(!s_material_ready && !reset) ||
(s_material_ready && s_material.generation == UINT32_MAX) ||
(expected_generation && expected_generation != s_material.generation)) {
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
*token = s_identity_token = ++s_next_identity_token;
xSemaphoreTake(s_security_mutex, portMAX_DELAY);
esp_err_t error = ESP_ERR_INVALID_STATE;
web_security_blob_t candidate;
memset(&candidate, 0, sizeof(candidate));
if (s_material_ready) {
candidate = s_material;
error = increment_generation(&candidate);
if (error == ESP_OK) {
error = generate_certificate(&candidate);
}
if (error == ESP_OK) {
error = save_blob(&candidate);
}
if (error == ESP_OK) {
install_committed_blob(&candidate);
}
}
secure_wipe(&candidate, sizeof(candidate));
s_identity_used = false;
xSemaphoreGive(s_security_mutex);
return error;
return ESP_OK;
}
esp_err_t web_security_reset_all(void)
esp_err_t web_security_replace_reserved(uint32_t token)
{
esp_err_t error = secure_random_init();
if (error != ESP_OK) {
return error;
}
error = ensure_security_mutex();
if (error != ESP_OK) {
return error;
}
if (!s_security_mutex || !token) return ESP_ERR_INVALID_STATE;
xSemaphoreTake(s_security_mutex, portMAX_DELAY);
web_security_blob_t candidate;
uint32_t generation = 1U;
if (s_material_ready) {
if (s_material.generation == UINT32_MAX) {
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
generation = s_material.generation + 1U;
if (s_identity_token != token || s_identity_used) {
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
error = generate_all(&candidate, generation);
if (error == ESP_OK) {
error = save_blob(&candidate);
}
if (error == ESP_OK) {
install_committed_blob(&candidate);
}
secure_wipe(&candidate, sizeof(candidate));
s_identity_used = true;
uint32_t generation = s_material_ready ? s_material.generation + 1U : 1U;
xSemaphoreGive(s_security_mutex);
/* The reservation, not a held mutex/spinlock, excludes all identity writers. */
web_security_blob_t candidate = {0};
esp_err_t error = generate_all(&candidate, generation);
if (error == ESP_OK) error = save_blob(&candidate);
xSemaphoreTake(s_security_mutex, portMAX_DELAY);
if (error == ESP_OK) install_committed_blob(&candidate);
xSemaphoreGive(s_security_mutex);
secure_wipe(&candidate, sizeof(candidate));
return error;
}
void web_security_release_identity(uint32_t token)
{
if (!s_security_mutex || !token) return;
xSemaphoreTake(s_security_mutex, portMAX_DELAY);
if (s_identity_token == token) s_identity_token = 0;
xSemaphoreGive(s_security_mutex);
}
static esp_err_t replace_identity(bool reset)
{
uint32_t token = 0;
esp_err_t error = web_security_reserve_identity(0, reset, &token);
if (error == ESP_OK) error = web_security_replace_reserved(token);
web_security_release_identity(token);
return error;
}
esp_err_t web_security_rotate_certificate(void) { return replace_identity(false); }
esp_err_t web_security_reset_all(void) { return replace_identity(true); }