Implement role-aware HTTPS and SSH authentication
This commit is contained in:
+241
-68
@@ -14,7 +14,6 @@
|
||||
#include "sdkconfig.h"
|
||||
#include "secure_random.h"
|
||||
#include "serial_service.h"
|
||||
#include "web_security.h"
|
||||
|
||||
#if !defined(CONFIG_HTTPD_WS_SUPPORT) || !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error "web_serial_transport requires CONFIG_HTTPD_WS_SUPPORT"
|
||||
@@ -30,6 +29,7 @@
|
||||
#define WEB_SERIAL_TASK_PRIORITY 4U
|
||||
#define WEB_SERIAL_ACTIVE_BURST_LOOPS 8U
|
||||
#define WEB_SERIAL_POLL_MS 5U
|
||||
#define WEB_SERIAL_CURRENTNESS_INTERVAL_US 250000LL
|
||||
#define WEB_SERIAL_DETACH_TIMEOUT_US 1000000LL
|
||||
|
||||
_Static_assert(WEB_SERIAL_TRANSPORT_TICKET_LENGTH ==
|
||||
@@ -48,7 +48,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES];
|
||||
int64_t expires_at_us;
|
||||
uint32_t material_generation;
|
||||
user_principal_t principal;
|
||||
bool active;
|
||||
} web_serial_ticket_t;
|
||||
|
||||
@@ -67,6 +67,8 @@ typedef struct web_serial_slot {
|
||||
int socket_fd;
|
||||
uint32_t generation;
|
||||
session_broker_client_id_t broker_client_id;
|
||||
user_principal_t principal;
|
||||
int64_t next_currentness_check_us;
|
||||
bool writer;
|
||||
bool hello_pending;
|
||||
bool work_pending;
|
||||
@@ -142,8 +144,8 @@ static bool slot_pointer_valid(const web_serial_slot_t *slot)
|
||||
static void clear_ticket_locked(web_serial_ticket_t *ticket)
|
||||
{
|
||||
secure_wipe(ticket->digest, sizeof(ticket->digest));
|
||||
secure_wipe(&ticket->principal, sizeof(ticket->principal));
|
||||
ticket->expires_at_us = 0;
|
||||
ticket->material_generation = 0U;
|
||||
ticket->active = false;
|
||||
}
|
||||
|
||||
@@ -154,20 +156,13 @@ static void clear_all_tickets_locked(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void purge_tickets_locked(int64_t now_us, uint32_t material_generation,
|
||||
bool check_generation)
|
||||
static void purge_tickets_locked(int64_t now_us)
|
||||
{
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++index) {
|
||||
web_serial_ticket_t *ticket = &s_tickets[index];
|
||||
if (!ticket->active) {
|
||||
continue;
|
||||
}
|
||||
if (ticket->expires_at_us <= now_us) {
|
||||
if (ticket->active && ticket->expires_at_us <= now_us) {
|
||||
clear_ticket_locked(ticket);
|
||||
++s_counters.tickets_expired;
|
||||
} else if (check_generation &&
|
||||
ticket->material_generation != material_generation) {
|
||||
clear_ticket_locked(ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,16 +204,6 @@ static bool base64url_character(char value)
|
||||
(value >= '0' && value <= '9') || value == '-' || value == '_';
|
||||
}
|
||||
|
||||
static esp_err_t current_material_generation(uint32_t *generation)
|
||||
{
|
||||
web_security_certificate_metadata_t metadata;
|
||||
esp_err_t result = web_security_get_certificate_metadata(&metadata);
|
||||
if (result == ESP_OK) {
|
||||
*generation = metadata.material_generation;
|
||||
}
|
||||
secure_wipe(&metadata, sizeof(metadata));
|
||||
return result;
|
||||
}
|
||||
|
||||
static esp_err_t sha256_ticket(const char *ticket,
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES])
|
||||
@@ -295,16 +280,16 @@ static esp_err_t validate_origin(httpd_req_t *request)
|
||||
return matches ? ESP_OK : ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static esp_err_t consume_ticket(const char *ticket, bool *consumed)
|
||||
static esp_err_t consume_ticket(const char *ticket,
|
||||
user_principal_t *principal, bool *consumed)
|
||||
{
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES] = {0};
|
||||
uint32_t generation = 0U;
|
||||
user_principal_t candidate = {0};
|
||||
bool ticket_found = false;
|
||||
*consumed = false;
|
||||
memset(principal, 0, sizeof(*principal));
|
||||
|
||||
esp_err_t result = current_material_generation(&generation);
|
||||
if (result == ESP_OK) {
|
||||
result = sha256_ticket(ticket, digest);
|
||||
}
|
||||
esp_err_t result = sha256_ticket(ticket, digest);
|
||||
if (result != ESP_OK) {
|
||||
secure_wipe(digest, sizeof(digest));
|
||||
add_counter(&s_counters.tickets_rejected, 1U);
|
||||
@@ -326,36 +311,45 @@ static esp_err_t consume_ticket(const char *ticket, bool *consumed)
|
||||
}
|
||||
}
|
||||
|
||||
purge_tickets_locked(now_us, generation, true);
|
||||
purge_tickets_locked(now_us);
|
||||
if (matching_count == 1U &&
|
||||
matching_index < WEB_SERIAL_TRANSPORT_MAX_TICKETS) {
|
||||
web_serial_ticket_t *entry = &s_tickets[matching_index];
|
||||
if (entry->active && entry->expires_at_us > now_us &&
|
||||
entry->material_generation == generation) {
|
||||
if (entry->active && entry->expires_at_us > now_us) {
|
||||
candidate = entry->principal;
|
||||
clear_ticket_locked(entry);
|
||||
++s_counters.tickets_consumed;
|
||||
*consumed = true;
|
||||
ticket_found = true;
|
||||
}
|
||||
}
|
||||
if (!*consumed) {
|
||||
if (!ticket_found && matching_count > 1U) {
|
||||
/* A defensive duplicate can never become a repeatedly usable ticket. */
|
||||
if (matching_count > 1U) {
|
||||
for (size_t index = 0U;
|
||||
index < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++index) {
|
||||
web_serial_ticket_t *entry = &s_tickets[index];
|
||||
if (entry->active && constant_time_equal(
|
||||
entry->digest, digest,
|
||||
sizeof(digest))) {
|
||||
clear_ticket_locked(entry);
|
||||
}
|
||||
for (size_t index = 0U;
|
||||
index < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++index) {
|
||||
web_serial_ticket_t *entry = &s_tickets[index];
|
||||
if (entry->active && constant_time_equal(
|
||||
entry->digest, digest, sizeof(digest))) {
|
||||
clear_ticket_locked(entry);
|
||||
}
|
||||
}
|
||||
++s_counters.tickets_rejected;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
|
||||
if (ticket_found) {
|
||||
bool current = false;
|
||||
result = user_database_principal_is_current(&candidate, ¤t);
|
||||
if (result == ESP_OK && current) {
|
||||
*principal = candidate;
|
||||
*consumed = true;
|
||||
add_counter(&s_counters.tickets_consumed, 1U);
|
||||
}
|
||||
}
|
||||
if (!*consumed) {
|
||||
add_counter(&s_counters.tickets_rejected, 1U);
|
||||
}
|
||||
|
||||
secure_wipe(&candidate, sizeof(candidate));
|
||||
secure_wipe(digest, sizeof(digest));
|
||||
return ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
static web_serial_slot_t *reserve_slot(httpd_handle_t server, int socket_fd,
|
||||
@@ -376,6 +370,8 @@ static web_serial_slot_t *reserve_slot(httpd_handle_t server, int socket_fd,
|
||||
slot->server = server;
|
||||
slot->socket_fd = socket_fd;
|
||||
slot->broker_client_id = SESSION_BROKER_NO_CLIENT;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
slot->work_pending = false;
|
||||
@@ -399,6 +395,8 @@ static void make_slot_free_locked(web_serial_slot_t *slot)
|
||||
slot->server = NULL;
|
||||
slot->socket_fd = -1;
|
||||
slot->broker_client_id = SESSION_BROKER_NO_CLIENT;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
slot->work_pending = false;
|
||||
@@ -481,6 +479,8 @@ static void web_serial_session_free(void *context)
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE) {
|
||||
slot->state = WEB_SERIAL_SLOT_CLOSING;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
slot->close_requested = false;
|
||||
@@ -517,6 +517,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
char ticket[WEB_SERIAL_TRANSPORT_TICKET_CAPACITY] = {0};
|
||||
uint32_t slot_generation = 0U;
|
||||
web_serial_slot_t *slot = NULL;
|
||||
user_principal_t principal = {0};
|
||||
bool consumed = false;
|
||||
esp_err_t result;
|
||||
|
||||
@@ -534,12 +535,25 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = consume_ticket(ticket, &consumed);
|
||||
result = consume_ticket(ticket, &principal, &consumed);
|
||||
if (result != ESP_OK || !consumed) {
|
||||
release_reserved_slot(slot, slot_generation);
|
||||
result = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool principal_staged = slot->state == WEB_SERIAL_SLOT_RESERVED &&
|
||||
slot->generation == slot_generation &&
|
||||
!slot->close_requested;
|
||||
if (principal_staged) {
|
||||
slot->principal = principal;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!principal_staged) {
|
||||
release_reserved_slot(slot, slot_generation);
|
||||
result = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!serial_service_is_running()) {
|
||||
result = serial_service_start();
|
||||
@@ -556,6 +570,14 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
}
|
||||
}
|
||||
|
||||
bool principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
release_reserved_slot(slot, slot_generation);
|
||||
result = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
size_t slot_index = (size_t)(slot - s_slots);
|
||||
char client_name[SESSION_BROKER_CLIENT_NAME_MAX + 1U];
|
||||
int written = snprintf(client_name, sizeof(client_name),
|
||||
@@ -579,6 +601,14 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
close_unpublished_broker_session(slot, slot_generation, client_id);
|
||||
result = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
add_counter(&s_counters.writer_requests, 1U);
|
||||
result = session_broker_request_writer(client_id);
|
||||
bool writer = result == ESP_OK;
|
||||
@@ -594,13 +624,25 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
close_unpublished_broker_session(slot, slot_generation, client_id);
|
||||
result = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bool activated = false;
|
||||
int64_t next_currentness_check_us =
|
||||
monotonic_time_us() + WEB_SERIAL_CURRENTNESS_INTERVAL_US;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (slot->state == WEB_SERIAL_SLOT_RESERVED &&
|
||||
slot->generation == slot_generation &&
|
||||
slot->generation == slot_generation && !slot->close_requested &&
|
||||
s_server == request->handle) {
|
||||
slot->state = WEB_SERIAL_SLOT_ACTIVE;
|
||||
slot->broker_client_id = client_id;
|
||||
slot->principal = principal;
|
||||
slot->next_currentness_check_us = next_currentness_check_us;
|
||||
slot->writer = writer;
|
||||
slot->hello_pending = true;
|
||||
++s_counters.connections;
|
||||
@@ -622,13 +664,15 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
result = ESP_OK;
|
||||
|
||||
cleanup:
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
secure_wipe(ticket, sizeof(ticket));
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool capture_active_session(httpd_req_t *request, web_serial_slot_t **slot_out,
|
||||
uint32_t *generation,
|
||||
session_broker_client_id_t *client_id)
|
||||
session_broker_client_id_t *client_id,
|
||||
user_principal_t *principal)
|
||||
{
|
||||
web_serial_slot_t *slot = request->sess_ctx;
|
||||
int socket_fd = httpd_req_to_sockfd(request);
|
||||
@@ -647,6 +691,7 @@ static bool capture_active_session(httpd_req_t *request, web_serial_slot_t **slo
|
||||
*slot_out = slot;
|
||||
*generation = slot->generation;
|
||||
*client_id = slot->broker_client_id;
|
||||
*principal = slot->principal;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return valid;
|
||||
@@ -757,7 +802,9 @@ static esp_err_t process_websocket_frame(httpd_req_t *request)
|
||||
web_serial_slot_t *slot = NULL;
|
||||
uint32_t generation = 0U;
|
||||
session_broker_client_id_t client_id = SESSION_BROKER_NO_CLIENT;
|
||||
if (!capture_active_session(request, &slot, &generation, &client_id)) {
|
||||
user_principal_t principal = {0};
|
||||
if (!capture_active_session(request, &slot, &generation, &client_id,
|
||||
&principal)) {
|
||||
add_counter(&s_counters.protocol_errors, 1U);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -765,27 +812,52 @@ static esp_err_t process_websocket_frame(httpd_req_t *request)
|
||||
int socket_fd = httpd_req_to_sockfd(request);
|
||||
if (httpd_ws_get_fd_info(request->handle, socket_fd) !=
|
||||
HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
return reject_protocol_frame(slot, generation, 0U);
|
||||
}
|
||||
|
||||
httpd_ws_frame_t frame = {0};
|
||||
esp_err_t result = httpd_ws_recv_frame(request, &frame, 0U);
|
||||
if (result != ESP_OK) {
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
return reject_protocol_frame(slot, generation, frame.len);
|
||||
}
|
||||
if (!frame.final || frame.type == HTTPD_WS_TYPE_CONTINUE ||
|
||||
frame.len > WEB_SERIAL_TRANSPORT_MAX_RX_PAYLOAD ||
|
||||
(frame.type != HTTPD_WS_TYPE_BINARY &&
|
||||
frame.type != HTTPD_WS_TYPE_TEXT)) {
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
return reject_protocol_frame(slot, generation, frame.len);
|
||||
}
|
||||
|
||||
frame.payload = slot->rx_data;
|
||||
result = httpd_ws_recv_frame(request, &frame, sizeof(slot->rx_data));
|
||||
if (result != ESP_OK) {
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
return reject_protocol_frame(slot, generation, frame.len);
|
||||
}
|
||||
|
||||
bool current = false;
|
||||
esp_err_t currentness_result =
|
||||
user_database_principal_is_current(&principal, ¤t);
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
if (currentness_result != ESP_OK || !current) {
|
||||
request_handler_close(slot, generation);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool still_active = slot->state == WEB_SERIAL_SLOT_ACTIVE &&
|
||||
!slot->close_requested &&
|
||||
slot->generation == generation &&
|
||||
slot->broker_client_id == client_id &&
|
||||
slot->server == request->handle &&
|
||||
slot->server == s_server;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!still_active) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (frame.type == HTTPD_WS_TYPE_BINARY) {
|
||||
return process_binary_frame(slot, generation, client_id,
|
||||
slot->rx_data, frame.len);
|
||||
@@ -817,19 +889,31 @@ static void web_serial_send_work(void *argument)
|
||||
httpd_ws_type_t type;
|
||||
size_t length;
|
||||
bool valid;
|
||||
bool retired = false;
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
valid = slot->work_pending && work == &slot->work &&
|
||||
work->generation == slot->generation &&
|
||||
work->server == slot->server &&
|
||||
work->socket_fd == slot->socket_fd;
|
||||
bool owned_work = slot->work_pending && work == &slot->work &&
|
||||
work->generation == slot->generation &&
|
||||
work->server == slot->server &&
|
||||
work->socket_fd == slot->socket_fd;
|
||||
valid = owned_work && slot->state == WEB_SERIAL_SLOT_ACTIVE &&
|
||||
!slot->close_requested;
|
||||
server = work->server;
|
||||
socket_fd = work->socket_fd;
|
||||
generation = work->generation;
|
||||
type = slot->tx_type;
|
||||
length = slot->tx_length;
|
||||
if (owned_work && !valid) {
|
||||
slot->work_pending = false;
|
||||
slot->tx_length = 0U;
|
||||
finish_closing_slot_locked(slot);
|
||||
retired = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!valid) {
|
||||
if (retired) {
|
||||
notify_transport_task();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1113,6 +1197,8 @@ static void process_close_request(web_serial_slot_t *slot)
|
||||
++s_counters.queue_failures;
|
||||
if (session_gone) {
|
||||
slot->state = WEB_SERIAL_SLOT_CLOSING;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
slot->work_pending = false;
|
||||
@@ -1164,6 +1250,42 @@ static void process_broker_disconnect(web_serial_slot_t *slot)
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
static void process_principal_currentness(web_serial_slot_t *slot)
|
||||
{
|
||||
user_principal_t principal = {0};
|
||||
uint32_t generation = 0U;
|
||||
bool check = false;
|
||||
int64_t now_us = monotonic_time_us();
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE && !slot->close_requested &&
|
||||
slot->next_currentness_check_us <= now_us) {
|
||||
generation = slot->generation;
|
||||
principal = slot->principal;
|
||||
slot->next_currentness_check_us =
|
||||
now_us + WEB_SERIAL_CURRENTNESS_INTERVAL_US;
|
||||
check = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!check) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool current = false;
|
||||
esp_err_t result = user_database_principal_is_current(&principal, ¤t);
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
if (result == ESP_OK && current) {
|
||||
return;
|
||||
}
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE &&
|
||||
slot->generation == generation) {
|
||||
slot->close_requested = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
static void process_active_output(web_serial_slot_t *slot)
|
||||
{
|
||||
uint32_t generation;
|
||||
@@ -1221,6 +1343,7 @@ static void transport_task(void *context)
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_SESSIONS;
|
||||
++index) {
|
||||
web_serial_slot_t *slot = &s_slots[index];
|
||||
process_principal_currentness(slot);
|
||||
process_close_request(slot);
|
||||
process_broker_disconnect(slot);
|
||||
process_active_output(slot);
|
||||
@@ -1332,6 +1455,8 @@ esp_err_t web_serial_transport_detach_server(httpd_handle_t server)
|
||||
slot->tx_length = 0U;
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE) {
|
||||
slot->state = WEB_SERIAL_SLOT_CLOSING;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
slot->close_requested = false;
|
||||
@@ -1398,20 +1523,27 @@ esp_err_t web_serial_transport_detach_server(httpd_handle_t server)
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_mint_ticket(char *ticket, size_t capacity)
|
||||
esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
char *ticket, size_t capacity)
|
||||
{
|
||||
if (ticket == NULL || capacity < WEB_SERIAL_TRANSPORT_TICKET_CAPACITY) {
|
||||
if (principal == NULL || ticket == NULL ||
|
||||
capacity < WEB_SERIAL_TRANSPORT_TICKET_CAPACITY) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ticket[0] = '\0';
|
||||
|
||||
bool current = false;
|
||||
esp_err_t result = user_database_principal_is_current(principal, ¤t);
|
||||
if (result != ESP_OK) {
|
||||
return result;
|
||||
}
|
||||
if (!current) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t random_bytes[WEB_SERIAL_RANDOM_BYTES] = {0};
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES] = {0};
|
||||
uint32_t generation = 0U;
|
||||
esp_err_t result = current_material_generation(&generation);
|
||||
if (result == ESP_OK) {
|
||||
result = secure_random_fill(random_bytes, sizeof(random_bytes));
|
||||
}
|
||||
result = secure_random_fill(random_bytes, sizeof(random_bytes));
|
||||
if (result == ESP_OK) {
|
||||
encode_base64url_24(random_bytes, ticket);
|
||||
result = sha256_ticket(ticket, digest);
|
||||
@@ -1425,7 +1557,7 @@ esp_err_t web_serial_transport_mint_ticket(char *ticket, size_t capacity)
|
||||
bool stored = false;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_initialized && s_server != NULL) {
|
||||
purge_tickets_locked(now_us, generation, true);
|
||||
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;
|
||||
@@ -1447,7 +1579,7 @@ esp_err_t web_serial_transport_mint_ticket(char *ticket, size_t capacity)
|
||||
entry->expires_at_us =
|
||||
now_us + (int64_t)WEB_SERIAL_TRANSPORT_TICKET_LIFETIME_SECONDS *
|
||||
1000000LL;
|
||||
entry->material_generation = generation;
|
||||
entry->principal = *principal;
|
||||
entry->active = true;
|
||||
++s_counters.tickets_issued;
|
||||
stored = true;
|
||||
@@ -1467,9 +1599,9 @@ cleanup:
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
httpd_req_t *request)
|
||||
httpd_req_t *request, const user_principal_t *principal)
|
||||
{
|
||||
if (request == NULL) {
|
||||
if (request == NULL || principal == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (request->method != HTTP_POST || request->content_len != 0U ||
|
||||
@@ -1487,7 +1619,8 @@ esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
|
||||
char ticket[WEB_SERIAL_TRANSPORT_TICKET_CAPACITY] = {0};
|
||||
char response[WEB_SERIAL_TICKET_RESPONSE_CAPACITY];
|
||||
esp_err_t result = web_serial_transport_mint_ticket(ticket, sizeof(ticket));
|
||||
esp_err_t result = web_serial_transport_mint_ticket(
|
||||
principal, ticket, sizeof(ticket));
|
||||
if (result != ESP_OK) {
|
||||
secure_wipe(ticket, sizeof(ticket));
|
||||
return result;
|
||||
@@ -1573,8 +1706,6 @@ esp_err_t web_serial_transport_get_snapshot(
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint32_t generation = 0U;
|
||||
bool have_generation = current_material_generation(&generation) == ESP_OK;
|
||||
int64_t now_us = monotonic_time_us();
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
@@ -1582,7 +1713,7 @@ esp_err_t web_serial_transport_get_snapshot(
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
purge_tickets_locked(now_us, generation, have_generation);
|
||||
purge_tickets_locked(now_us);
|
||||
memset(snapshot, 0, sizeof(*snapshot));
|
||||
snapshot->initialized = true;
|
||||
snapshot->server_attached = s_server != NULL;
|
||||
@@ -1602,12 +1733,17 @@ esp_err_t web_serial_transport_get_snapshot(
|
||||
continue;
|
||||
}
|
||||
session->active = true;
|
||||
session->principal_valid = true;
|
||||
session->writer = slot->writer;
|
||||
session->tx_pending = slot->work_pending;
|
||||
session->close_requested = slot->close_requested;
|
||||
session->socket_fd = slot->socket_fd;
|
||||
session->generation = slot->generation;
|
||||
session->broker_client_id = slot->broker_client_id;
|
||||
session->user_role = slot->principal.role;
|
||||
session->auth_method = slot->principal.method;
|
||||
memcpy(session->username, slot->principal.username,
|
||||
slot->principal.username_length);
|
||||
++snapshot->active_sessions;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
@@ -1626,6 +1762,43 @@ esp_err_t web_serial_transport_clear_counters(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_serial_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 notify = false;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (!s_initialized) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++index) {
|
||||
web_serial_ticket_t *ticket = &s_tickets[index];
|
||||
if (ticket->active && ticket->principal.username_length == username_length &&
|
||||
memcmp(ticket->principal.username, username, username_length) == 0) {
|
||||
clear_ticket_locked(ticket);
|
||||
}
|
||||
}
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_SESSIONS; ++index) {
|
||||
web_serial_slot_t *slot = &s_slots[index];
|
||||
if ((slot->state == WEB_SERIAL_SLOT_RESERVED ||
|
||||
slot->state == WEB_SERIAL_SLOT_ACTIVE) &&
|
||||
slot->principal.username_length == username_length &&
|
||||
memcmp(slot->principal.username, username, username_length) == 0) {
|
||||
slot->close_requested = true;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (notify) {
|
||||
notify_transport_task();
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_revoke_sessions(void)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
|
||||
Reference in New Issue
Block a user