Add SSH host identity rotation controls

This commit is contained in:
2026-09-13 19:58:05 +02:00
parent aa4bbc2c8c
commit 8df1d2218b
19 changed files with 626 additions and 107 deletions
+32 -1
View File
@@ -40,6 +40,19 @@ static bool s_initialized, s_running, s_transitioning, s_cleanup_pending, s_desi
static uint32_t s_management_generation, s_requested_sequence, s_completed_sequence;
static int s_command_result;
static bool owner_stalled, owner_fail;
static uint32_t identity_generation = 3, identity_token;
static unsigned replacements;
static bool persist_fail;
static esp_err_t ssh_security_reserve_identity(uint32_t generation, bool reset, uint32_t *token) {
(void)reset; assert(mutex_storage && !depth); *token = 0;
if (identity_token || (generation && generation != identity_generation)) return ESP_ERR_INVALID_STATE;
*token = identity_token = 1; return ESP_OK;
}
static esp_err_t ssh_security_replace_reserved(uint32_t token) {
assert(token == identity_token && mutex_storage && !depth && !s_running && !s_cleanup_pending);
++replacements; if (persist_fail) return ESP_FAIL; ++identity_generation; return ESP_OK;
}
static void ssh_security_release_identity(uint32_t token) { if (token) { assert(identity_token == token && mutex_storage && !depth); identity_token = 0; } }
static int s_lock;
#define taskENTER_CRITICAL(p) do { (void)(p); assert(depth++ == 0); } while(0)
#define taskEXIT_CRITICAL(p) do { (void)(p); assert(--depth == 0); } while(0)
@@ -71,6 +84,24 @@ static void reset(void) {
}
}
int main(void) {
reset(); bool committed = true;
assert(ssh_transport_replace_identity(6,3,false,&committed)==ESP_ERR_INVALID_STATE && !committed && !notifications && !replacements);
assert(ssh_transport_replace_identity(7,2,false,&committed)==ESP_ERR_INVALID_STATE && !notifications && !replacements);
identity_token=1; assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_ERR_INVALID_STATE && !notifications); identity_token=0;
mutex_storage=1; assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_ERR_TIMEOUT && !notifications); mutex_storage=0;
owner_fail=true; assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_FAIL && !committed && !replacements && notifications==1 && s_cleanup_pending && !identity_token);
assert(ssh_transport_start()==ESP_ERR_INVALID_STATE && notifications==1);
reset(); owner_stalled=true;
assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_ERR_TIMEOUT && !committed && !replacements && notifications==1 && s_transitioning && !identity_token);
reset(); persist_fail=true;
assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_FAIL && !committed && replacements==1 && notifications==2 && s_running && identity_generation==3);
reset(); persist_fail=false;
assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_OK && committed && s_running && identity_generation==4 && notifications==2);
assert(ssh_transport_replace_identity(7,3,false,&committed)==ESP_ERR_INVALID_STATE && !committed && notifications==2);
reset(); s_running=false;
assert(ssh_transport_replace_identity(7,4,false,&committed)==ESP_OK && committed && !s_running && !notifications);
assert(ssh_transport_replace_host_key(true)==ESP_OK && s_running && notifications==1);
puts("PASS SSH combined generation/service-owner admission, competing reservation, failed-stop no mutation/start, timeout retention, persistence recovery, replay fence and stopped/reset semantics");
reset(); ssh_transport_management_snapshot_t v;
assert(ssh_transport_get_management_snapshot(NULL) == ESP_ERR_INVALID_ARG);
assert(ssh_transport_get_management_snapshot(&v) == ESP_OK && v.generation == 7 && v.running && !v.transitioning);
@@ -128,7 +159,7 @@ int main(void) {
puts("PASS SSH admitted timeout is not cancellation; failed cleanup and saturated versions preserve CLI recovery");
}
'''
names = ('next_generation', 'make_session_id', 'consume_external_close', 'find_free_slot', 'request_running_locked', 'request_running', 'ssh_transport_start', 'ssh_transport_stop', 'ssh_transport_get_management_snapshot', 'ssh_transport_manage_current')
names = ('next_generation', 'make_session_id', 'consume_external_close', 'find_free_slot', 'request_running_locked', 'request_running', 'ssh_transport_start', 'ssh_transport_stop', 'ssh_transport_replace_identity', 'ssh_transport_replace_host_key', 'ssh_transport_get_management_snapshot', 'ssh_transport_manage_current')
# Guard the accept path, which is not executed with the socket double here.
assert 'uint32_t generation = slot->generation + 1U;' in function('accept_connections')
assert 'next_generation(slot->generation)' not in source