Remove Legacy Credential Bootstrap Paths

Decouple user provisioning from HTTPS identity storage while retaining
compatible v1 user records and migrating TLS material to the
credential-free
v2 format. Add focused security regression coverage and update operator
documentation.
This commit is contained in:
2026-09-08 19:09:26 +02:00
parent 82f21d6116
commit ac80863d80
26 changed files with 1013 additions and 583 deletions
+72 -1
View File
@@ -6,7 +6,6 @@ static void reset(void)
s_candidate=&candidate_storage; s_mutex=(void *)1; s_initialized=true;
s_database.version=USER_DATABASE_SCHEMA_VERSION;
s_database.size=sizeof(s_database); s_database.generation=1;
s_database.admin_bootstrapped=1;
fail_stage=0; invalidate_during_derivation=false; derivation_invalidations=0;
assert(initialize_user(&s_database.users[0], (const uint8_t *)"admin", 5,
USER_ROLE_ADMIN, (const uint8_t *)"test-password", 13)==ESP_OK);
@@ -138,8 +137,80 @@ static void typed_password_tests(void)
}
assert(user_database_generate_password_value(NULL)==ESP_ERR_INVALID_ARG);
}
static void unload_database(void)
{
s_initialized=false; s_mutex=NULL; release_candidate();
secure_wipe(&s_database,sizeof(s_database));
}
static void storage_tests(void)
{
reset(); storage_test=true;
/* Both historical v1 states load without any account/verifier/ID changes. */
for (unsigned admins=0;admins<2;++admins) {
reset();
s_database.users[0].role=admins ? USER_ROLE_ADMIN : USER_ROLE_USER;
recount(&s_database);
stored_database_t before=s_database;
memcpy(persisted,&before,sizeof(before)); persisted_size=sizeof(before);
unload_database();
user_database_load_result_t result;
assert(user_database_init(&result)==ESP_OK && result==USER_DATABASE_LOAD_STORED);
assert(!memcmp(&before,&s_database,sizeof(before)) && !writes && !commits);
assert(user_database_recover_empty()==ESP_ERR_INVALID_STATE);
if (!admins) {
assert(user_database_delete((const uint8_t *)"admin",5)==ESP_OK);
assert(s_database.admin_count==0 && s_database.user_count==2);
}
}
for (unsigned kind=0;kind<4;++kind) {
reset(); stored_database_t bad=s_database;
if (kind==0) ++bad.version;
if (kind==1) bad.v1_admin_marker=0;
if (kind==2) bad.users[0].user_id=0;
memcpy(persisted,&bad,sizeof(bad)); persisted_size=sizeof(bad)-(kind==3);
size_t size=persisted_size;
unload_database(); user_database_load_result_t result;
assert(user_database_init(&result)!=ESP_OK && !s_initialized && !s_mutex);
assert(!writes && !commits && persisted_size==size && !memcmp(persisted,&bad,size));
web=false; remote=true;
assert(run("user recover --force")!=0 && !writes);
remote=false;
assert(run("user recover")!=0 && !writes);
assert(run("user recover --force")==0 && s_initialized);
assert(!s_database.user_count && !s_database.admin_count);
assert(validate_database(&s_database)==ESP_OK);
}
reset(); unload_database(); persisted_size=0;
user_database_load_result_t result;
assert(user_database_init(&result)==ESP_OK && result==USER_DATABASE_LOAD_EMPTY);
assert(s_initialized && !s_database.user_count && writes==1 && commits==1);
stored_database_t empty=s_database;
assert(persisted_size==sizeof(empty) && !memcmp(persisted,&empty,sizeof(empty)));
unload_database();
assert(user_database_init(&result)==ESP_OK && result==USER_DATABASE_LOAD_STORED);
assert(!memcmp(&empty,&s_database,sizeof(empty)) && writes==1 && commits==1);
user_database_snapshot_t snapshot;
assert(user_database_get_snapshot(&snapshot)==ESP_OK && snapshot.initialized);
assert(!snapshot.user_count && !snapshot.admin_count);
web=remote=false;
assert(run("user bootstrap")!=0 && run("user bootstrap --generate")!=0);
assert(run("user recover --force")!=0 && !memcmp(&empty,&s_database,sizeof(empty)));
assert(run("user add chief admin")==0 && s_database.admin_count==1);
assert(user_database_delete((const uint8_t *)"chief",5)==ESP_ERR_INVALID_STATE);
assert(user_database_set_role((const uint8_t *)"chief",5,USER_ROLE_USER)==ESP_ERR_INVALID_STATE);
for (unsigned stage=1;stage<=3;++stage) {
reset(); unload_database(); persisted_size=0; fail_stage=stage;
assert(user_database_init(&result)==ESP_FAIL && !s_initialized && !s_mutex);
assert(!persisted_size);
assert(user_database_recover_empty()==ESP_FAIL && !s_initialized && !s_mutex);
}
storage_test=false;
}
int main(void)
{
storage_tests();
typed_account_tests();
typed_password_tests();
const char *supported[]={