Files
ESP32_Serial_Swiss_Army_Knife/src/user_database.h
T

192 lines
7.9 KiB
C

/* SPDX-License-Identifier: GPL-3.0-only */
/* Bounded persistent role-based user and SSH authorized-key database. */
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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