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
+3
View File
@@ -19,6 +19,9 @@ void esp_restart(void) { assert(false); }
esp_err_t web_server_stop(void) { assert(false); return ESP_FAIL; }
esp_err_t web_server_start(void) { assert(false); return ESP_FAIL; }
esp_err_t web_security_rotate_certificate(void) { assert(false); return ESP_FAIL; }
esp_err_t web_server_replace_identity(uint32_t service, uint32_t identity, bool reset, bool *committed) {
(void)service; (void)identity; (void)reset; (void)committed; assert(false); return ESP_FAIL;
}
void *heap_caps_calloc(size_t n, size_t size, unsigned caps) {
assert(caps == (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); return calloc(n, size);
}
+32 -4
View File
@@ -25,6 +25,13 @@ static esp_err_t owner_action(unsigned action, uint32_t generation) {
esp_err_t web_server_stop_current(uint32_t generation) { return owner_action(0, generation); }
esp_err_t web_server_restart_current(uint32_t generation) { return owner_action(1, generation); }
esp_err_t web_server_reboot_current(uint32_t generation) { return owner_action(2, generation); }
esp_err_t web_server_replace_identity(uint32_t generation, uint32_t identity, bool reset, bool *committed) {
assert(identity == 11 && !reset); *committed = owner_error == ESP_OK; return owner_action(3, generation);
}
esp_err_t web_security_get_identity_snapshot(web_security_identity_snapshot_t *out) {
assert(on_handler && !host_lock_depth); *out = (web_security_identity_snapshot_t){.generation=11};
memset(out->fingerprint, 0xab, 32); return ESP_OK;
}
esp_err_t httpd_queue_work(httpd_handle_t handle, void (*callback)(void *), void *argument) {
assert(on_handler && !on_dispatcher && !on_callback && !host_lock_depth && handle == &server);
assert(!strcmp(response_status, "202 Accepted") && !send_fail && !aux.remaining_len && sends);
@@ -53,7 +60,7 @@ static void expect_lifecycle(const char *status, bool snapshot_read) {
on_handler = false;
assert(error == (send_fail || aux.remaining_len ? ESP_FAIL : ESP_OK));
assert(!strcmp(response_status, status) && mutations == before && submit_calls == submitted);
assert(strlen(output) < (snapshot_read ? 128 : 96)); zero(scratch, sizeof(scratch));
assert(strlen(output) < (snapshot_read ? 320 : 96)); zero(scratch, sizeof(scratch));
assert(!strstr(output, "principal") && !strstr(output, "csrf") && !strstr(output, "password"));
}
static const char *stop_body = "{\"action\":\"stop\",\"generation\":7}";
@@ -109,7 +116,23 @@ static void lifecycle_tests(void) {
for (unsigned i = 0; i < sizeof(invalid)/sizeof(*invalid); ++i) {
lifecycle_begin(&admin, invalid[i], false); expect_lifecycle("400 Bad Request", false);
}
const char *invalid_rotation[] = {
"{\"action\":\"rotate\",\"generation\":7}",
"{\"action\":\"rotate\",\"identity_generation\":11}",
"{\"action\":\"rotate\",\"generation\":7,\"identity_generation\":0}",
"{\"action\":\"rotate\",\"generation\":7,\"identity_generation\":4294967295}",
"{\"action\":\"rotate\",\"generation\":7,\"identity_generation\":4294967296}",
"{\"action\":\"rotate\",\"generation\":7,\"identity_generation\":\"11\"}",
"{\"action\":\"rotate\",\"generation\":7,\"identity_generation\":11,\"reset\":true}",
"{\"action\":\"stop\",\"generation\":7,\"identity_generation\":11}"
};
for (unsigned i = 0; i < sizeof(invalid_rotation)/sizeof(*invalid_rotation); ++i) {
lifecycle_begin(&admin, invalid_rotation[i], false); expect_lifecycle("400 Bad Request", false);
}
lifecycle_operation_t parsed = {0};
const char *rotation = "{\"identity_generation\":11,\"generation\":7,\"action\":\"rotate\"}";
assert(parse(rotation, strlen(rotation), &parsed) && parsed.identity_generation == 11 && parsed.action == 3);
for (size_t n = 0; n < strlen(rotation); ++n) assert(!parse(rotation, n, &parsed));
for (size_t n = 0; n < strlen(stop_body); ++n) assert(!parse(stop_body, n, &parsed));
assert(parse(stop_body, strlen(stop_body), &parsed)); assert(!parse(stop_body, strlen(stop_body) + 1, &parsed));
const char *reordered = " { \"generation\":4294967294, \"action\":\"restart\" } ";
@@ -129,7 +152,11 @@ static void lifecycle_tests(void) {
for (unsigned mode = 0; mode < 2; ++mode) {
owner_error = mode ? ESP_FAIL : ESP_OK;
lifecycle_begin(&admin, NULL, true); expect_lifecycle(mode ? "503 Service Unavailable" : "200 OK", true);
if (!mode) assert(!strcmp(output, "{\"generation\":7,\"running\":true,\"transitioning\":false,\"controllable\":true}"));
if (!mode) {
assert(strstr(output, "\"identity_generation\":11") && strstr(output, "\"rotatable\":true"));
assert(strstr(output, "abababababababababababababababababababababababababababababababab"));
assert(!strstr(output, "private") && !strstr(output, "certificate_der"));
}
}
owner_error = ESP_OK;
puts("PASS lifecycle bounded scalar snapshot and optional owner failure isolation");
@@ -165,9 +192,10 @@ static void lifecycle_tests(void) {
lifecycle_submit(&admin, stop_body); on_callback = true; ack_handoff((void *)(uintptr_t)late); on_callback = false;
assert(s_ack_id == s_operation.id); owner_callback(); dispatch(); assert(s_operation.state == OK);
puts("PASS lifecycle original-login expiry/revocation/validation races and shutdown/restart same-owner ABA");
for (unsigned action = 0; action < 3; ++action) {
for (unsigned action = 0; action < 4; ++action) {
auth_reset(); admin = mint(&alice); char body[80];
snprintf(body, sizeof(body), "{\"action\":\"%s\",\"generation\":7}", s_actions[action]);
snprintf(body, sizeof(body), "{\"action\":\"%s\",\"generation\":7%s}", s_actions[action],
action == 3 ? ",\"identity_generation\":11" : "");
invalidate_during_owner = true; lifecycle_submit(&admin, body); owner_callback(); before = mutations; dispatch();
invalidate_during_owner = false;
assert(s_operation.state == OK && mutations == before + 1);