From 999774889d5ba2546223eb037912d33f64b3d11f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 07:17:24 -0600 Subject: [PATCH] Add comments explaining constant-time comparison logic --- esphome/components/web_server_idf/web_server_idf.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 2c2ceef3a6d..7cb56def573 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -354,13 +354,19 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw esp_crypto_base64_encode(reinterpret_cast(digest.get()), n, &out, reinterpret_cast(user_info), user_info_len); - // Constant-time comparison to avoid timing side channels + // Constant-time comparison to avoid timing side channels. + // No early return on length mismatch — the length difference is folded + // into the accumulator so the loop always runs over the full digest. const char *provided = auth_str + auth_prefix_len; - size_t digest_len = out; + size_t digest_len = out; // length from esp_crypto_base64_encode size_t provided_len = strlen(provided); volatile uint8_t result = 0; + // Non-zero if lengths differ; XOR of two size_t values truncated to 8 bits + // catches differences in the low byte, and any length mismatch also causes + // the byte-wise loop below to accumulate non-zero XOR values. result |= static_cast(digest_len ^ provided_len); for (size_t i = 0; i < digest_len; i++) { + // Bounds-safe: use 0 for bytes beyond provided_len char provided_ch = (i < provided_len) ? provided[i] : 0; result |= static_cast(digest.get()[i] ^ provided_ch); }