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:
@@ -41,6 +41,20 @@ enum { ESP_OK, ESP_FAIL, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE,
|
||||
typedef void *SemaphoreHandle_t;
|
||||
#define portMAX_DELAY 0
|
||||
#define NVS_READWRITE 1
|
||||
#define NVS_READONLY 0
|
||||
#define ESP_ERR_NVS_NOT_FOUND 100
|
||||
static uint8_t persisted[65536], staged[65536];
|
||||
static size_t persisted_size, staged_size;
|
||||
static bool storage_test;
|
||||
static void *xSemaphoreCreateMutex(void) { return (void *)1; }
|
||||
static void vSemaphoreDelete(void *m) { (void)m; }
|
||||
static int nvs_flash_init(void) { return ESP_OK; }
|
||||
static int nvs_get_blob(int h, const char *key, void *out, size_t *n) {
|
||||
(void)h; (void)key;
|
||||
if (!persisted_size) return ESP_ERR_NVS_NOT_FOUND;
|
||||
if (out) { assert(*n>=persisted_size); memcpy(out,persisted,persisted_size); }
|
||||
*n=persisted_size; return ESP_OK;
|
||||
}
|
||||
typedef int nvs_handle_t;
|
||||
static unsigned locks, writes, commits, random_calls, prompts, checks, web_revokes, ssh_revokes;
|
||||
static unsigned fail_stage, revoke_prompt, revoke_check, derivation_invalidations;
|
||||
@@ -53,16 +67,21 @@ static int xSemaphoreTake(void *m, int t) { (void)m; last_wait=t; if (snapshot_b
|
||||
static void xSemaphoreGive(void *m) { (void)m; assert(locks-- == 1); }
|
||||
static const char *esp_err_to_name(int e) { (void)e; return "injected error"; }
|
||||
static int nvs_open(const char *ns, int mode, int *h) {
|
||||
(void)ns; (void)mode; assert(locks);
|
||||
(void)ns; (void)mode; assert(locks || storage_test);
|
||||
if (invalidate_during_derivation) {
|
||||
assert(derivation_invalidations==1 && !owner_current);
|
||||
}
|
||||
*h=1; return fail_stage==1 ? ESP_FAIL : ESP_OK;
|
||||
}
|
||||
static int nvs_set_blob(int h, const char *key, const void *data, size_t n) {
|
||||
(void)h; (void)key; (void)data; (void)n; ++writes; return fail_stage==2 ? ESP_FAIL : ESP_OK;
|
||||
(void)h; (void)key; ++writes;
|
||||
if (fail_stage==2) return ESP_FAIL;
|
||||
assert(n<=sizeof(staged)); memcpy(staged,data,n); staged_size=n; return ESP_OK;
|
||||
}
|
||||
static int nvs_commit(int h) {
|
||||
(void)h; ++commits; if (fail_stage==3) return ESP_FAIL;
|
||||
memcpy(persisted,staged,staged_size); persisted_size=staged_size; return ESP_OK;
|
||||
}
|
||||
static int nvs_commit(int h) { (void)h; ++commits; return fail_stage==3 ? ESP_FAIL : ESP_OK; }
|
||||
static void nvs_close(int h) { (void)h; }
|
||||
static int secure_random_fill(void *p, size_t n) {
|
||||
memset(p, ++random_calls, n); return fail_stage==4 ? ESP_FAIL : ESP_OK;
|
||||
@@ -108,6 +127,8 @@ state = db[db.index("#define USER_DATABASE_SCHEMA_VERSION"):db.index("static esp
|
||||
fakes = r'''
|
||||
|
||||
static stored_database_t candidate_storage;
|
||||
static int allocate_candidate(void) { s_candidate=&candidate_storage; return ESP_OK; }
|
||||
static void release_candidate(void) { secure_wipe(&candidate_storage,sizeof(candidate_storage)); s_candidate=NULL; }
|
||||
static user_principal_t actor;
|
||||
static bool admin_ssh_console_dispatch_is_remote(void) { return remote; }
|
||||
static bool admin_ssh_console_dispatch_is_web(void) { return web; }
|
||||
@@ -138,8 +159,7 @@ static int ssh_transport_revoke_user(const uint8_t *u, size_t n) {
|
||||
}
|
||||
/* Forbidden paths are traps rather than alternative implementations. */
|
||||
static int show_users(const char *n) { (void)n; return 0; }
|
||||
static int recover_database(void) { assert(!"recovery"); return 1; }
|
||||
static int bootstrap(bool g) { (void)g; assert(!"bootstrap"); return 1; }
|
||||
|
||||
static int add_key(const char *n) { (void)n; assert(!"key mutation"); return 1; }
|
||||
static int add_key_parts(const char *n,const uint8_t *t,size_t tl,const uint8_t *b,size_t bl) {
|
||||
(void)n; (void)t; (void)tl; (void)b; (void)bl; assert(!"key mutation"); return 1;
|
||||
@@ -167,10 +187,12 @@ db_names = ["constant_time_equal", "all_zero", "user_database_username_valid",
|
||||
"user_database_get_account_keys", "add_ssh_key", "remove_ssh_key", "clear_ssh_keys",
|
||||
"user_database_add_ssh_key", "user_database_remove_ssh_key", "user_database_clear_ssh_keys",
|
||||
"key_target_valid", "user_database_add_ssh_key_current", "user_database_remove_ssh_key_current",
|
||||
"user_database_clear_ssh_keys_current", "fill_principal", "user_database_authorize_ssh_public_key"]
|
||||
"user_database_clear_ssh_keys_current", "fill_principal", "user_database_authorize_ssh_public_key",
|
||||
"initialize_dummy_verifier", "user_database_init", "user_database_recover_empty",
|
||||
"user_database_get_snapshot"]
|
||||
console_names = ["print_usage", "revoke_user_network_sessions", "read_password",
|
||||
"show_generated_password", "mutation_currentness", "add_user", "change_password",
|
||||
"parse_key_index", "command_user_inner", "command_user"]
|
||||
"parse_key_index", "recover_database", "command_user_inner", "command_user"]
|
||||
unit = prelude + header + "\n" + state + fakes
|
||||
unit += "\n".join(function(db, n) for n in db_names)
|
||||
unit += function(admin, "admin_ssh_console_web_user_command_allowed")
|
||||
@@ -190,6 +212,7 @@ with tempfile.TemporaryDirectory(prefix="admin-accounts-") as directory:
|
||||
result = subprocess.run([str(path / "test")], check=True, timeout=10, capture_output=True, text=True)
|
||||
assert "test-password" not in result.stdout
|
||||
assert "Generated password for" not in result.stdout
|
||||
print("PASS: empty initialization/recovery, unchanged v1 records, corrupt/unsupported fail-closed loads, first UART0 administrator and removed bootstrap commands")
|
||||
print("PASS: canonical SSH keys: Ed25519/P256 parser and authorization, malformed/off-curve/truncated inputs, zero-wait fingerprints, stale ID/generation/recreation, duplicates/capacity, sparse indices, failed persistence and CLI parity (OpenSSL-backed curve/SHA adapters)")
|
||||
print("PASS: operation-admission semantics: browser invalidated in derivation double before NVS; admitted add/password transactions still commit, only target is revoked, next command rejects; persistence failure still preserves live state (not precommit cancellation or real concurrency)")
|
||||
print("PASS: canonical parsed accounts + production DB transactions: nonself isolation, prompt revocation/cancel/mismatch, currentness, persistence/RNG/derive failures, final-admin invariants, self/generated/key/recovery traps; no password output")
|
||||
|
||||
Reference in New Issue
Block a user