Feature: HttpGetter: cache digest challenge

this allows us to add a valid Authorization header to each but the first
GET request, saving us from performing two GET requests every time we
want to perform the GET request. we still need a new client nonce and we
need to increase the nonce counter, so we also need to calculate a whole
new response, as we cannot just reuse the previous Authorization header
(that would be a replay attack).
This commit is contained in:
Bernhard Kirchen 2024-08-21 22:01:10 +02:00 committed by Bernhard Kirchen
parent fd3b65f4bd
commit 1cbf18d4a7
2 changed files with 32 additions and 9 deletions

View File

@ -69,7 +69,7 @@ public:
char const* getErrorText() const { return _errBuffer; } char const* getErrorText() const { return _errBuffer; }
private: private:
std::pair<bool, String> getAuthDigest(String const& authReq, unsigned int counter); std::pair<bool, String> getAuthDigest();
HttpRequestConfig const& _config; HttpRequestConfig const& _config;
template<typename... Args> template<typename... Args>
@ -81,6 +81,9 @@ private:
String _uri; String _uri;
uint16_t _port; uint16_t _port;
String _wwwAuthenticate = "";
unsigned _nonceCounter = 0;
sp_wifi_client_t _spWiFiClient; // reused for multiple HTTP requests sp_wifi_client_t _spWiFiClient; // reused for multiple HTTP requests
std::vector<std::pair<std::string, std::string>> _additionalHeaders; std::vector<std::pair<std::string, std::string>> _additionalHeaders;

View File

@ -143,6 +143,16 @@ HttpRequestResult HttpGetter::performGetRequest()
const char *headers[2] = {"WWW-Authenticate", "Connection"}; const char *headers[2] = {"WWW-Authenticate", "Connection"};
upTmpHttpClient->collectHeaders(headers, 2); upTmpHttpClient->collectHeaders(headers, 2);
// try with new auth response based on previous WWW-Authenticate
// header, which allows us to retrieve the resource without a
// second GET request. if the server decides that we reused the
// previous challenge too often, it will respond with HTTP401 and
// a new challenge, which we handle as if we had no challenge yet.
auto authorization = getAuthDigest();
if (authorization.first) {
upTmpHttpClient->addHeader("Authorization", authorization.second);
}
break; break;
} }
} }
@ -150,13 +160,21 @@ HttpRequestResult HttpGetter::performGetRequest()
int httpCode = upTmpHttpClient->GET(); int httpCode = upTmpHttpClient->GET();
if (httpCode == HTTP_CODE_UNAUTHORIZED && _config.AuthType == Auth_t::Digest) { if (httpCode == HTTP_CODE_UNAUTHORIZED && _config.AuthType == Auth_t::Digest) {
_wwwAuthenticate = "";
if (!upTmpHttpClient->hasHeader("WWW-Authenticate")) { if (!upTmpHttpClient->hasHeader("WWW-Authenticate")) {
logError("Cannot perform digest authentication as server did " logError("Cannot perform digest authentication as server did "
"not send a WWW-Authenticate header"); "not send a WWW-Authenticate header");
return { false }; return { false };
} }
String authReq = upTmpHttpClient->header("WWW-Authenticate");
auto authorization = getAuthDigest(authReq, 1); _wwwAuthenticate = upTmpHttpClient->header("WWW-Authenticate");
// using a new WWW-Authenticate challenge means
// we never used the server's nonce in a response
_nonceCounter = 0;
auto authorization = getAuthDigest();
if (!authorization.first) { if (!authorization.first) {
logError("Digest Error: %s", authorization.second.c_str()); logError("Digest Error: %s", authorization.second.c_str());
return { false }; return { false };
@ -257,16 +275,18 @@ static std::pair<bool, String> getAlgo(String const& authReq) {
return { false, "unsupported digest algorithm" }; return { false, "unsupported digest algorithm" };
} }
std::pair<bool, String> HttpGetter::getAuthDigest(String const& authReq, unsigned int counter) { std::pair<bool, String> HttpGetter::getAuthDigest() {
if (_wwwAuthenticate.isEmpty()) { return { false, "no digest challenge yet" }; }
// extracting required parameters for RFC 2617 Digest // extracting required parameters for RFC 2617 Digest
String realm = extractParam(authReq, "realm=\"", '"'); String realm = extractParam(_wwwAuthenticate, "realm=\"", '"');
String nonce = extractParam(authReq, "nonce=\"", '"'); String nonce = extractParam(_wwwAuthenticate, "nonce=\"", '"');
String cNonce = getcNonce(8); String cNonce = getcNonce(8); // client nonce
char nc[9]; char nc[9];
snprintf(nc, sizeof(nc), "%08x", counter); snprintf(nc, sizeof(nc), "%08x", ++_nonceCounter);
auto algo = getAlgo(authReq); auto algo = getAlgo(_wwwAuthenticate);
if (!algo.first) { return { false, algo.second }; } if (!algo.first) { return { false, algo.second }; }
auto hash = (algo.second == "SHA-256") ? &sha256 : &md5; auto hash = (algo.second == "SHA-256") ? &sha256 : &md5;