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:
@@ -62,7 +62,8 @@ def run():
|
||||
*sanitizer,
|
||||
"-I" + str(tmp), "-I" + str(ROOT / "src"),
|
||||
"-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections",
|
||||
str(HERE / ("serial_test.c" if serial else "test.c")), str(source), *crypto,
|
||||
str(HERE / ("serial_test.c" if serial else "test.c")), str(source),
|
||||
*([str(ROOT / "src/web_auth_parse.c")] if serial else []), *crypto,
|
||||
"-o", str(tmp / "test")], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / "test")], check=True, timeout=10)
|
||||
|
||||
|
||||
@@ -24,12 +24,13 @@ TaskHandle_t xTaskCreateStatic(void (*)(void *), const char *, uint32_t, void *,
|
||||
#include "esp_err.h"
|
||||
typedef void *httpd_handle_t;
|
||||
typedef struct { httpd_handle_t handle; void *sess_ctx; void (*free_ctx)(void *);
|
||||
int method; size_t content_len; } httpd_req_t;
|
||||
int method; size_t content_len; const char *uri; void *aux; } httpd_req_t;
|
||||
typedef enum { HTTPD_WS_TYPE_CONTINUE, HTTPD_WS_TYPE_TEXT, HTTPD_WS_TYPE_BINARY } httpd_ws_type_t;
|
||||
typedef enum { HTTPD_WS_CLIENT_HTTP, HTTPD_WS_CLIENT_WEBSOCKET } httpd_ws_client_info_t;
|
||||
typedef struct { bool final, fragmented; httpd_ws_type_t type; unsigned char *payload;
|
||||
size_t len; } httpd_ws_frame_t;
|
||||
#define HTTP_POST 1
|
||||
#define HTTP_GET 0
|
||||
size_t httpd_req_get_url_query_len(httpd_req_t *);
|
||||
esp_err_t httpd_req_get_url_query_str(httpd_req_t *, char *, size_t);
|
||||
esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *, const char *, char *, size_t);
|
||||
|
||||
@@ -10,15 +10,29 @@ static unsigned broker_connections, broker_disconnects, writes, closes;
|
||||
static esp_err_t close_result = ESP_OK;
|
||||
static httpd_req_t request = { .handle = (void *)1 };
|
||||
static void (*connect_hook)(void);
|
||||
esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *s) { (void)r; (void)s; return ESP_OK; }
|
||||
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *s) { (void)r; (void)s; return ESP_OK; }
|
||||
esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *k, const char *v) { (void)r; (void)k; (void)v; return ESP_OK; }
|
||||
esp_err_t httpd_resp_send(httpd_req_t *r, const char *s, int n) { (void)r; (void)s; (void)n; return ESP_OK; }
|
||||
void xTaskNotifyGive(TaskHandle_t task) { (void)task; assert(!host_lock_depth); }
|
||||
size_t httpd_req_get_url_query_len(httpd_req_t *r) { (void)r; return strlen(query); }
|
||||
esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *out, size_t n) {
|
||||
(void)r; assert(strlen(query) < n); strcpy(out, query); return ESP_OK;
|
||||
}
|
||||
esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *key, char *out, size_t n) {
|
||||
(void)r; (void)key; (void)out; (void)n; return ESP_ERR_NOT_FOUND;
|
||||
(void)r;
|
||||
const char *value = !strcmp(key, "Host") ? "device.example" :
|
||||
!strcmp(key, "Origin") ? "https://device.example" : NULL;
|
||||
if (!value) return ESP_ERR_NOT_FOUND;
|
||||
assert(strlen(value) < n); strcpy(out, value); return ESP_OK;
|
||||
}
|
||||
size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *key) {
|
||||
char value[140]; return httpd_req_get_hdr_value_str(r, key, value, sizeof(value)) == ESP_OK ? strlen(value) : 0;
|
||||
}
|
||||
bool web_httpd_upgrade_requested(httpd_req_t *r) { (void)r; return true; }
|
||||
esp_err_t web_httpd_upgrade(httpd_req_t *r, esp_err_t (*handler)(httpd_req_t *)) {
|
||||
(void)r; (void)handler; return ESP_OK;
|
||||
}
|
||||
size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *key) { (void)r; (void)key; return 0; }
|
||||
int httpd_req_to_sockfd(httpd_req_t *r) { (void)r; return 10; }
|
||||
httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t h, int fd) {
|
||||
(void)h; (void)fd; return HTTPD_WS_CLIENT_WEBSOCKET;
|
||||
@@ -126,12 +140,9 @@ int main(void) {
|
||||
now += WEB_SERIAL_CURRENTNESS_INTERVAL_US; stale_user = alice.user_id; db_hook = reuse_slot_hook;
|
||||
process_principal_currentness(sa); assert(!sa->close_requested);
|
||||
|
||||
/* Basic tickets still work with a disabled store, but never accept bound tickets. */
|
||||
/* Zero identity is no longer a Basic compatibility route. */
|
||||
serial_reset(); web_session_store_stop();
|
||||
assert(web_serial_transport_mint_ticket(&alice, 0, ta, sizeof(ta)) == ESP_OK);
|
||||
snprintf(query, sizeof(query), "ticket=%s", ta);
|
||||
assert(connect_websocket(&request, 10, 0) == ESP_OK);
|
||||
before = writes; assert(process_websocket_frame(&request) == ESP_OK && writes == before + 1);
|
||||
assert(web_serial_transport_mint_ticket(&alice, 0, ta, sizeof(ta)) != ESP_OK);
|
||||
serial_reset(); a = mint(&alice); b = mint(&bob);
|
||||
assert(web_serial_transport_revoke_sessions() == ESP_OK); absent(&a); absent(&b);
|
||||
assert(snapshot().initialized); assert(!host_lock_depth && closes > 0);
|
||||
@@ -153,6 +164,12 @@ int main(void) {
|
||||
assert(web_session_store_check_principal(a.view.id, &p, ¤t) != ESP_OK && !current);
|
||||
present(&a);
|
||||
}
|
||||
puts("PASS: serial/session binding, isolation, cleanup, failure fallback, races, Basic regression");
|
||||
serial_reset(); a = mint(&alice);
|
||||
char tickets[4][33];
|
||||
for (unsigned i = 0; i < 4; ++i) ticket_for(&a, tickets[i]);
|
||||
assert(web_serial_transport_mint_ticket(&alice, a.view.id, ta, sizeof(ta)) == ESP_ERR_NO_MEM);
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
assert(consume_ticket(tickets[i], a.view.id, &p, &consumed) == ESP_OK && consumed);
|
||||
puts("PASS: serial/session binding, isolation, cleanup, races, no Basic fallback or live ticket eviction");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user