/* SPDX-License-Identifier: GPL-3.0-only */ /* Bounded persistent role-based user and SSH authorized-key database. */ #pragma once #include #include #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif #define USER_DATABASE_NVS_NAMESPACE "user_db" #define USER_DATABASE_NVS_BLOB_KEY "database" #define USER_DATABASE_MAX_USERS 8U #define USER_DATABASE_MAX_SSH_KEYS_PER_USER 3U #define USER_DATABASE_USERNAME_CAPACITY 16U #define USER_DATABASE_PASSWORD_CAPACITY 64U #define USER_DATABASE_PASSWORD_MIN_LENGTH 12U #define USER_DATABASE_GENERATED_PASSWORD_LENGTH 24U #define USER_DATABASE_SSH_KEY_TYPE_CAPACITY 32U #define USER_DATABASE_SSH_KEY_BLOB_CAPACITY 128U #define USER_DATABASE_SHA256_LENGTH 32U #define USER_DATABASE_ERR_DUPLICATE_SSH_KEY ESP_ERR_NOT_ALLOWED typedef enum { USER_ROLE_USER = 1, USER_ROLE_ADMIN = 2, } user_role_t; typedef enum { USER_AUTH_METHOD_PASSWORD = 1, USER_AUTH_METHOD_SSH_PUBLIC_KEY = 2, } user_auth_method_t; typedef enum { USER_DATABASE_LOAD_STORED = 0, USER_DATABASE_LOAD_MIGRATED_LEGACY, USER_DATABASE_LOAD_EMPTY, } user_database_load_result_t; typedef struct { const uint8_t *username; size_t username_length; const uint8_t *password; size_t password_length; } user_database_legacy_credentials_t; typedef struct { uint32_t user_id; uint32_t auth_generation; user_role_t role; user_auth_method_t method; size_t username_length; char username[USER_DATABASE_USERNAME_CAPACITY + 1U]; } user_principal_t; typedef struct { size_t password_length; uint8_t password[USER_DATABASE_PASSWORD_CAPACITY + 1U]; } user_database_generated_password_t; typedef struct { bool active; uint8_t index; size_t key_type_length; char key_type[USER_DATABASE_SSH_KEY_TYPE_CAPACITY + 1U]; uint8_t sha256_fingerprint[USER_DATABASE_SHA256_LENGTH]; } user_database_key_snapshot_t; typedef struct { bool active; uint32_t user_id; uint32_t auth_generation; user_role_t role; size_t username_length; char username[USER_DATABASE_USERNAME_CAPACITY + 1U]; uint8_t public_key_count; user_database_key_snapshot_t public_keys[USER_DATABASE_MAX_SSH_KEYS_PER_USER]; } user_database_user_snapshot_t; typedef struct { bool initialized; bool admin_bootstrapped; uint32_t generation; uint8_t user_count; uint8_t admin_count; user_database_user_snapshot_t users[USER_DATABASE_MAX_USERS]; } user_database_snapshot_t; esp_err_t user_database_init(const user_database_legacy_credentials_t *legacy, user_database_load_result_t *load_result); /* * Before the first administrator is established, keep the migrated account in * sync with the legacy recovery credential. Once bootstrapped, that credential * remains independent and no longer authenticates Phase 8B network services. */ esp_err_t user_database_sync_legacy_credentials( const user_database_legacy_credentials_t *legacy, bool *synchronized); /* Explicit UART0 recovery: replace unavailable user storage with one legacy user. */ esp_err_t user_database_recover_from_legacy( const user_database_legacy_credentials_t *legacy); esp_err_t user_database_get_snapshot(user_database_snapshot_t *snapshot); /* Compact secret-free list, zero-wait mutex acquisition; no key material. */ typedef struct { uint32_t user_id, auth_generation; user_role_t role; char username[USER_DATABASE_USERNAME_CAPACITY + 1U]; } user_database_account_t; typedef struct { size_t count; user_database_account_t users[USER_DATABASE_MAX_USERS]; } user_database_accounts_t; esp_err_t user_database_get_accounts(user_database_accounts_t *accounts); /* Zero-wait, identity-conditional projection; fingerprints only, no key blobs. * Output is cleared on failure; absent/stale identity returns NOT_FOUND. */ esp_err_t user_database_get_account_keys(const user_database_account_t *expected, user_database_user_snapshot_t *snapshot); esp_err_t user_database_add_ssh_key_current(const user_database_account_t *expected, const uint8_t *key_type, size_t key_type_length, const uint8_t *key_blob, size_t key_blob_length, uint8_t *key_index); esp_err_t user_database_remove_ssh_key_current(const user_database_account_t *expected, uint8_t key_index); esp_err_t user_database_clear_ssh_keys_current(const user_database_account_t *expected); /* Compare target identity under the mutation lock, before candidate/commit. * ESP_ERR_NOT_FOUND means absent or stale; existing account invariants apply. */ esp_err_t user_database_delete_current(const user_database_account_t *expected); esp_err_t user_database_set_role_current(const user_database_account_t *expected, user_role_t role); esp_err_t user_database_set_password_current(const user_database_account_t *expected, const uint8_t *password, size_t password_length); /* RNG only: no database initialization, account mutation or persistence. Caller * owns/wipes successful output; failures clear it. Same generator as CLI. */ esp_err_t user_database_generate_password_value(user_database_generated_password_t *generated); esp_err_t user_database_authenticate_password( const uint8_t *username, size_t username_length, const uint8_t *password, size_t password_length, user_principal_t *principal, bool *authenticated); esp_err_t user_database_authorize_ssh_public_key( const uint8_t *username, size_t username_length, const uint8_t *key_type, size_t key_type_length, const uint8_t *key_blob, size_t key_blob_length, user_principal_t *principal, bool *authorized); esp_err_t user_database_principal_is_current(const user_principal_t *principal, bool *current); esp_err_t user_database_bootstrap_admin(const uint8_t *password, size_t password_length); esp_err_t user_database_bootstrap_admin_generated( user_database_generated_password_t *generated_password); esp_err_t user_database_create(const uint8_t *username, size_t username_length, user_role_t role, const uint8_t *password, size_t password_length); esp_err_t user_database_create_generated( const uint8_t *username, size_t username_length, user_role_t role, user_database_generated_password_t *generated_password); esp_err_t user_database_delete(const uint8_t *username, size_t username_length); esp_err_t user_database_set_role(const uint8_t *username, size_t username_length, user_role_t role); esp_err_t user_database_set_password(const uint8_t *username, size_t username_length, const uint8_t *password, size_t password_length); esp_err_t user_database_generate_password( const uint8_t *username, size_t username_length, user_database_generated_password_t *generated_password); esp_err_t user_database_add_ssh_key( const uint8_t *username, size_t username_length, const uint8_t *key_type, size_t key_type_length, const uint8_t *key_blob, size_t key_blob_length, uint8_t *key_index); esp_err_t user_database_remove_ssh_key(const uint8_t *username, size_t username_length, uint8_t key_index); esp_err_t user_database_clear_ssh_keys(const uint8_t *username, size_t username_length); bool user_database_username_valid(const uint8_t *username, size_t length); bool user_database_password_valid(const uint8_t *password, size_t length); bool user_database_key_valid(const uint8_t *key_type, size_t key_type_length, const uint8_t *key_blob, size_t key_blob_length); const char *user_role_to_string(user_role_t role); bool user_role_parse(const char *text, user_role_t *role); #ifdef __cplusplus } #endif