From fd3b65f4bd19852a0b265f753d032e711a7eae44 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Wed, 21 Aug 2024 21:21:11 +0200 Subject: [PATCH] Feature: HttpGetter: support MD5 digest authentication the MD5 scheme should still be widely deployed, even though it is deprecated. it is also still the default if not specific algorithm is requested by the server. --- include/HttpGetter.h | 2 +- src/HttpGetter.cpp | 74 ++++++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/include/HttpGetter.h b/include/HttpGetter.h index 41b354f4..d1690a93 100644 --- a/include/HttpGetter.h +++ b/include/HttpGetter.h @@ -69,7 +69,7 @@ public: char const* getErrorText() const { return _errBuffer; } private: - String getAuthDigest(String const& authReq, unsigned int counter); + std::pair getAuthDigest(String const& authReq, unsigned int counter); HttpRequestConfig const& _config; template diff --git a/src/HttpGetter.cpp b/src/HttpGetter.cpp index 1bd1d9b3..226b8bbe 100644 --- a/src/HttpGetter.cpp +++ b/src/HttpGetter.cpp @@ -2,6 +2,7 @@ #include "HttpGetter.h" #include #include "mbedtls/sha256.h" +#include "mbedtls/md5.h" #include #include @@ -155,8 +156,12 @@ HttpRequestResult HttpGetter::performGetRequest() return { false }; } String authReq = upTmpHttpClient->header("WWW-Authenticate"); - String authorization = getAuthDigest(authReq, 1); - upTmpHttpClient->addHeader("Authorization", authorization); + auto authorization = getAuthDigest(authReq, 1); + if (!authorization.first) { + logError("Digest Error: %s", authorization.second.c_str()); + return { false }; + } + upTmpHttpClient->addHeader("Authorization", authorization.second); // use a new TCP connection if the server sent "Connection: close". bool restart = true; @@ -183,6 +188,29 @@ HttpRequestResult HttpGetter::performGetRequest() return { true, std::move(upTmpHttpClient), _spWiFiClient }; } +template +static String bin2hex(uint8_t* hash) { + size_t constexpr kOutLen = binLen * 2 + 1; + char res[kOutLen]; + for (int i = 0; i < binLen; i++) { + snprintf(res + (i*2), sizeof(res) - (i*2), "%02x", hash[i]); + } + return res; +} + +static String md5(const String& data) { + uint8_t hash[16]; + + mbedtls_md5_context ctx; + mbedtls_md5_init(&ctx); + mbedtls_md5_starts_ret(&ctx); + mbedtls_md5_update_ret(&ctx, reinterpret_cast(data.c_str()), data.length()); + mbedtls_md5_finish_ret(&ctx, hash); + mbedtls_md5_free(&ctx); + + return bin2hex(hash); +} + static String sha256(const String& data) { uint8_t hash[32]; @@ -193,12 +221,7 @@ static String sha256(const String& data) { mbedtls_sha256_finish(&ctx, hash); mbedtls_sha256_free(&ctx); - char res[sizeof(hash) * 2 + 1]; - for (int i = 0; i < sizeof(hash); i++) { - snprintf(res + (i*2), sizeof(res) - (i*2), "%02x", hash[i]); - } - - return res; + return bin2hex(hash); } static String extractParam(String const& authReq, String const& param, char delimiter) { @@ -219,7 +242,22 @@ static String getcNonce(int len) { return s; } -String HttpGetter::getAuthDigest(String const& authReq, unsigned int counter) { +static std::pair getAlgo(String const& authReq) { + // the algorithm is NOT enclosed in double quotes, so we can't use extractParam + auto paramBegin = authReq.indexOf("algorithm="); + if (paramBegin == -1) { return { true, "MD5" }; } // default as per RFC2617 + auto valueBegin = paramBegin + 10; + + String algo = authReq.substring(valueBegin, valueBegin + 3); + if (algo == "MD5") { return { true, algo }; } + + algo = authReq.substring(valueBegin, valueBegin + 7); + if (algo == "SHA-256") { return { true, algo }; } + + return { false, "unsupported digest algorithm" }; +} + +std::pair HttpGetter::getAuthDigest(String const& authReq, unsigned int counter) { // extracting required parameters for RFC 2617 Digest String realm = extractParam(authReq, "realm=\"", '"'); String nonce = extractParam(authReq, "nonce=\"", '"'); @@ -228,21 +266,19 @@ String HttpGetter::getAuthDigest(String const& authReq, unsigned int counter) { char nc[9]; snprintf(nc, sizeof(nc), "%08x", counter); - // sha256 of the user:realm:password - String ha1 = sha256(String(_config.Username) + ":" + realm + ":" + _config.Password); + auto algo = getAlgo(authReq); + if (!algo.first) { return { false, algo.second }; } - // sha256 of method:uri - String ha2 = sha256("GET:" + _uri); - - // sha256 of h1:nonce:nc:cNonce:auth:h2 - String response = sha256(ha1 + ":" + nonce + ":" + String(nc) + + auto hash = (algo.second == "SHA-256") ? &sha256 : &md5; + String ha1 = hash(String(_config.Username) + ":" + realm + ":" + _config.Password); + String ha2 = hash("GET:" + _uri); + String response = hash(ha1 + ":" + nonce + ":" + String(nc) + ":" + cNonce + ":" + "auth" + ":" + ha2); - // Final authorization String - return String("Digest username=\"") + _config.Username + + return { true, String("Digest username=\"") + _config.Username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + _uri + "\", cnonce=\"" + cNonce + "\", nc=" + nc + - ", qop=auth, response=\"" + response + "\", algorithm=SHA-256"; + ", qop=auth, response=\"" + response + "\", algorithm=" + algo.second }; } void HttpGetter::addHeader(char const* key, char const* value)