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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user