Skip to content

Commit

Permalink
Feature: HttpGetter: support MD5 digest authentication
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
schlimmchen committed Aug 28, 2024
1 parent 63612e9 commit fd3b65f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 20 deletions.
2 changes: 1 addition & 1 deletion include/HttpGetter.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class HttpGetter {
char const* getErrorText() const { return _errBuffer; }

private:
String getAuthDigest(String const& authReq, unsigned int counter);
std::pair<bool, String> getAuthDigest(String const& authReq, unsigned int counter);
HttpRequestConfig const& _config;

template<typename... Args>
Expand Down
74 changes: 55 additions & 19 deletions src/HttpGetter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "HttpGetter.h"
#include <WiFiClientSecure.h>
#include "mbedtls/sha256.h"
#include "mbedtls/md5.h"
#include <base64.h>
#include <ESPmDNS.h>

Expand Down Expand Up @@ -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;
Expand All @@ -183,6 +188,29 @@ HttpRequestResult HttpGetter::performGetRequest()
return { true, std::move(upTmpHttpClient), _spWiFiClient };
}

template<size_t binLen>
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<const unsigned char*>(data.c_str()), data.length());
mbedtls_md5_finish_ret(&ctx, hash);
mbedtls_md5_free(&ctx);

return bin2hex<sizeof(hash)>(hash);
}

static String sha256(const String& data) {
uint8_t hash[32];

Expand All @@ -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<sizeof(hash)>(hash);
}

static String extractParam(String const& authReq, String const& param, char delimiter) {
Expand All @@ -219,7 +242,22 @@ static String getcNonce(int len) {
return s;
}

String HttpGetter::getAuthDigest(String const& authReq, unsigned int counter) {
static std::pair<bool, String> 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<bool, String> 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=\"", '"');
Expand All @@ -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);

// sha256 of method:uri
String ha2 = sha256("GET:" + _uri);
auto algo = getAlgo(authReq);
if (!algo.first) { return { false, algo.second }; }

// 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)
Expand Down

0 comments on commit fd3b65f

Please sign in to comment.