Skip to content

Commit 31c5cab

Browse files
committed
Extend digest_auth_fail_response for algorithm specification
- Add algorithm parameter to digest_auth_fail_response constructor (defaults to MD5 for backward compatibility) - Use MHD_queue_auth_fail_response2() to specify the algorithm in the WWW-Authenticate challenge header - Add separate MD5 and SHA256 test resources for deterministic testing - Add SHA256 digest auth tests alongside existing MD5 tests This enables server-driven algorithm selection, where the server requests a specific digest algorithm in the challenge and curl responds using that algorithm.
1 parent 2a4e0ea commit 31c5cab

File tree

3 files changed

+136
-19
lines changed

3 files changed

+136
-19
lines changed

src/digest_auth_fail_response.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ struct MHD_Response;
3030
namespace httpserver {
3131

3232
int digest_auth_fail_response::enqueue_response(MHD_Connection* connection, MHD_Response* response) {
33-
return MHD_queue_auth_fail_response(connection, realm.c_str(), opaque.c_str(), response, reload_nonce ? MHD_YES : MHD_NO);
33+
return MHD_queue_auth_fail_response2(
34+
connection,
35+
realm.c_str(),
36+
opaque.c_str(),
37+
response,
38+
reload_nonce ? MHD_YES : MHD_NO,
39+
static_cast<MHD_DigestAuthAlgorithm>(algorithm));
3440
}
3541

3642
} // namespace httpserver

src/httpserver/digest_auth_fail_response.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ class digest_auth_fail_response : public string_response {
4545
const std::string& opaque = "",
4646
bool reload_nonce = false,
4747
int response_code = http::http_utils::http_ok,
48-
const std::string& content_type = http::http_utils::text_plain):
48+
const std::string& content_type = http::http_utils::text_plain,
49+
http::http_utils::digest_algorithm algorithm =
50+
http::http_utils::digest_algorithm::MD5):
4951
string_response(content, response_code, content_type),
5052
realm(realm),
5153
opaque(opaque),
52-
reload_nonce(reload_nonce) { }
54+
reload_nonce(reload_nonce),
55+
algorithm(algorithm) { }
5356

5457
digest_auth_fail_response(const digest_auth_fail_response& other) = default;
5558
digest_auth_fail_response(digest_auth_fail_response&& other) noexcept = default;
@@ -64,6 +67,8 @@ class digest_auth_fail_response : public string_response {
6467
std::string realm = "";
6568
std::string opaque = "";
6669
bool reload_nonce = false;
70+
http::http_utils::digest_algorithm algorithm =
71+
http::http_utils::digest_algorithm::MD5;
6772
};
6873

6974
} // namespace httpserver

test/integ/authentication.cpp

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,23 +175,49 @@ static const unsigned char PRECOMPUTED_HA1_SHA256[32] = {
175175
0x20, 0x7e, 0x02, 0xd7, 0xc4, 0xbd, 0x8a, 0x05
176176
};
177177

178-
class digest_ha1_resource : public http_resource {
178+
class digest_ha1_md5_resource : public http_resource {
179179
public:
180180
shared_ptr<http_response> render_GET(const http_request& req) {
181181
if (req.get_digested_user() == "") {
182182
return std::make_shared<digest_auth_fail_response>(
183-
"FAIL", "examplerealm", MY_OPAQUE, true);
183+
"FAIL", "examplerealm", MY_OPAQUE, true,
184+
httpserver::http::http_utils::http_ok,
185+
httpserver::http::http_utils::text_plain,
186+
httpserver::http::http_utils::digest_algorithm::MD5);
184187
}
185188
bool reload_nonce = false;
186-
// Try MD5 first (default), then SHA-256 if that fails
187-
if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_MD5, 16, 300, &reload_nonce,
189+
if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_MD5,
190+
httpserver::http::http_utils::md5_digest_size, 300, &reload_nonce,
188191
httpserver::http::http_utils::digest_algorithm::MD5)) {
189-
// Try SHA-256
190-
if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_SHA256, 32, 300, &reload_nonce,
191-
httpserver::http::http_utils::digest_algorithm::SHA256)) {
192-
return std::make_shared<digest_auth_fail_response>(
193-
"FAIL", "examplerealm", MY_OPAQUE, reload_nonce);
194-
}
192+
return std::make_shared<digest_auth_fail_response>(
193+
"FAIL", "examplerealm", MY_OPAQUE, reload_nonce,
194+
httpserver::http::http_utils::http_ok,
195+
httpserver::http::http_utils::text_plain,
196+
httpserver::http::http_utils::digest_algorithm::MD5);
197+
}
198+
return std::make_shared<string_response>("SUCCESS", 200, "text/plain");
199+
}
200+
};
201+
202+
class digest_ha1_sha256_resource : public http_resource {
203+
public:
204+
shared_ptr<http_response> render_GET(const http_request& req) {
205+
if (req.get_digested_user() == "") {
206+
return std::make_shared<digest_auth_fail_response>(
207+
"FAIL", "examplerealm", MY_OPAQUE, true,
208+
httpserver::http::http_utils::http_ok,
209+
httpserver::http::http_utils::text_plain,
210+
httpserver::http::http_utils::digest_algorithm::SHA256);
211+
}
212+
bool reload_nonce = false;
213+
if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_SHA256,
214+
httpserver::http::http_utils::sha256_digest_size, 300, &reload_nonce,
215+
httpserver::http::http_utils::digest_algorithm::SHA256)) {
216+
return std::make_shared<digest_auth_fail_response>(
217+
"FAIL", "examplerealm", MY_OPAQUE, reload_nonce,
218+
httpserver::http::http_utils::http_ok,
219+
httpserver::http::http_utils::text_plain,
220+
httpserver::http::http_utils::digest_algorithm::SHA256);
195221
}
196222
return std::make_shared<string_response>("SUCCESS", 200, "text/plain");
197223
}
@@ -277,12 +303,92 @@ LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_wrong_pass)
277303
ws.stop();
278304
LT_END_AUTO_TEST(digest_auth_wrong_pass)
279305

280-
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1)
306+
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_md5)
307+
webserver ws = create_webserver(PORT)
308+
.digest_auth_random("myrandom")
309+
.nonce_nc_size(300);
310+
311+
digest_ha1_md5_resource digest_ha1;
312+
LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1));
313+
ws.start(false);
314+
315+
#if defined(_WINDOWS)
316+
curl_global_init(CURL_GLOBAL_WIN32);
317+
#else
318+
curl_global_init(CURL_GLOBAL_ALL);
319+
#endif
320+
321+
std::string s;
322+
CURL *curl = curl_easy_init();
323+
CURLcode res;
324+
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
325+
#if defined(_WINDOWS)
326+
curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:mypass");
327+
#else
328+
curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:mypass");
329+
#endif
330+
curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base");
331+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
332+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
333+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
334+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L);
335+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L);
336+
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
337+
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
338+
res = curl_easy_perform(curl);
339+
LT_ASSERT_EQ(res, 0);
340+
LT_CHECK_EQ(s, "SUCCESS");
341+
curl_easy_cleanup(curl);
342+
343+
ws.stop();
344+
LT_END_AUTO_TEST(digest_auth_with_ha1_md5)
345+
346+
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_md5_wrong_pass)
347+
webserver ws = create_webserver(PORT)
348+
.digest_auth_random("myrandom")
349+
.nonce_nc_size(300);
350+
351+
digest_ha1_md5_resource digest_ha1;
352+
LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1));
353+
ws.start(false);
354+
355+
#if defined(_WINDOWS)
356+
curl_global_init(CURL_GLOBAL_WIN32);
357+
#else
358+
curl_global_init(CURL_GLOBAL_ALL);
359+
#endif
360+
361+
std::string s;
362+
CURL *curl = curl_easy_init();
363+
CURLcode res;
364+
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
365+
#if defined(_WINDOWS)
366+
curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:wrongpass");
367+
#else
368+
curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:wrongpass");
369+
#endif
370+
curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base");
371+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
372+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
373+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
374+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L);
375+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L);
376+
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
377+
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
378+
res = curl_easy_perform(curl);
379+
LT_ASSERT_EQ(res, 0);
380+
LT_CHECK_EQ(s, "FAIL");
381+
curl_easy_cleanup(curl);
382+
383+
ws.stop();
384+
LT_END_AUTO_TEST(digest_auth_with_ha1_md5_wrong_pass)
385+
386+
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_sha256)
281387
webserver ws = create_webserver(PORT)
282388
.digest_auth_random("myrandom")
283389
.nonce_nc_size(300);
284390

285-
digest_ha1_resource digest_ha1;
391+
digest_ha1_sha256_resource digest_ha1;
286392
LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1));
287393
ws.start(false);
288394

@@ -315,14 +421,14 @@ LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1)
315421
curl_easy_cleanup(curl);
316422

317423
ws.stop();
318-
LT_END_AUTO_TEST(digest_auth_with_ha1)
424+
LT_END_AUTO_TEST(digest_auth_with_ha1_sha256)
319425

320-
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_wrong_pass)
426+
LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_sha256_wrong_pass)
321427
webserver ws = create_webserver(PORT)
322428
.digest_auth_random("myrandom")
323429
.nonce_nc_size(300);
324430

325-
digest_ha1_resource digest_ha1;
431+
digest_ha1_sha256_resource digest_ha1;
326432
LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1));
327433
ws.start(false);
328434

@@ -355,7 +461,7 @@ LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_wrong_pass)
355461
curl_easy_cleanup(curl);
356462

357463
ws.stop();
358-
LT_END_AUTO_TEST(digest_auth_with_ha1_wrong_pass)
464+
LT_END_AUTO_TEST(digest_auth_with_ha1_sha256_wrong_pass)
359465

360466
#endif
361467

0 commit comments

Comments
 (0)