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:
parent
fd3b65f4bd
commit
1cbf18d4a7
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user