Replace Web Basic Auth With Cookie Sessions
Add bounded login challenges, CSRF/origin enforcement, logout, and session-bound WebSocket admission. Isolate private HTTPD access behind a version-guarded adapter and add focused host coverage. Also let empty admin SSH input reach the normal console handler.
This commit is contained in:
+38
-16
@@ -14,6 +14,8 @@
|
||||
#include "sdkconfig.h"
|
||||
#include "secure_random.h"
|
||||
#include "serial_service.h"
|
||||
#include "web_auth_parse.h"
|
||||
#include "web_httpd_adapter.h"
|
||||
|
||||
#if !defined(CONFIG_HTTPD_WS_SUPPORT) || !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error "web_serial_transport requires CONFIG_HTTPD_WS_SUPPORT"
|
||||
@@ -104,8 +106,7 @@ static uint64_t s_ticket_epoch;
|
||||
static esp_err_t identity_is_current(const user_principal_t *principal,
|
||||
web_session_id_t id, bool *current)
|
||||
{
|
||||
return id == 0U ? user_database_principal_is_current(principal, current)
|
||||
: web_session_store_check_principal(id, principal, current);
|
||||
return web_session_store_check_principal(id, principal, current);
|
||||
}
|
||||
|
||||
static TickType_t milliseconds_to_ticks(uint32_t milliseconds)
|
||||
@@ -270,9 +271,6 @@ static esp_err_t validate_origin(httpd_req_t *request)
|
||||
|
||||
esp_err_t result = httpd_req_get_hdr_value_str(
|
||||
request, "Origin", origin, sizeof(origin));
|
||||
if (result == ESP_ERR_NOT_FOUND) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (result != ESP_OK) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
@@ -284,9 +282,7 @@ static esp_err_t validate_origin(httpd_req_t *request)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int written = snprintf(expected, sizeof(expected), "https://%s", host);
|
||||
bool matches = written > 0 && (size_t)written < sizeof(expected) &&
|
||||
strcmp(origin, expected) == 0;
|
||||
bool matches = web_auth_parse_origin(host, strlen(host), origin, strlen(origin), expected);
|
||||
secure_wipe(origin, sizeof(origin));
|
||||
secure_wipe(host, sizeof(host));
|
||||
secure_wipe(expected, sizeof(expected));
|
||||
@@ -652,6 +648,12 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd,
|
||||
}
|
||||
|
||||
bool activated = false;
|
||||
/* Ticket and principal admission must succeed before HTTP 101. */
|
||||
result = web_httpd_upgrade(request, web_serial_transport_ws_handler);
|
||||
if (result != ESP_OK) {
|
||||
close_unpublished_broker_session(slot, slot_generation, client_id);
|
||||
goto cleanup;
|
||||
}
|
||||
int64_t next_currentness_check_us =
|
||||
monotonic_time_us() + WEB_SERIAL_CURRENTNESS_INTERVAL_US;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
@@ -1576,6 +1578,26 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Reclaim stale identities without database calls under the transport lock.
|
||||
* A late result must not clear a ticket published into the same array slot. */
|
||||
for (size_t i = 0; i < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++i) {
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
web_serial_ticket_t candidate = s_tickets[i];
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
bool live = false;
|
||||
if (candidate.active &&
|
||||
(identity_is_current(&candidate.principal, candidate.web_session_id, &live) != ESP_OK || !live)) {
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
web_serial_ticket_t *entry = &s_tickets[i];
|
||||
if (entry->active && entry->web_session_id == candidate.web_session_id &&
|
||||
entry->expires_at_us == candidate.expires_at_us &&
|
||||
constant_time_equal(entry->digest, candidate.digest, sizeof(entry->digest)))
|
||||
clear_ticket_locked(entry);
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
secure_wipe(&candidate, sizeof(candidate));
|
||||
}
|
||||
|
||||
uint8_t random_bytes[WEB_SERIAL_RANDOM_BYTES] = {0};
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES] = {0};
|
||||
result = secure_random_fill(random_bytes, sizeof(random_bytes));
|
||||
@@ -1602,7 +1624,6 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
epoch != UINT64_MAX) {
|
||||
purge_tickets_locked(now_us);
|
||||
size_t selected = WEB_SERIAL_TRANSPORT_MAX_TICKETS;
|
||||
int64_t oldest_expiry = INT64_MAX;
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_TICKETS;
|
||||
++index) {
|
||||
web_serial_ticket_t *entry = &s_tickets[index];
|
||||
@@ -1610,10 +1631,6 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
selected = index;
|
||||
break;
|
||||
}
|
||||
if (entry->expires_at_us < oldest_expiry) {
|
||||
oldest_expiry = entry->expires_at_us;
|
||||
selected = index;
|
||||
}
|
||||
}
|
||||
if (selected < WEB_SERIAL_TRANSPORT_MAX_TICKETS) {
|
||||
web_serial_ticket_t *entry = &s_tickets[selected];
|
||||
@@ -1688,6 +1705,9 @@ esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
if (result == ESP_OK) {
|
||||
result = httpd_resp_set_hdr(request, "X-Content-Type-Options", "nosniff");
|
||||
}
|
||||
if (result == ESP_OK) {
|
||||
result = httpd_resp_set_hdr(request, "Referrer-Policy", "no-referrer");
|
||||
}
|
||||
if (result == ESP_OK) {
|
||||
result = httpd_resp_send(request, response, written);
|
||||
}
|
||||
@@ -1715,12 +1735,14 @@ esp_err_t web_serial_transport_session_ws_handler(httpd_req_t *request,
|
||||
|
||||
httpd_ws_client_info_t info =
|
||||
httpd_ws_get_fd_info(request->handle, socket_fd);
|
||||
if (info == HTTPD_WS_CLIENT_HTTP) {
|
||||
bool opening = request->sess_ctx == NULL && web_session_id != 0U &&
|
||||
request->method == HTTP_GET && web_httpd_upgrade_requested(request);
|
||||
if (info == HTTPD_WS_CLIENT_HTTP && !opening) {
|
||||
(void)send_plain_bad_request(request);
|
||||
add_counter(&s_counters.protocol_errors, 1U);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (info != HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
if (info != HTTPD_WS_CLIENT_WEBSOCKET && !opening) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -1736,7 +1758,7 @@ esp_err_t web_serial_transport_session_ws_handler(httpd_req_t *request,
|
||||
|
||||
esp_err_t result;
|
||||
if (request->sess_ctx == NULL) {
|
||||
/* IDF has already sent 101; authentication failures must only close. */
|
||||
/* The registered HTTP route defers 101 until admission. */
|
||||
result = connect_websocket(request, socket_fd, web_session_id);
|
||||
} else {
|
||||
result = process_websocket_frame(request);
|
||||
|
||||
Reference in New Issue
Block a user