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
+48 -3
View File
@@ -11,13 +11,14 @@
static uint8_t stored[1600], pending[1600];
static size_t stored_size, pending_size;
static int fault, writes, commits, rng_calls, legacy_wipes, groups;
static bool locked, fail_mutex, fail_rng, fail_mac, alternate_mac, watch_publication;
static bool locked, fail_mutex, fail_rng, fail_mac, alternate_mac, watch_publication, mutex_busy;
static void (*crypto_hook)(void);
static web_security_blob_t expected_live;
enum { OPEN_RO = 20, OPEN_RW, QUERY, READ, SET, COMMIT, TYPE, SHORT_READ };
SemaphoreHandle_t xSemaphoreCreateMutex(void) { return fail_mutex ? NULL : (void *)1; }
int xSemaphoreTake(SemaphoreHandle_t m, unsigned delay)
{ (void)delay; assert(m && !locked); locked = true; return 1; }
{ assert(m && !locked); if (mutex_busy) { assert(delay == 0); return 0; } locked = true; return 1; }
int xSemaphoreGive(SemaphoreHandle_t m)
{ assert(m && locked); locked = false; return 1; }
esp_err_t esp_read_mac(uint8_t *mac, int type)
@@ -32,6 +33,7 @@ esp_err_t secure_random_init(void) { return fail_rng ? ESP_FAIL : ESP_OK; }
esp_err_t secure_random_fill(void *out, size_t length)
{
++rng_calls;
if (crypto_hook) { void (*hook)(void) = crypto_hook; crypto_hook = NULL; hook(); }
if (fail_rng) return ESP_FAIL;
return getrandom(out, length, 0) == (ssize_t)length ? ESP_OK : ESP_FAIL;
}
@@ -68,7 +70,7 @@ esp_err_t nvs_get_blob(nvs_handle_t handle, const char *key, void *data, size_t
esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *data, size_t size)
{
assert(handle == NVS_READWRITE && !strcmp(key, "material"));
assert(size == 1340 && locked);
assert(size == 1340 && (s_identity_token ? !locked : locked));
++writes;
if (watch_publication) assert(!memcmp(&s_material, &expected_live, sizeof(s_material)));
if (fault == SET) return ESP_FAIL;
@@ -90,6 +92,8 @@ static void boot(void)
{
memset(&s_material, 0, sizeof(s_material));
s_material_ready = false; s_security_mutex = NULL;
s_identity_token = s_next_identity_token = 0; s_identity_used = false;
crypto_hook = NULL; mutex_busy = false;
s_load_result = WEB_SECURITY_LOAD_STORED;
fault = writes = commits = rng_calls = legacy_wipes = 0;
fail_mutex = fail_rng = fail_mac = alternate_mac = locked = false;
@@ -124,6 +128,19 @@ static void rejected(void)
assert(writes == 0 || fault == SET || fault == COMMIT);
assert(web_security_rotate_certificate() == ESP_ERR_INVALID_STATE);
}
static void competing_identity(void)
{
assert(!locked && s_identity_token);
web_security_identity_snapshot_t snapshot;
assert(web_security_get_identity_snapshot(&snapshot) == ESP_OK && snapshot.busy);
assert(snapshot.generation == expected_live.generation);
assert(!memcmp(snapshot.fingerprint, expected_live.certificate_fingerprint, 32));
assert(web_security_rotate_certificate() == ESP_ERR_INVALID_STATE);
assert(web_security_reset_all() == ESP_ERR_INVALID_STATE);
assert(web_security_init(NULL) == ESP_ERR_INVALID_STATE);
assert(!memcmp(&expected_live, &s_material, sizeof(s_material)));
}
int main(void)
{
boot(); stored_size = 0;
@@ -262,6 +279,34 @@ int main(void)
}
group("rotation/reset transactional failures, identity change and generation increment");
uint32_t token = 0, generation = s_material.generation;
int prior_writes = writes, prior_rng = rng_calls;
assert(web_security_reserve_identity(generation - 1, false, &token) == ESP_ERR_INVALID_STATE && !token);
assert(writes == prior_writes && rng_calls == prior_rng);
web_security_identity_snapshot_t projection;
mutex_busy = true;
assert(web_security_get_identity_snapshot(&projection) == ESP_ERR_TIMEOUT);
assert(web_security_reserve_identity(generation, false, &token) == ESP_ERR_TIMEOUT);
mutex_busy = false;
assert(web_security_reserve_identity(generation, false, &token) == ESP_OK && token);
crypto_hook = competing_identity;
assert(web_security_replace_reserved(token) == ESP_OK && !crypto_hook);
assert(s_identity_token == token && s_material.generation == generation + 1);
assert(web_security_replace_reserved(token) == ESP_ERR_INVALID_STATE);
assert(web_security_reset_all() == ESP_ERR_INVALID_STATE);
web_security_release_identity(token - 1); assert(s_identity_token == token);
web_security_release_identity(token); assert(!s_identity_token);
assert(web_security_replace_reserved(token) == ESP_ERR_INVALID_STATE);
assert(web_security_get_identity_snapshot(&projection) == ESP_OK && !projection.busy);
assert(!memcmp(projection.fingerprint, s_material.certificate_fingerprint, 32));
expected_live = s_material;
group("zero-wait public projection and stale/token fencing; real crypto outside locks excludes canonical writers through release");
s_next_identity_token = UINT32_MAX;
assert(web_security_reserve_identity(0, true, &token) == ESP_ERR_INVALID_STATE);
assert(web_security_get_identity_snapshot(&projection) == ESP_OK && projection.busy);
assert(!memcmp(&s_material, &expected_live, sizeof(s_material)));
group("reservation IDs saturate without ABA or recovery mutation bypass");
for (int kind = 0; kind < 3; ++kind) {
boot(); legacy(&identity);
if (kind == 0) stored_size = 0;