26#include <esp_system.h>
35static void sha256_hex(
const uint8_t *data,
size_t len,
char out[65])
46static bool digest_field(
const char *hdr,
const char *key,
char *out,
size_t out_size)
48 size_t klen = strnlen(key, 32);
50 while ((p = strstr(p, key)) !=
nullptr)
52 bool left_ok = (p == hdr) || p[-1] ==
' ' || p[-1] ==
',';
53 const char *after = p + klen;
54 if (!left_ok || *after !=
'=')
73 while (*ve && *ve !=
',' && *ve !=
' ')
76 size_t vlen = (size_t)(ve - vs);
77 if (vlen > out_size - 1)
79 memcpy(out, vs, vlen);
86void DetWebServer::regen_digest_secret()
93 static uint32_t counter = 0;
96 for (
int i = 0; i < 4; i++)
98 uint32_t r = esp_random();
99 memcpy(seed + i * 4, &r, 4);
101 uint32_t c = counter;
102 uint32_t t = (uint32_t)millis();
103 memcpy(seed + 16, &c, 4);
104 memcpy(seed + 20, &t, 4);
107 memcpy(_digest_secret, d,
sizeof(_digest_secret));
115static uint32_t digest_nonce_mac(
const uint8_t *secret, uint32_t issue,
char *mac_hex)
117 uint8_t material[20];
118 memcpy(material, secret, 16);
119 memcpy(material + 16, &issue, 4);
126void DetWebServer::make_digest_nonce(
char *out,
size_t cap)
132 digest_nonce_mac(_digest_secret, issue, mac_hex);
133 snprintf(out, cap,
"%s.%s", issue_hex, mac_hex);
136bool DetWebServer::verify_digest_nonce(
const char *nonce,
bool *expired)
140 if (strnlen(nonce, 42) != 8 + 1 + 32 || nonce[8] !=
'.')
146 digest_nonce_mac(_digest_secret, issue, mac_hex);
149 const char *got = nonce + 9;
151 for (
int i = 0; i < 32; i++)
152 diff |= (uint8_t)(mac_hex[i] ^ got[i]);
160void DetWebServer::send_unauth(uint8_t slot_id,
const Route *r,
bool stale)
162 if (!det_conn_active(slot_id))
176 make_digest_nonce(nonce,
sizeof(nonce));
177 snprintf(challenge,
sizeof(challenge),
178 "WWW-Authenticate: Digest realm=\"%s\", qop=\"auth\", algorithm=SHA-256, nonce=\"%s\"%s\r\n",
179 r->auth_realm, nonce, stale ?
", stale=true" :
"");
182 snprintf(challenge,
sizeof(challenge),
"WWW-Authenticate: Basic realm=\"%s\"\r\n", r->auth_realm);
185 const char *cl = resp_conn_hdr(slot_id, &keep);
187 static const char body[] =
"Unauthorized";
189 int hlen = snprintf(header,
sizeof(header),
190 "HTTP/1.1 401 Unauthorized\r\n"
192 "Content-Type: text/plain\r\n"
193 "Content-Length: %d\r\n"
196 challenge, (
int)(
sizeof(body) - 1), _cors_enabled ? _cors_header_buf :
"", cl);
210 resp_end(slot_id, 401, (
int)(
sizeof(body) - 1), keep,
true);
216static bool ct_equal(
const void *a,
const void *b,
size_t len)
218 const uint8_t *pa = (
const uint8_t *)a;
219 const uint8_t *pb = (
const uint8_t *)b;
221 for (
size_t i = 0; i < len; i++)
222 diff |= (uint8_t)(pa[i] ^ pb[i]);
226bool DetWebServer::check_basic_auth(uint8_t ,
HttpReq *req,
const Route *r)
229 if (!auth_hdr || strncmp(auth_hdr,
"Basic ", 6) != 0)
235 size_t n =
base64_decode(auth_hdr + 6, decoded,
sizeof(decoded) - 1);
240 const char *colon = (
const char *)memchr(decoded,
':', n);
244 size_t ulen = (size_t)(colon - (
const char *)decoded);
245 const char *pass = colon + 1;
246 size_t plen = n - (size_t)(pass - (
const char *)decoded);
251 bool user_ok = (ulen == strnlen(r->auth_user,
MAX_AUTH_LEN)) && ct_equal(decoded, r->auth_user, ulen);
252 bool pass_ok = (plen == strnlen(r->auth_pass,
MAX_AUTH_LEN)) && ct_equal(pass, r->auth_pass, plen);
253 return user_ok && pass_ok;
259bool DetWebServer::check_digest_auth(uint8_t ,
HttpReq *req,
const Route *r,
bool *stale)
263 const char *hdr = req->authorization;
264 if (strncmp(hdr,
"Digest ", 7) != 0)
266 const char *d = hdr + 7;
276 if (!digest_field(d,
"username", username,
sizeof(username)) || !digest_field(d,
"nonce", nonce,
sizeof(nonce)) ||
277 !digest_field(d,
"uri", uri,
sizeof(uri)) || !digest_field(d,
"qop", qop,
sizeof(qop)) ||
278 !digest_field(d,
"nc", nc,
sizeof(nc)) || !digest_field(d,
"cnonce", cnonce,
sizeof(cnonce)) ||
279 !digest_field(d,
"response", response,
sizeof(response)))
283 if (strcmp(username, r->auth_user) != 0)
288 bool nonce_expired =
false;
289 if (!verify_digest_nonce(nonce, &nonce_expired))
291 if (strcmp(qop,
"auth") != 0)
299 snprintf(target,
sizeof(target),
"%s?%s", req->
path, req->
query);
301 snprintf(target,
sizeof(target),
"%s", req->
path);
302 if (strcmp(uri, target) != 0)
310 int n = snprintf(tmp,
sizeof(tmp),
"%s:%s:%s", r->auth_user, r->auth_realm, r->auth_pass);
311 sha256_hex((
const uint8_t *)tmp, (
size_t)n, ha1);
313 char tmp2[
sizeof(uri) + 16];
314 n = snprintf(tmp2,
sizeof(tmp2),
"%s:%s", req->
method, uri);
315 sha256_hex((
const uint8_t *)tmp2, (
size_t)n, ha2);
317 char tmp3[65 + 48 + 16 + 64 + 8 + 65 + 8];
318 n = snprintf(tmp3,
sizeof(tmp3),
"%s:%s:%s:%s:%s:%s", ha1, nonce, nc, cnonce, qop, ha2);
319 sha256_hex((
const uint8_t *)tmp3, (
size_t)n, expected);
321 if (strcasecmp(expected, response) != 0)
#define MAX_QUERY_LEN
Maximum raw query-string length (everything after ?).
#define RESP_HDR_BUF_SIZE
Stack buffer for HTTP response header lines in send() / send_empty() / send_unauth() / serve_file().
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define MAX_AUTH_LEN
Maximum username or password length for HTTP Basic Authentication.
#define DETWS_DIGEST_NONCE_LIFETIME_MS
Lifetime of a Digest nonce, in milliseconds (default 5 minutes).
size_t base64_decode(const char *src, uint8_t *dst, size_t dst_cap)
Decode a null-terminated Base64 string.
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
bool req_is_head(uint8_t slot_id)
True if the request in slot slot_id used the HEAD method (send headers, no body).
Layer 7 (Application) - public HTTP routing API.
Library-private declarations shared between dwserver.cpp and the src/server/*.cpp request-handler tra...
Tiny no-stdlib hex encode / decode / nibble helpers (one shared copy).
int det_hex_decode(const char *in, size_t hexlen, uint8_t *out, size_t out_cap)
Decode exactly hexlen hex chars into out (hexlen/2 bytes).
void det_hex_encode(const uint8_t *in, size_t n, char *out, bool upper=false)
Encode n bytes as 2*n hex chars + NUL into out (needs cap >= 2*n+1).
const char * http_get_header(const HttpReq *req, const char *key)
Look up a header value by name (case-insensitive).
void http_reset(uint8_t slot_id)
Reset the HTTP parser for a connection slot.
void ssh_sha256(const uint8_t *data, size_t len, uint8_t digest[SSH_SHA256_DIGEST_LEN])
One-shot SHA-256: hash len bytes and write the digest.
SHA-256 (FIPS 180-4) - streaming context and one-shot API.
#define SSH_SHA256_DIGEST_LEN
SHA-256 digest length in bytes.
Fully-parsed HTTP/1.1 request.
char query[MAX_QUERY_LEN]
Raw query string (after ?).
char method[DETWS_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
Internal route entry stored in the routing table.
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
bool det_conn_send_flush(uint8_t slot, const void *data, u16_t len)
Send len bytes on slot and flush in a single tcpip_thread round-trip.
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.