From 3ce3892cd6a81be6abe67272b09ee0855e1c06c5 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Thu, 24 Aug 2023 10:19:24 +0200 Subject: [PATCH 1/7] httpauth: http digest challenge request using RFC 7616 + definition of a request struct allowing to handle necessary data used for the digest challenge + adding data fields to challenge struct to support the new data fields --- include/re_httpauth.h | 29 +++++++ src/httpauth/digest.c | 191 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/include/re_httpauth.h b/include/re_httpauth.h index 83f6d3b21..d896f9409 100644 --- a/include/re_httpauth.h +++ b/include/re_httpauth.h @@ -5,6 +5,21 @@ */ +/** HTTP digest request challenge */ +struct httpauth_digest_chall_req { + char *realm; + char *domain; + char *nonce; + char *opaque; + bool stale; + char *algorithm; + char *qop; + + /* optional */ + char *charset; + bool userhash; +}; + /** HTTP Digest Challenge */ struct httpauth_digest_chall { struct pl realm; @@ -15,6 +30,9 @@ struct httpauth_digest_chall { struct pl stale; struct pl algorithm; struct pl qop; + struct pl domain; + struct pl charset; + struct pl userhash; }; /** HTTP Digest response */ @@ -62,6 +80,17 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **resp, int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, struct mbuf *mb); + +int httpauth_digest_chall_req_print(struct re_printf *pf, + const struct httpauth_digest_chall_req *req); +int httpauth_digest_min_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *etag, const char *qop); +int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, + const char *real, const char *domain, const char *etag, + const char *opaque, const bool stale, const char *algo, + const char *qop, const char *charset, const bool userhash); + + struct httpauth_basic *httpauth_basic_alloc(void); int httpauth_basic_decode(struct httpauth_basic *basic, const struct pl *hval); diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index d10019535..b2132985c 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -1,14 +1,17 @@ /** - * @file digest.c HTTP Digest authentication (RFC 2617) + * @file digest.c HTTP Digest authentication (RFC 2617) - obsolete + * HTTP Digest authentication (RFC 7616) - wip * * Copyright (C) 2010 Creytiv.com */ #include +#include #include #include #include #include #include +#include #include #include @@ -410,3 +413,189 @@ int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, mbuf_set_pos(mb, 0); return err; } + + +static void httpauth_digest_chall_req_destructor(void *arg) +{ + struct httpauth_digest_chall_req *req = arg; + + mem_deref(req->realm); + mem_deref(req->domain); + mem_deref(req->nonce); + mem_deref(req->opaque); + mem_deref(req->algorithm); + mem_deref(req->qop); + mem_deref(req->charset); +} + + +static int generate_nonce(char **pnonce, const time_t ts, + const char *etag, const char *secret) +{ + struct mbuf *mb = NULL; + char *nonce = NULL; + uint8_t *hash = NULL; + uint8_t hashlen = SHA256_DIGEST_LENGTH; + int err = 0; + + mb = mbuf_alloc(32); + if (!mb) + return ENOMEM; + + hash = mem_zalloc(hashlen, NULL); + if (!hash) { + err = ENOMEM; + goto out; + } + + if (str_isset(secret)) + err = mbuf_printf(mb, "%lu:%s:%s", ts, etag, secret); + else + err = mbuf_printf(mb, "%lu:%s", ts, etag); + + if (err) + goto out; + + sha256(mb->buf, mb->end, hash); + mbuf_rewind(mb); + + err = mbuf_printf(mb, "%w%016lx", hash, hashlen, ts); + if (err) + goto out; + + mbuf_set_pos(mb, 0); + err = mbuf_strdup(mb, &nonce, mbuf_get_left(mb)); + +out: + if (err) + mem_deref(nonce); + else + *pnonce = nonce; + + mem_deref(mb); + mem_deref(hash); + + return err; +} + + +/** + * Prints / encodes an HTTP digest request challenge + * + * @param pf Re_printf object + * @param req Request to print + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_chall_req_print(struct re_printf *pf, + const struct httpauth_digest_chall_req *req) +{ + int err = 0; + + if (!req) + return EINVAL; + + /* historical reason quoted strings: */ + /* realm, domain, nonce, opaque, qop */ + /* historical reason unquoted strings: */ + /* stale, algorithm */ + err = re_hprintf(pf, "Digest realm=\"%s\", " + "qop=\"%s\", nonce=\"%s\", algorithm=%s", + req->realm, req->qop, req->nonce, req->algorithm); + + if (str_isset(req->opaque)) + err |= re_hprintf(pf, ", opaque=\"%s\"", req->opaque); + if (str_isset(req->domain)) + err |= re_hprintf(pf, ", domain=\"%s\"", req->domain); + if (req->stale) + err |= re_hprintf(pf, ", stale=true"); + if (str_isset(req->charset)) + err |= re_hprintf(pf, ", charset=\"%s\"", req->charset); + if (req->userhash) + err |= re_hprintf(pf, ", userhash=true"); + + return err; +} + + +/** + * Minimal requirement digest authentication request + * + * @param preq Httpauth_digest_chall_req object ptr + * @param realm Realm + * @param etag Changing data for nonce creation + * (HTTP ETag header / SIP msg src address) + * @param qop Quality of protection + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_min_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *etag, const char *qop) +{ + return httpauth_digest_chall_request(preq, realm, NULL, etag, NULL, + false, NULL, qop, NULL, false); +} + + +/** + * Create a digest authentication request + * + * @param preq Httpauth_digest_chall_req object ptr + * @param realm Realm + * @param domain Domain (not used in SIP) + * @param etag Changing data for nonce creation + * (HTTP ETag header / SIP msg src address) + * @param opaque Opaque + * @param stale Stale + * @param algo Supported algorithm (MD5, SHA1, SHA256 and sess versions) + * @param qop Quality of protection + * @param charset Character set used (not used in SIP) + * @param userhash Userhash support (not used in SIP) + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *domain, const char *etag, + const char *opaque, const bool stale, const char *algo, + const char *qop, const char *charset, const bool userhash) +{ + struct httpauth_digest_chall_req *req = NULL; + int err = 0; + + if (!preq || !realm || !etag || !qop) + return EINVAL; + + req = mem_zalloc(sizeof(*req), httpauth_digest_chall_req_destructor); + if (!req) + return ENOMEM; + + req->stale = stale; + req->userhash = userhash; + err = str_dup(&req->realm, realm); + err |= str_dup(&req->qop, qop); + + if (str_isset(algo)) + err |= str_dup(&req->algorithm, algo); + else + err |= str_dup(&req->algorithm, "MD5"); + + if (str_isset(domain)) + err |= str_dup(&req->domain, domain); + if (str_isset(opaque)) + err |= str_dup(&req->opaque, opaque); + if (str_isset(charset) && str_casecmp(charset, "UTF-8") == 0) + err |= str_dup(&req->charset, charset); + + if (err) + goto out; + + err = generate_nonce(&req->nonce, time(NULL), etag, NULL); + +out: + if (err) + mem_deref(req); + else + *preq = req; + + return err; +} From 25f1e70ed46a40df52f5ba98bf6835c349975926 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Thu, 24 Aug 2023 11:27:52 +0200 Subject: [PATCH 2/7] httpauth: test cases for http digest challenge requests --- test/httpauth.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++- test/test.c | 1 + test/test.h | 1 + 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/test/httpauth.c b/test/httpauth.c index 18bed195c..9fc0212a6 100644 --- a/test/httpauth.c +++ b/test/httpauth.c @@ -86,6 +86,9 @@ int test_httpauth_chall(void) PL("123"), PL("true"), PL("MD5"), + PL_INIT, + PL_INIT, + PL_INIT, PL_INIT}, 0 }, @@ -98,13 +101,15 @@ int test_httpauth_chall(void) PL("9c916919cbc6ad7f54a4f64e5b5115074ee109fa"), PL_INIT, PL_INIT, PL_INIT, PL("auth"), + PL_INIT, PL_INIT, PL_INIT }, 0 }, { "Basic bogus", - {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT}, + {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, + PL_INIT, PL_INIT, PL_INIT}, EBADMSG }, }; @@ -329,3 +334,173 @@ int test_httpauth_basic_request(void) { return err; } + + +int test_httpauth_digest_request(void) +{ + static const struct { + const char *hval_fmt; + const char *realm; + const char *domain; + const char *etag; + const char *opaque; + const bool stale; + const char *algorithm; + const char *qop; + const char *charset; + const bool userhash; + int err; + } testv [] = { + { + "", + NULL, NULL, "", NULL, false, NULL, "auth", NULL, false, + EINVAL + }, + { + "Digest realm=\"/my/home\", qop=\"\"," + " nonce=\"%s\", algorithm=MD5", + "/my/home", NULL, "localhost:5060", NULL, false, + NULL, "", NULL, false, + 0 + }, + { + "Digest realm=\"/my/home\", qop=\"\"," + " nonce=\"%s\", algorithm=MD5", + "/my/home", NULL, "localhost:5060", NULL, false, + NULL, "", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256", + "/my/home", NULL, "localhost:5060", NULL, false, + "SHA256", "auth", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256-sess, stale=true", + "/my/home", NULL, "localhost:5060", NULL, true, + "SHA256-sess", "auth", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA1," + " stale=true, userhash=true", + "/my/home", NULL, "localhost:5060", NULL, true, + "SHA1", "auth", NULL, true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA1-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "SHA1-sess", "auth", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "SHA256", "auth", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=MD5-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "MD5-sess", "auth-int", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=SHA1-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "213579023", NULL, + true, "SHA1-sess", "auth-int", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=MD5," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "129842", NULL, + true, NULL, "auth-int", "UTF-8", true, 0 + }, + }; + + int err = 0; + for (unsigned int i = 0; i < RE_ARRAY_SIZE(testv); i++) { + struct httpauth_digest_chall_req *req = NULL; + struct mbuf *mb_refval = NULL; + struct mbuf *mb_printed = NULL; + + mb_refval = mbuf_alloc(512); + mb_printed = mbuf_alloc(512); + if (!mb_refval || !mb_printed) { + err = ENOMEM; + goto for_out; + } + + err = httpauth_digest_chall_request(&req, testv[i].realm, + testv[i].domain, testv[i].etag, testv[i].opaque, + testv[i].stale, testv[i].algorithm, testv[i].qop, + testv[i].charset, testv[i].userhash); + if (err == ENOMEM) { + goto for_out; + } + else if (err != testv[i].err) { + DEBUG_WARNING("[%d]" + " Expected return value %m, got %m\n", + i, testv[i].err, err); + } + else if (err) { + goto for_continue; + } + + err = mbuf_printf(mb_refval, testv[i].hval_fmt, req->nonce); + if (err) { + DEBUG_WARNING("[%d]" + " No reference created %m\n", i, err); + goto for_out; + } + + err = mbuf_printf(mb_printed, "%H", + httpauth_digest_chall_req_print, req); + if (err) { + DEBUG_WARNING("[%d]" + " Digest request print error %m\n", i, err); + goto for_out; + } + + if (mb_refval->end != mb_printed->end) { + DEBUG_WARNING("[%d] Expected header len %d, got %d\n", + i, mb_refval->end, mb_printed->end); + err = EINVAL; + goto for_out; + } + + if (memcmp(mb_refval->buf, mb_printed->buf, mb_refval->end)) { + DEBUG_WARNING("[%d] Expected header %b, got %b\n", i, + mb_refval->buf, mb_refval->end, + mb_printed->buf, mb_printed->end); + err = EINVAL; + goto for_out; + } + +for_continue: + mem_deref(req); + mem_deref(mb_refval); + mem_deref(mb_printed); + continue; + +for_out: + mem_deref(req); + mem_deref(mb_refval); + mem_deref(mb_printed); + break; + } + + return err; +} diff --git a/test/test.c b/test/test.c index 2e25c310b..d268d0623 100644 --- a/test/test.c +++ b/test/test.c @@ -119,6 +119,7 @@ static const struct test tests[] = { TEST(test_httpauth_chall), TEST(test_httpauth_resp), TEST(test_httpauth_basic_request), + TEST(test_httpauth_digest_request), TEST(test_ice_cand), TEST(test_ice_loop), TEST(test_jbuf), diff --git a/test/test.h b/test/test.h index d03909ed4..dc614f9b5 100644 --- a/test/test.h +++ b/test/test.h @@ -223,6 +223,7 @@ int test_https_conn_post_handshake(void); int test_httpauth_chall(void); int test_httpauth_resp(void); int test_httpauth_basic_request(void); +int test_httpauth_digest_request(void); int test_ice_loop(void); int test_ice_cand(void); int test_jbuf(void); From 0703164f4dd6431be65ca84a8ad45a4322c69cb5 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Mon, 28 Aug 2023 10:32:41 +0200 Subject: [PATCH 3/7] httpauth: fix mingw by casting time_t when printing timestamp --- src/httpauth/digest.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index b2132985c..8255521cd 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -449,9 +449,10 @@ static int generate_nonce(char **pnonce, const time_t ts, } if (str_isset(secret)) - err = mbuf_printf(mb, "%lu:%s:%s", ts, etag, secret); + err = mbuf_printf(mb, "%lu:%s:%s", + (long unsigned)ts, etag, secret); else - err = mbuf_printf(mb, "%lu:%s", ts, etag); + err = mbuf_printf(mb, "%lu:%s", (long unsigned)ts, etag); if (err) goto out; @@ -459,7 +460,7 @@ static int generate_nonce(char **pnonce, const time_t ts, sha256(mb->buf, mb->end, hash); mbuf_rewind(mb); - err = mbuf_printf(mb, "%w%016lx", hash, hashlen, ts); + err = mbuf_printf(mb, "%w%016lx", hash, hashlen, (long unsigned)ts); if (err) goto out; From 9177a2c835d510d93b64bd754b79961b08c622fa Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Tue, 29 Aug 2023 13:37:26 +0200 Subject: [PATCH 4/7] httpauth: rename digest challenge request functions --- include/re_httpauth.h | 4 ++-- src/httpauth/digest.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/re_httpauth.h b/include/re_httpauth.h index d896f9409..0fce26dbd 100644 --- a/include/re_httpauth.h +++ b/include/re_httpauth.h @@ -83,9 +83,9 @@ int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, int httpauth_digest_chall_req_print(struct re_printf *pf, const struct httpauth_digest_chall_req *req); -int httpauth_digest_min_chall_request(struct httpauth_digest_chall_req **preq, - const char *realm, const char *etag, const char *qop); int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *etag, const char *qop); +int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, const char *real, const char *domain, const char *etag, const char *opaque, const bool stale, const char *algo, const char *qop, const char *charset, const bool userhash); diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index 8255521cd..9145bcb23 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -520,7 +520,7 @@ int httpauth_digest_chall_req_print(struct re_printf *pf, /** - * Minimal requirement digest authentication request + * Create a digest authentication request * * @param preq Httpauth_digest_chall_req object ptr * @param realm Realm @@ -530,16 +530,16 @@ int httpauth_digest_chall_req_print(struct re_printf *pf, * * @return 0 if success, otherwise errorcode */ -int httpauth_digest_min_chall_request(struct httpauth_digest_chall_req **preq, +int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, const char *realm, const char *etag, const char *qop) { - return httpauth_digest_chall_request(preq, realm, NULL, etag, NULL, - false, NULL, qop, NULL, false); + return httpauth_digest_chall_request_full(preq, realm, NULL, etag, + NULL, false, NULL, qop, NULL, false); } /** - * Create a digest authentication request + * Create a full configurable digest authentication request * * @param preq Httpauth_digest_chall_req object ptr * @param realm Realm @@ -555,7 +555,7 @@ int httpauth_digest_min_chall_request(struct httpauth_digest_chall_req **preq, * * @return 0 if success, otherwise errorcode */ -int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, +int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, const char *realm, const char *domain, const char *etag, const char *opaque, const bool stale, const char *algo, const char *qop, const char *charset, const bool userhash) From 0cb2bbc94556213d8d108d6107198f07f0ccce03 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Tue, 29 Aug 2023 13:37:58 +0200 Subject: [PATCH 5/7] httpauth: fix testcase with new function name --- test/httpauth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/httpauth.c b/test/httpauth.c index 9fc0212a6..4c01a981a 100644 --- a/test/httpauth.c +++ b/test/httpauth.c @@ -443,7 +443,7 @@ int test_httpauth_digest_request(void) goto for_out; } - err = httpauth_digest_chall_request(&req, testv[i].realm, + err = httpauth_digest_chall_request_full(&req, testv[i].realm, testv[i].domain, testv[i].etag, testv[i].opaque, testv[i].stale, testv[i].algorithm, testv[i].qop, testv[i].charset, testv[i].userhash); From af1451707a916b3ddb22b54b923ea0f690e02830 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Wed, 30 Aug 2023 14:13:27 +0200 Subject: [PATCH 6/7] httpauth: fixed size integer cast of time_t. hashlen variable to size_t --- src/httpauth/digest.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index 9145bcb23..5fe4b0e08 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -435,7 +435,7 @@ static int generate_nonce(char **pnonce, const time_t ts, struct mbuf *mb = NULL; char *nonce = NULL; uint8_t *hash = NULL; - uint8_t hashlen = SHA256_DIGEST_LENGTH; + size_t hashlen = SHA256_DIGEST_LENGTH; int err = 0; mb = mbuf_alloc(32); @@ -449,10 +449,10 @@ static int generate_nonce(char **pnonce, const time_t ts, } if (str_isset(secret)) - err = mbuf_printf(mb, "%lu:%s:%s", - (long unsigned)ts, etag, secret); + err = mbuf_printf(mb, "%"PRIu64":%s:%s", + (uint64_t)ts, etag, secret); else - err = mbuf_printf(mb, "%lu:%s", (long unsigned)ts, etag); + err = mbuf_printf(mb, "%"PRIu64":%s", (uint64_t)ts, etag); if (err) goto out; @@ -460,7 +460,7 @@ static int generate_nonce(char **pnonce, const time_t ts, sha256(mb->buf, mb->end, hash); mbuf_rewind(mb); - err = mbuf_printf(mb, "%w%016lx", hash, hashlen, (long unsigned)ts); + err = mbuf_printf(mb, "%w%016"PRIx64"", hash, hashlen, (uint64_t)ts); if (err) goto out; From 8eda4828c2937a24e4280a6ca7da4936c1427221 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Wed, 30 Aug 2023 14:54:05 +0200 Subject: [PATCH 7/7] httpauth: use a stack array for the hash instead of heap --- src/httpauth/digest.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index 5fe4b0e08..c0788b153 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -434,20 +434,13 @@ static int generate_nonce(char **pnonce, const time_t ts, { struct mbuf *mb = NULL; char *nonce = NULL; - uint8_t *hash = NULL; - size_t hashlen = SHA256_DIGEST_LENGTH; + uint8_t hash [SHA256_DIGEST_LENGTH]; int err = 0; mb = mbuf_alloc(32); if (!mb) return ENOMEM; - hash = mem_zalloc(hashlen, NULL); - if (!hash) { - err = ENOMEM; - goto out; - } - if (str_isset(secret)) err = mbuf_printf(mb, "%"PRIu64":%s:%s", (uint64_t)ts, etag, secret); @@ -460,7 +453,8 @@ static int generate_nonce(char **pnonce, const time_t ts, sha256(mb->buf, mb->end, hash); mbuf_rewind(mb); - err = mbuf_printf(mb, "%w%016"PRIx64"", hash, hashlen, (uint64_t)ts); + err = mbuf_printf(mb, "%w%016"PRIx64"", hash, sizeof(hash), + (uint64_t)ts); if (err) goto out; @@ -474,7 +468,6 @@ static int generate_nonce(char **pnonce, const time_t ts, *pnonce = nonce; mem_deref(mb); - mem_deref(hash); return err; }