Cache web authentication results
Add a short-lived, HMAC-keyed cache for validated principals and invalidate entries when principals become stale. Improve duplicate SSH key errors and enable Ed25519 streaming verification.
This commit is contained in:
@@ -7,6 +7,7 @@ add_compile_definitions(
|
|||||||
WC_RNG_SEED_CB
|
WC_RNG_SEED_CB
|
||||||
NO_WOLFSSL_ESP32_CRYPT_AES
|
NO_WOLFSSL_ESP32_CRYPT_AES
|
||||||
NO_WOLFSSL_ESP32_CRYPT_HASH
|
NO_WOLFSSL_ESP32_CRYPT_HASH
|
||||||
|
WOLFSSL_ED25519_STREAMING_VERIFY
|
||||||
)
|
)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|||||||
+8
-1
@@ -356,7 +356,14 @@ static int add_key(const char *username)
|
|||||||
secure_wipe(blob, sizeof(blob));
|
secure_wipe(blob, sizeof(blob));
|
||||||
secure_wipe(line, sizeof(line));
|
secure_wipe(line, sizeof(line));
|
||||||
if (error != ESP_OK) {
|
if (error != ESP_OK) {
|
||||||
printf("Could not add SSH key: %s\n", esp_err_to_name(error));
|
if (error == USER_DATABASE_ERR_DUPLICATE_SSH_KEY) {
|
||||||
|
printf("Could not add SSH key: that public key is already assigned to an account.\n");
|
||||||
|
} else if (error == ESP_ERR_NO_MEM) {
|
||||||
|
printf("Could not add SSH key: the account already has %u keys.\n",
|
||||||
|
USER_DATABASE_MAX_SSH_KEYS_PER_USER);
|
||||||
|
} else {
|
||||||
|
printf("Could not add SSH key: %s\n", esp_err_to_name(error));
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
revoke_user_network_sessions(username);
|
revoke_user_network_sessions(username);
|
||||||
|
|||||||
+1
-1
@@ -1175,7 +1175,7 @@ esp_err_t user_database_add_ssh_key(
|
|||||||
key->blob_length == key_blob_length &&
|
key->blob_length == key_blob_length &&
|
||||||
memcmp(key->type, key_type, key_type_length) == 0 &&
|
memcmp(key->type, key_type, key_type_length) == 0 &&
|
||||||
constant_time_equal(key->blob, key_blob, key_blob_length)) {
|
constant_time_equal(key->blob, key_blob, key_blob_length)) {
|
||||||
error = ESP_ERR_INVALID_STATE;
|
error = USER_DATABASE_ERR_DUPLICATE_SSH_KEY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ extern "C" {
|
|||||||
#define USER_DATABASE_SSH_KEY_TYPE_CAPACITY 32U
|
#define USER_DATABASE_SSH_KEY_TYPE_CAPACITY 32U
|
||||||
#define USER_DATABASE_SSH_KEY_BLOB_CAPACITY 128U
|
#define USER_DATABASE_SSH_KEY_BLOB_CAPACITY 128U
|
||||||
#define USER_DATABASE_SHA256_LENGTH 32U
|
#define USER_DATABASE_SHA256_LENGTH 32U
|
||||||
|
#define USER_DATABASE_ERR_DUPLICATE_SSH_KEY ESP_ERR_NOT_ALLOWED
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
USER_ROLE_USER = 1,
|
USER_ROLE_USER = 1,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
#include "mbedtls/base64.h"
|
#include "mbedtls/base64.h"
|
||||||
|
#include "mbedtls/md.h"
|
||||||
#include "secure_random.h"
|
#include "secure_random.h"
|
||||||
#include "serial_config.h"
|
#include "serial_config.h"
|
||||||
#include "serial_service.h"
|
#include "serial_service.h"
|
||||||
@@ -34,6 +35,17 @@
|
|||||||
#define WEB_SERVER_MAX_AUTHORIZATION \
|
#define WEB_SERVER_MAX_AUTHORIZATION \
|
||||||
((sizeof("Basic ") - 1U) + WEB_SERVER_MAX_BASIC_ENCODED + 1U)
|
((sizeof("Basic ") - 1U) + WEB_SERVER_MAX_BASIC_ENCODED + 1U)
|
||||||
#define WEB_SERVER_STATUS_JSON_CAPACITY 3072U
|
#define WEB_SERVER_STATUS_JSON_CAPACITY 3072U
|
||||||
|
#define WEB_SERVER_AUTH_CACHE_ENTRIES 4U
|
||||||
|
#define WEB_SERVER_AUTH_CACHE_KEY_LENGTH 32U
|
||||||
|
#define WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH 32U
|
||||||
|
#define WEB_SERVER_AUTH_CACHE_TTL_US 300000000LL
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool active;
|
||||||
|
int64_t expires_at_us;
|
||||||
|
uint8_t digest[WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH];
|
||||||
|
user_principal_t principal;
|
||||||
|
} web_server_auth_cache_entry_t;
|
||||||
|
|
||||||
static SemaphoreHandle_t s_server_mutex;
|
static SemaphoreHandle_t s_server_mutex;
|
||||||
static httpd_handle_t s_server;
|
static httpd_handle_t s_server;
|
||||||
@@ -45,6 +57,10 @@ static bool s_serial_transport_attached;
|
|||||||
static esp_err_t s_last_error = ESP_ERR_INVALID_STATE;
|
static esp_err_t s_last_error = ESP_ERR_INVALID_STATE;
|
||||||
static esp_err_t s_serial_transport_error = ESP_ERR_INVALID_STATE;
|
static esp_err_t s_serial_transport_error = ESP_ERR_INVALID_STATE;
|
||||||
static web_server_counters_t s_counters;
|
static web_server_counters_t s_counters;
|
||||||
|
static bool s_auth_cache_ready;
|
||||||
|
static uint8_t s_auth_cache_key[WEB_SERVER_AUTH_CACHE_KEY_LENGTH];
|
||||||
|
static web_server_auth_cache_entry_t
|
||||||
|
s_auth_cache[WEB_SERVER_AUTH_CACHE_ENTRIES];
|
||||||
|
|
||||||
static esp_err_t ensure_mutex(void)
|
static esp_err_t ensure_mutex(void)
|
||||||
{
|
{
|
||||||
@@ -106,6 +122,93 @@ static esp_err_t send_authentication_required(httpd_req_t *request)
|
|||||||
return send_plain_error(request, "401 Unauthorized", "Authentication required.\n");
|
return send_plain_error(request, "401 Unauthorized", "Authentication required.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool constant_time_equal(const uint8_t *left, const uint8_t *right,
|
||||||
|
size_t length)
|
||||||
|
{
|
||||||
|
uint8_t difference = 0U;
|
||||||
|
for (size_t index = 0U; index < length; ++index) {
|
||||||
|
difference |= left[index] ^ right[index];
|
||||||
|
}
|
||||||
|
return difference == 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t calculate_auth_cache_digest(
|
||||||
|
const char *authorization, size_t authorization_length,
|
||||||
|
uint8_t digest[WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH])
|
||||||
|
{
|
||||||
|
if (!s_auth_cache_ready) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||||
|
if (info == NULL ||
|
||||||
|
mbedtls_md_hmac(info, s_auth_cache_key, sizeof(s_auth_cache_key),
|
||||||
|
(const uint8_t *)authorization, authorization_length,
|
||||||
|
digest) != 0) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool authenticate_from_cache(
|
||||||
|
const uint8_t digest[WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH],
|
||||||
|
user_principal_t *principal)
|
||||||
|
{
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
for (size_t index = 0U; index < WEB_SERVER_AUTH_CACHE_ENTRIES; ++index) {
|
||||||
|
web_server_auth_cache_entry_t *entry = &s_auth_cache[index];
|
||||||
|
if (!entry->active || entry->expires_at_us <= now ||
|
||||||
|
!constant_time_equal(entry->digest, digest, sizeof(entry->digest))) {
|
||||||
|
if (entry->active && entry->expires_at_us <= now) {
|
||||||
|
secure_wipe(entry, sizeof(*entry));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool current = false;
|
||||||
|
if (user_database_principal_is_current(&entry->principal, ¤t) == ESP_OK &&
|
||||||
|
current) {
|
||||||
|
*principal = entry->principal;
|
||||||
|
entry->expires_at_us = now + WEB_SERVER_AUTH_CACHE_TTL_US;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
secure_wipe(entry, sizeof(*entry));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void store_authenticated_request(
|
||||||
|
const uint8_t digest[WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH],
|
||||||
|
const user_principal_t *principal)
|
||||||
|
{
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
size_t selected = 0U;
|
||||||
|
int64_t earliest_expiry = INT64_MAX;
|
||||||
|
for (size_t index = 0U; index < WEB_SERVER_AUTH_CACHE_ENTRIES; ++index) {
|
||||||
|
web_server_auth_cache_entry_t *entry = &s_auth_cache[index];
|
||||||
|
if (entry->active &&
|
||||||
|
constant_time_equal(entry->digest, digest, sizeof(entry->digest))) {
|
||||||
|
selected = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry->active || entry->expires_at_us <= now) {
|
||||||
|
selected = index;
|
||||||
|
earliest_expiry = INT64_MIN;
|
||||||
|
} else if (earliest_expiry != INT64_MIN &&
|
||||||
|
entry->expires_at_us < earliest_expiry) {
|
||||||
|
selected = index;
|
||||||
|
earliest_expiry = entry->expires_at_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
web_server_auth_cache_entry_t *entry = &s_auth_cache[selected];
|
||||||
|
secure_wipe(entry, sizeof(*entry));
|
||||||
|
entry->active = true;
|
||||||
|
entry->expires_at_us = now + WEB_SERVER_AUTH_CACHE_TTL_US;
|
||||||
|
memcpy(entry->digest, digest, sizeof(entry->digest));
|
||||||
|
entry->principal = *principal;
|
||||||
|
}
|
||||||
|
|
||||||
static esp_err_t authenticate_request(httpd_req_t *request,
|
static esp_err_t authenticate_request(httpd_req_t *request,
|
||||||
user_principal_t *principal,
|
user_principal_t *principal,
|
||||||
bool *authenticated)
|
bool *authenticated)
|
||||||
@@ -113,6 +216,8 @@ static esp_err_t authenticate_request(httpd_req_t *request,
|
|||||||
char authorization[WEB_SERVER_MAX_AUTHORIZATION] = {0};
|
char authorization[WEB_SERVER_MAX_AUTHORIZATION] = {0};
|
||||||
uint8_t decoded[WEB_SERVER_MAX_BASIC_DECODED] = {0};
|
uint8_t decoded[WEB_SERVER_MAX_BASIC_DECODED] = {0};
|
||||||
size_t decoded_length = 0U;
|
size_t decoded_length = 0U;
|
||||||
|
uint8_t cache_digest[WEB_SERVER_AUTH_CACHE_DIGEST_LENGTH] = {0};
|
||||||
|
bool cache_digest_valid = false;
|
||||||
esp_err_t result = ESP_OK;
|
esp_err_t result = ESP_OK;
|
||||||
memset(principal, 0, sizeof(*principal));
|
memset(principal, 0, sizeof(*principal));
|
||||||
*authenticated = false;
|
*authenticated = false;
|
||||||
@@ -128,6 +233,16 @@ static esp_err_t authenticate_request(httpd_req_t *request,
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = calculate_auth_cache_digest(authorization, header_length, cache_digest);
|
||||||
|
if (result != ESP_OK) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
cache_digest_valid = true;
|
||||||
|
if (authenticate_from_cache(cache_digest, principal)) {
|
||||||
|
*authenticated = true;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
int decode_result = mbedtls_base64_decode(
|
int decode_result = mbedtls_base64_decode(
|
||||||
decoded, sizeof(decoded), &decoded_length,
|
decoded, sizeof(decoded), &decoded_length,
|
||||||
(const unsigned char *)authorization + 6U, header_length - 6U);
|
(const unsigned char *)authorization + 6U, header_length - 6U);
|
||||||
@@ -144,10 +259,14 @@ static esp_err_t authenticate_request(httpd_req_t *request,
|
|||||||
result = user_database_authenticate_password(
|
result = user_database_authenticate_password(
|
||||||
decoded, username_length, separator + 1U, password_length,
|
decoded, username_length, separator + 1U, password_length,
|
||||||
principal, authenticated);
|
principal, authenticated);
|
||||||
|
if (result == ESP_OK && *authenticated && cache_digest_valid) {
|
||||||
|
store_authenticated_request(cache_digest, principal);
|
||||||
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
secure_wipe(authorization, sizeof(authorization));
|
secure_wipe(authorization, sizeof(authorization));
|
||||||
secure_wipe(decoded, sizeof(decoded));
|
secure_wipe(decoded, sizeof(decoded));
|
||||||
|
secure_wipe(cache_digest, sizeof(cache_digest));
|
||||||
if (result != ESP_OK) {
|
if (result != ESP_OK) {
|
||||||
memset(principal, 0, sizeof(*principal));
|
memset(principal, 0, sizeof(*principal));
|
||||||
return result;
|
return result;
|
||||||
@@ -475,6 +594,19 @@ esp_err_t web_server_init(void)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
||||||
|
if (!s_auth_cache_ready) {
|
||||||
|
error = secure_random_fill(s_auth_cache_key, sizeof(s_auth_cache_key));
|
||||||
|
if (error == ESP_OK) {
|
||||||
|
secure_wipe(s_auth_cache, sizeof(s_auth_cache));
|
||||||
|
s_auth_cache_ready = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(s_server_mutex);
|
||||||
|
if (error != ESP_OK) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
bool initialize_serial_transport = false;
|
bool initialize_serial_transport = false;
|
||||||
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
||||||
if (!s_serial_transport_init_attempted) {
|
if (!s_serial_transport_init_attempted) {
|
||||||
|
|||||||
Reference in New Issue
Block a user