|
|
|
@@ -15,6 +15,7 @@
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
#include "freertos/semphr.h"
|
|
|
|
|
#include "mbedtls/base64.h"
|
|
|
|
|
#include "mbedtls/md.h"
|
|
|
|
|
#include "secure_random.h"
|
|
|
|
|
#include "serial_config.h"
|
|
|
|
|
#include "serial_service.h"
|
|
|
|
@@ -34,6 +35,17 @@
|
|
|
|
|
#define WEB_SERVER_MAX_AUTHORIZATION \
|
|
|
|
|
((sizeof("Basic ") - 1U) + WEB_SERVER_MAX_BASIC_ENCODED + 1U)
|
|
|
|
|
#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 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_serial_transport_error = ESP_ERR_INVALID_STATE;
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
@@ -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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
user_principal_t *principal,
|
|
|
|
|
bool *authenticated)
|
|
|
|
@@ -113,6 +216,8 @@ static esp_err_t authenticate_request(httpd_req_t *request,
|
|
|
|
|
char authorization[WEB_SERVER_MAX_AUTHORIZATION] = {0};
|
|
|
|
|
uint8_t decoded[WEB_SERVER_MAX_BASIC_DECODED] = {0};
|
|
|
|
|
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;
|
|
|
|
|
memset(principal, 0, sizeof(*principal));
|
|
|
|
|
*authenticated = false;
|
|
|
|
@@ -128,6 +233,16 @@ static esp_err_t authenticate_request(httpd_req_t *request,
|
|
|
|
|
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(
|
|
|
|
|
decoded, sizeof(decoded), &decoded_length,
|
|
|
|
|
(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(
|
|
|
|
|
decoded, username_length, separator + 1U, password_length,
|
|
|
|
|
principal, authenticated);
|
|
|
|
|
if (result == ESP_OK && *authenticated && cache_digest_valid) {
|
|
|
|
|
store_authenticated_request(cache_digest, principal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
secure_wipe(authorization, sizeof(authorization));
|
|
|
|
|
secure_wipe(decoded, sizeof(decoded));
|
|
|
|
|
secure_wipe(cache_digest, sizeof(cache_digest));
|
|
|
|
|
if (result != ESP_OK) {
|
|
|
|
|
memset(principal, 0, sizeof(*principal));
|
|
|
|
|
return result;
|
|
|
|
@@ -475,6 +594,19 @@ esp_err_t web_server_init(void)
|
|
|
|
|
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;
|
|
|
|
|
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
|
|
|
|
if (!s_serial_transport_init_attempted) {
|
|
|
|
|