Implement role-aware HTTPS and SSH authentication

This commit is contained in:
2026-08-30 01:31:05 +02:00
parent cd235445c7
commit 0c058b6a8f
16 changed files with 707 additions and 331 deletions
+264 -36
View File
@@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-3.0-only */
/* Password-authenticated SSH stream transport with two fixed broker sessions. */
/* Role-aware SSH stream transport with two fixed broker sessions. */
#include "ssh_transport.h"
@@ -22,7 +22,7 @@
#include "secure_random.h"
#include "serial_service.h"
#include "ssh_security.h"
#include "web_security.h"
#include "user_database.h"
#include <wolfssl/wolfcrypt/memory.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssh/ssh.h>
@@ -57,6 +57,10 @@ typedef struct {
int socket_fd;
WOLFSSH *ssh;
session_broker_client_id_t broker_client_id;
user_principal_t principal;
user_principal_t pending_principal;
bool principal_valid;
bool pending_principal_valid;
bool authenticated;
bool shell_requested;
uint8_t authentication_attempts;
@@ -126,6 +130,7 @@ static void publish_slot(const ssh_slot_t *slot, size_t slot_index)
ssh_transport_session_snapshot_t snapshot = {
.active = slot->state != SSH_TRANSPORT_SESSION_FREE,
.authenticated = slot->authenticated,
.principal_valid = slot->principal_valid,
.writer = slot->writer,
.close_requested = slot->close_requested,
.rx_pending = slot->rx_length > slot->rx_offset,
@@ -135,7 +140,15 @@ static void publish_slot(const ssh_slot_t *slot, size_t slot_index)
.socket_fd = slot->socket_fd,
.broker_client_id = slot->broker_client_id,
.state = slot->state,
.user_role = slot->principal_valid ? slot->principal.role : USER_ROLE_USER,
.auth_method = slot->principal_valid
? slot->principal.method
: USER_AUTH_METHOD_PASSWORD,
};
if (slot->principal_valid) {
memcpy(snapshot.username, slot->principal.username,
slot->principal.username_length);
}
memcpy(snapshot.peer, slot->peer, sizeof(snapshot.peer));
taskENTER_CRITICAL(&s_lock);
@@ -223,7 +236,111 @@ static int allowed_auth_types(WOLFSSH *ssh, void *context)
{
(void)ssh;
(void)context;
return WOLFSSH_USERAUTH_PASSWORD;
return WOLFSSH_USERAUTH_PASSWORD | WOLFSSH_USERAUTH_PUBLICKEY;
}
static void clear_pending_principal(ssh_slot_t *slot)
{
if (slot != NULL) {
secure_wipe(&slot->pending_principal, sizeof(slot->pending_principal));
slot->pending_principal_valid = false;
}
}
static bool complete_authentication_attempt(ssh_slot_t *slot, bool failed)
{
add_counter(&s_counters.authentication_attempts, 1U);
if (failed) {
add_counter(&s_counters.authentication_failures, 1U);
}
if (slot != NULL && slot->authentication_attempts < UINT8_MAX) {
++slot->authentication_attempts;
}
if (!failed || slot == NULL ||
slot->authentication_attempts < SSH_TRANSPORT_MAX_AUTH_ATTEMPTS) {
return true;
}
slot->close_requested = true;
if (slot->socket_fd >= 0) {
(void)shutdown(slot->socket_fd, SHUT_RDWR);
}
return false;
}
static int authenticate_password(ssh_slot_t *slot,
WS_UserAuthData *authentication)
{
clear_pending_principal(slot);
if (authentication->sf.password.hasNewPassword != 0U) {
(void)complete_authentication_attempt(slot, true);
return slot->close_requested ? WOLFSSH_USERAUTH_REJECTED
: WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
}
user_principal_t principal;
bool authenticated = false;
esp_err_t error = user_database_authenticate_password(
authentication->username, authentication->usernameSz,
authentication->sf.password.password,
authentication->sf.password.passwordSz,
&principal, &authenticated);
if (error == ESP_OK && authenticated) {
(void)complete_authentication_attempt(slot, false);
slot->principal = principal;
slot->principal_valid = true;
slot->authenticated = true;
return WOLFSSH_USERAUTH_SUCCESS;
}
secure_wipe(&principal, sizeof(principal));
bool retry = complete_authentication_attempt(slot, true);
if (!retry) {
return WOLFSSH_USERAUTH_REJECTED;
}
return error == ESP_OK ? WOLFSSH_USERAUTH_INVALID_PASSWORD
: WOLFSSH_USERAUTH_FAILURE;
}
static int authenticate_public_key(ssh_slot_t *slot,
WS_UserAuthData *authentication)
{
WS_UserAuthData_PublicKey *public_key = &authentication->sf.publicKey;
clear_pending_principal(slot);
user_principal_t principal;
bool authorized = false;
esp_err_t error = ESP_OK;
if (public_key->isCert != 0U) {
memset(&principal, 0, sizeof(principal));
} else {
error = user_database_authorize_ssh_public_key(
authentication->username, authentication->usernameSz,
public_key->publicKeyType, public_key->publicKeyTypeSz,
public_key->publicKey, public_key->publicKeySz,
&principal, &authorized);
}
if (error != ESP_OK || !authorized) {
secure_wipe(&principal, sizeof(principal));
if (public_key->hasSignature == 0U) {
return error == ESP_OK ? WOLFSSH_USERAUTH_INVALID_PUBLICKEY
: WOLFSSH_USERAUTH_FAILURE;
}
bool retry = complete_authentication_attempt(slot, true);
if (!retry) {
return WOLFSSH_USERAUTH_REJECTED;
}
return error == ESP_OK ? WOLFSSH_USERAUTH_INVALID_PUBLICKEY
: WOLFSSH_USERAUTH_FAILURE;
}
if (public_key->hasSignature != 0U) {
slot->pending_principal = principal;
slot->pending_principal_valid = true;
}
secure_wipe(&principal, sizeof(principal));
return WOLFSSH_USERAUTH_SUCCESS;
}
static int authenticate_user(byte authentication_type,
@@ -231,39 +348,56 @@ static int authenticate_user(byte authentication_type,
void *context)
{
ssh_slot_t *slot = (ssh_slot_t *)context;
add_counter(&s_counters.authentication_attempts, 1U);
if (slot != NULL && slot->authentication_attempts < UINT8_MAX) {
++slot->authentication_attempts;
}
if (slot == NULL || authentication == NULL ||
authentication_type != WOLFSSH_USERAUTH_PASSWORD ||
authentication->type != WOLFSSH_USERAUTH_PASSWORD ||
authentication->sf.password.hasNewPassword != 0U) {
add_counter(&s_counters.authentication_failures, 1U);
authentication_type != authentication->type) {
clear_pending_principal(slot);
return WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
}
bool authenticated = false;
esp_err_t error = web_security_authenticate_admin(
authentication->username, authentication->usernameSz,
authentication->sf.password.password,
authentication->sf.password.passwordSz,
&authenticated);
if (error == ESP_OK && authenticated) {
slot->authenticated = true;
return WOLFSSH_USERAUTH_SUCCESS;
if (authentication_type == WOLFSSH_USERAUTH_PASSWORD) {
return authenticate_password(slot, authentication);
}
if (authentication_type == WOLFSSH_USERAUTH_PUBLICKEY) {
return authenticate_public_key(slot, authentication);
}
add_counter(&s_counters.authentication_failures, 1U);
if (slot->authentication_attempts >= SSH_TRANSPORT_MAX_AUTH_ATTEMPTS) {
slot->close_requested = true;
if (slot->socket_fd >= 0) {
(void)shutdown(slot->socket_fd, SHUT_RDWR);
}
return WOLFSSH_USERAUTH_REJECTED;
clear_pending_principal(slot);
return WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
}
static int authentication_result(byte result, WS_UserAuthData *authentication,
void *context)
{
ssh_slot_t *slot = (ssh_slot_t *)context;
if (slot == NULL || authentication == NULL ||
authentication->type != WOLFSSH_USERAUTH_PUBLICKEY ||
authentication->sf.publicKey.hasSignature == 0U) {
clear_pending_principal(slot);
return WS_ERROR;
}
return error == ESP_OK ? WOLFSSH_USERAUTH_INVALID_PASSWORD
: WOLFSSH_USERAUTH_FAILURE;
if (result != WOLFSSH_USERAUTH_SUCCESS) {
(void)complete_authentication_attempt(slot, true);
clear_pending_principal(slot);
return WS_SUCCESS;
}
bool current = false;
esp_err_t error = slot->pending_principal_valid
? user_database_principal_is_current(
&slot->pending_principal, &current)
: ESP_ERR_INVALID_STATE;
if (error != ESP_OK || !current) {
(void)complete_authentication_attempt(slot, true);
clear_pending_principal(slot);
return WS_ERROR;
}
(void)complete_authentication_attempt(slot, false);
slot->principal = slot->pending_principal;
slot->principal_valid = true;
slot->authenticated = true;
clear_pending_principal(slot);
return WS_SUCCESS;
}
static int accept_shell(WOLFSSH_CHANNEL *channel, void *context)
@@ -382,6 +516,7 @@ static esp_err_t create_context(void)
wolfSSH_SetIORecv(context, bounded_ssh_receive);
wolfSSH_SetUserAuth(context, authenticate_user);
wolfSSH_SetUserAuthTypes(context, allowed_auth_types);
wolfSSH_SetUserAuthResult(context, authentication_result);
(void)wolfSSH_CTX_SetChannelReqShellCb(context, accept_shell);
(void)wolfSSH_CTX_SetChannelReqExecCb(context, reject_channel_request);
(void)wolfSSH_CTX_SetChannelReqSubsysCb(context, reject_channel_request);
@@ -640,11 +775,35 @@ static void accept_connections(void)
}
wolfSSH_SetIOReadCtx(slot->ssh, slot);
wolfSSH_SetUserAuthCtx(slot->ssh, slot);
wolfSSH_SetUserAuthResultCtx(slot->ssh, slot);
wolfSSH_SetChannelReqCtx(slot->ssh, slot);
publish_slot(slot, slot_index);
}
}
static bool slot_principal_is_current(const ssh_slot_t *slot)
{
bool current = false;
return slot->principal_valid &&
user_database_principal_is_current(&slot->principal, &current) == ESP_OK &&
current;
}
static void disconnect_failed_admission(ssh_slot_t *slot)
{
if (slot->broker_client_id == SESSION_BROKER_NO_CLIENT) {
return;
}
esp_err_t error = session_broker_disconnect(slot->broker_client_id);
if (error == ESP_OK || error == ESP_ERR_NOT_FOUND) {
slot->broker_client_id = SESSION_BROKER_NO_CLIENT;
slot->writer = false;
add_counter(&s_counters.disconnections, 1U);
} else {
add_counter(&s_counters.broker_failures, 1U);
}
}
static esp_err_t connect_broker(ssh_slot_t *slot, size_t slot_index)
{
if (!serial_service_is_running()) {
@@ -657,6 +816,10 @@ static esp_err_t connect_broker(ssh_slot_t *slot, size_t slot_index)
}
}
if (!slot_principal_is_current(slot)) {
return ESP_ERR_INVALID_STATE;
}
char name[SESSION_BROKER_CLIENT_NAME_MAX + 1U];
int written = snprintf(name, sizeof(name), "ssh-%u-%" PRIu32,
(unsigned int)slot_index, slot->generation);
@@ -670,19 +833,26 @@ static esp_err_t connect_broker(ssh_slot_t *slot, size_t slot_index)
return error;
}
add_counter(&s_counters.broker_connections, 1U);
if (!slot_principal_is_current(slot)) {
disconnect_failed_admission(slot);
return ESP_ERR_INVALID_STATE;
}
add_counter(&s_counters.writer_requests, 1U);
error = session_broker_request_writer(slot->broker_client_id);
if (error == ESP_OK) {
slot->writer = true;
add_counter(&s_counters.writer_grants, 1U);
return ESP_OK;
}
if (error == ESP_ERR_INVALID_STATE) {
} else if (error == ESP_ERR_INVALID_STATE) {
slot->writer = false;
add_counter(&s_counters.writer_denials, 1U);
return ESP_OK;
} else {
return error;
}
return error;
if (!slot_principal_is_current(slot)) {
disconnect_failed_admission(slot);
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
static void process_handshake(ssh_slot_t *slot, size_t slot_index)
@@ -696,7 +866,20 @@ static void process_handshake(ssh_slot_t *slot, size_t slot_index)
slot->io_read_budget = SSH_TRANSPORT_WOLFSSH_READ_BUDGET;
int result = wolfSSH_accept(slot->ssh);
if (result == WS_SUCCESS) {
if (!slot->authenticated || !slot->shell_requested ||
bool principal_current = false;
esp_err_t principal_error = slot->principal_valid
? user_database_principal_is_current(
&slot->principal, &principal_current)
: ESP_ERR_INVALID_STATE;
if (!slot->authenticated || !slot->principal_valid) {
request_slot_close(slot, false);
return;
}
if (principal_error != ESP_OK || !principal_current) {
request_slot_close(slot, true);
return;
}
if (!slot->shell_requested ||
wolfSSH_GetSessionType(slot->ssh) != WOLFSSH_SESSION_SHELL) {
add_counter(&s_counters.request_rejections, 1U);
request_slot_close(slot, false);
@@ -709,6 +892,11 @@ static void process_handshake(ssh_slot_t *slot, size_t slot_index)
request_slot_close(slot, false);
return;
}
if (!slot_principal_is_current(slot)) {
disconnect_failed_admission(slot);
request_slot_close(slot, true);
return;
}
slot->state = SSH_TRANSPORT_SESSION_ACTIVE;
slot->last_reconcile_us = esp_timer_get_time();
add_counter(&s_counters.handshake_successes, 1U);
@@ -728,6 +916,16 @@ static bool reconcile_writer(ssh_slot_t *slot)
}
slot->last_reconcile_us = now;
bool principal_current = false;
esp_err_t principal_error = slot->principal_valid
? user_database_principal_is_current(
&slot->principal, &principal_current)
: ESP_ERR_INVALID_STATE;
if (principal_error != ESP_OK || !principal_current) {
request_slot_close(slot, true);
return true;
}
session_broker_client_snapshot_t snapshot;
esp_err_t error = session_broker_get_client_snapshot(
slot->broker_client_id, &snapshot);
@@ -1201,6 +1399,36 @@ esp_err_t ssh_transport_disconnect(uint32_t session_id)
return ESP_ERR_NOT_FOUND;
}
esp_err_t ssh_transport_revoke_user(const uint8_t *username,
size_t username_length)
{
if (!user_database_username_valid(username, username_length)) {
return ESP_ERR_INVALID_ARG;
}
bool found = false;
taskENTER_CRITICAL(&s_lock);
if (!s_initialized) {
taskEXIT_CRITICAL(&s_lock);
return ESP_ERR_INVALID_STATE;
}
for (size_t index = 0U; index < SSH_TRANSPORT_MAX_SESSIONS; ++index) {
const ssh_transport_session_snapshot_t *session =
&s_session_snapshots[index];
if (session->active && session->principal_valid &&
strlen(session->username) == username_length &&
memcmp(session->username, username, username_length) == 0) {
s_external_close_id[index] = session->session_id;
found = true;
}
}
taskEXIT_CRITICAL(&s_lock);
if (found) {
notify_task();
}
return ESP_OK;
}
esp_err_t ssh_transport_revoke_sessions(void)
{
taskENTER_CRITICAL(&s_lock);