Add Phase 9C security hardening

Generate exact-hash SDK source overrides without modifying dependencies.
Harden
SSH allocation and algorithm policy, tighten web authentication cleanup,
and add
focused host contract tests and documentation.
This commit is contained in:
2026-09-15 22:12:57 +02:00
parent 751dfb9ddb
commit cdc9c7335a
41 changed files with 3597 additions and 89 deletions
+67 -4
View File
@@ -9,6 +9,7 @@
#undef user_database_username_valid
#endif
#include "web_cookie_auth.h"
#include "web_auth_parse.h"
#include "web_httpd_adapter.h"
#include "esp_httpd_priv.h"
#ifdef HOST_ADMIN
@@ -32,11 +33,48 @@ static bool send_fail, recv_fail;
static size_t receive_fragment = 7;
static void (*password_hook)(void);
static void (*send_hook)(void);
static void (*header_hook)(void), (*parse_hook)(void), (*recv_hook)(void);
static unsigned receive_calls, parser_calls, body_wipes, credential_wipes;
static size_t recv_fail_after;
static bool watch_login;
static const char *watched_body;
static web_auth_credentials_t *watched_credentials;
static char retry_value[16];
static char response_status[48];
static struct httpd_req_aux aux;
static httpd_req_t req;
size_t host_read_pending(httpd_req_t *r, char *out, size_t n);
/* Link-time wrappers retain the production parser, wipe, and IDF header setter. */
bool __real_web_auth_parse_login(const char *, size_t, web_auth_credentials_t *);
void __real_secure_wipe(void *, size_t);
esp_err_t __real_httpd_resp_set_hdr(httpd_req_t *, const char *, const char *);
bool __wrap_web_auth_parse_login(const char *body, size_t length, web_auth_credentials_t *credentials) {
assert(!host_lock_depth); ++parser_calls;
if (watch_login) { watched_body = body; watched_credentials = credentials; }
bool valid = __real_web_auth_parse_login(body, length, credentials);
if (parse_hook) { void (*hook)(void) = parse_hook; parse_hook = NULL; hook(); }
return valid;
}
void __wrap_secure_wipe(void *data, size_t length) {
__real_secure_wipe(data, length);
if (watch_login && data == watched_body && length == WEB_AUTH_LOGIN_BODY_MAX + 1) {
zero(data, length); ++body_wipes;
}
if (watch_login && data == watched_credentials && length == sizeof(*watched_credentials)) {
zero(data, length); ++credential_wipes;
}
}
esp_err_t __wrap_httpd_resp_set_hdr(httpd_req_t *r, const char *name, const char *value) {
assert(!host_lock_depth);
esp_err_t error = __real_httpd_resp_set_hdr(r, name, value);
if (header_hook && !strcmp(name, "Set-Cookie") &&
strstr(value, "__Host-sak-prelogin=") && strstr(value, "Max-Age=0")) {
void (*hook)(void) = header_hook; header_hook = NULL; hook();
}
return error;
}
esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status) {
(void)r; if (fail_header && ++setter_calls == fail_header) return ESP_FAIL;
snprintf(response_status, sizeof(response_status), "%s", status); return ESP_OK;
@@ -45,11 +83,18 @@ esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type) {
(void)r; (void)type; return ESP_OK;
}
esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *body) {
(void)r; ++sends; assert(strlen(body) < sizeof(output)); strcpy(output, body);
(void)r; assert(!host_lock_depth);
if (watch_login) {
if (watched_body) { assert(body_wipes); zero(watched_body, WEB_AUTH_LOGIN_BODY_MAX + 1); }
if (watched_credentials) { assert(credential_wipes); zero(watched_credentials, sizeof(*watched_credentials)); }
}
++sends; assert(strlen(body) < sizeof(output)); strcpy(output, body);
cookie_count = 0;
for (unsigned i = 0; i < aux.resp_hdrs_count; ++i) {
assert(response_headers[i].value);
assert(strcmp(response_headers[i].field, "WWW-Authenticate"));
if (!strcmp(response_headers[i].field, "Retry-After"))
snprintf(retry_value, sizeof(retry_value), "%s", response_headers[i].value);
if (!strcmp(response_headers[i].field, "Set-Cookie")) {
assert(cookie_count < 2);
snprintf(cookie_values[cookie_count++], 200, "%s", response_headers[i].value);
@@ -59,10 +104,14 @@ esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *body) {
return send_fail ? ESP_FAIL : ESP_OK;
}
int httpd_req_recv(httpd_req_t *r, char *out, size_t size) {
(void)r; if (recv_fail) return -1;
(void)r; assert(!host_lock_depth); ++receive_calls;
if (watch_login && !body_offset) watched_body = out;
if (recv_fail || (recv_fail_after && body_offset >= recv_fail_after)) return -1;
if (size > receive_fragment) size = receive_fragment;
memcpy(out, request_body + body_offset, size); body_offset += size;
aux.remaining_len -= size; return (int)size;
aux.remaining_len -= size;
if (recv_hook) { void (*hook)(void) = recv_hook; recv_hook = NULL; hook(); }
return (int)size;
}
esp_err_t web_login_ui_send_response(httpd_req_t *r) { return httpd_resp_sendstr(r, "login document"); }
esp_err_t web_serial_transport_revoke_web_session(web_session_id_t id) {
@@ -78,6 +127,12 @@ esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *r, const char *protocol
esp_err_t user_database_authenticate_password(const uint8_t *u, size_t un,
const uint8_t *p, size_t pn, user_principal_t *principal, bool *authenticated) {
assert(!host_lock_depth); ++password_calls;
if (watch_login) {
assert(watched_body && watched_credentials && body_wipes && !credential_wipes);
zero(watched_body, WEB_AUTH_LOGIN_BODY_MAX + 1);
assert(u == watched_credentials->username && p == watched_credentials->password);
assert(un == watched_credentials->username_length && pn == watched_credentials->password_length);
}
if (password_hook) { void (*hook)(void) = password_hook; password_hook = NULL; hook(); }
*authenticated = un == 5 && !memcmp(u, "alice", 5) && pn == 12 && !memcmp(p, "password1234", 12);
if (*authenticated) *principal = alice;
@@ -93,7 +148,9 @@ static void begin(const char *uri, int method, const char *body) {
.content_len = body ? strlen(body) : 0};
aux.remaining_len = req.content_len;
request_body = body; body_offset = 0;
response_status[0] = output[0] = 0; cookie_count = 0;
response_status[0] = output[0] = retry_value[0] = 0; cookie_count = 0;
watch_login = false; watched_body = NULL; watched_credentials = NULL;
body_wipes = credential_wipes = 0;
}
static void add(const char *key, const char *value) {
char *at = scratch;
@@ -126,10 +183,15 @@ static void login_request(const char *token, const char *csrf, const char *body)
char cookies[100]; snprintf(cookies, sizeof(cookies), "__Host-sak-prelogin=%s", token); add("Cookie", cookies);
}
static void auth_reset(void) {
watch_login = false; watched_body = NULL; watched_credentials = NULL;
header_hook = parse_hook = recv_hook = NULL; recv_fail_after = 0;
receive_calls = parser_calls = 0;
web_cookie_auth_stop(); reset(); assert(web_cookie_auth_start() == ESP_OK);
password_calls = 0; password_hook = NULL;
}
#include "admission_test.c"
#ifdef HOST_ADMIN
#include "admin_test.c"
#endif
@@ -302,6 +364,7 @@ int main(void) {
server.config.max_resp_headers = 6; expect("200 OK"); assert(cookie_count == 2);
server.config.max_resp_headers = 8;
puts("PASS: exact six-header successful login budget; all smaller header capacities invalidate unpublished login");
auth_admission_tests();
#ifdef HOST_ADMIN
admin_tests();
#endif