From db3ca9568867a0015474ddf1256d2571f7750000 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Thu, 11 Jan 2024 09:27:56 +0100 Subject: [PATCH] httpauth: digest verification rfc 7616 (#1044) * httpauth: digest verification function using RFC 7616 * httpauth: test cases for http digest verification * httpauth: use re_atomic for nc to avoid data race in multithreaded environment * httpauth: fix win onversion warning --- include/re_httpauth.h | 10 +- src/httpauth/digest.c | 194 +++++++++++++++++++++++++++++++++++-- test/httpauth.c | 220 ++++++++++++++++++++++++++++++++++++++---- test/test.c | 1 + test/test.h | 1 + 5 files changed, 400 insertions(+), 26 deletions(-) diff --git a/include/re_httpauth.h b/include/re_httpauth.h index 758a90862..d548e874e 100644 --- a/include/re_httpauth.h +++ b/include/re_httpauth.h @@ -70,6 +70,12 @@ struct httpauth_digest_resp { struct pl cnonce; struct pl qop; + struct pl algorithm; + struct pl charset; + struct pl userhash; + void (*hashh)(const uint8_t *, size_t, uint8_t *); + size_t hash_length; + struct mbuf *mb; }; @@ -117,6 +123,9 @@ int httpauth_digest_response_full(struct httpauth_digest_enc_resp **presp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *uri, const char *user, const char *passwd, const char *qop, const char *entitybody, const char *charset, const bool userhash); +int httpauth_digest_verify(struct httpauth_digest_chall_req *req, + const struct pl *hval, const struct pl *method, const char *etag, + const char *user, const char *passwd, const char *entitybody); int httpauth_digest_chall_req_print(struct re_printf *pf, const struct httpauth_digest_chall_req *req); @@ -127,7 +136,6 @@ int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, 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 2c3d04ccf..43f7c0f0e 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include #include @@ -70,6 +71,21 @@ static void challenge_decode(const struct pl *name, const struct pl *val, } +static void algorithm_decode(struct httpauth_digest_resp *resp, + const struct pl *val) +{ + resp->algorithm = *val; + if (pl_strstr(val, "SHA-256")) { + resp->hashh = &sha256; + resp->hash_length = SHA256_DIGEST_LENGTH; + } + else { + resp->hashh = &md5; + resp->hash_length = MD5_SIZE; + } +} + + static void response_decode(const struct pl *name, const struct pl *val, void *arg) { @@ -91,6 +107,12 @@ static void response_decode(const struct pl *name, const struct pl *val, resp->cnonce = *val; else if (!pl_casecmp(name, ¶m_qop)) resp->qop = *val; + else if (!pl_casecmp(name, ¶m_algorithm)) + algorithm_decode(resp, val); + else if (!pl_casecmp(name, ¶m_charset)) + resp->charset = *val; + else if (!pl_casecmp(name, ¶m_userhash)) + resp->userhash = *val; } @@ -243,7 +265,7 @@ int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp, } -static uint32_t nc = 1; +static RE_ATOMIC uint32_t nc = 1; int httpauth_digest_make_response(struct httpauth_digest_resp **presp, const struct httpauth_digest_chall *chall, @@ -277,7 +299,7 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **presp, pl_set_str(&resp->uri, path); resp->qop = chall->qop; - err = mbuf_printf(mb, "%x", nc); + err = mbuf_printf(mb, "%x", re_atomic_rlx(&nc)); err |= mbuf_write_u8(mb, 0); if (err) goto out; @@ -348,7 +370,8 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **presp, 0 == pl_strcmp(&resp->qop, "auth")) { /* response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) */ err = mbuf_printf(mb, "%w:%r:%x:%x:%r:%w", - ha1, sizeof(ha1), &resp->nonce, nc, cnonce, + ha1, sizeof(ha1), &resp->nonce, + re_atomic_rlx(&nc), cnonce, &resp->qop, ha2, sizeof(ha2)); } else { @@ -370,7 +393,7 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **presp, if (err) goto out; - ++nc; + re_atomic_rlx_add(&nc, 1); mbuf_set_pos(mb, 0); pl_set_str(&resp->nc, (const char*) mbuf_buf(mb)); mbuf_set_pos(mb, p1); @@ -389,7 +412,7 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **presp, int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, - struct mbuf *mb) + struct mbuf *mb) { int err; size_t s; @@ -488,6 +511,165 @@ static int generate_nonce(char **pnonce, const time_t ts, } +static int check_nonce(const char *req_nonce, const struct pl *resp_nonce, + const char *etag) +{ + struct pl pl = PL_INIT; + time_t ts; + char *renonce = NULL; + int err = 0; + + if (!req_nonce || !resp_nonce || !etag) + return EINVAL; + + pl = *resp_nonce; + pl.p = pl.p + (pl.l - 16); + pl.l = 16; + ts = (time_t) pl_x64(&pl); + + if (time(NULL) - ts > 300) { + err = ETIMEDOUT; + goto out; + } + + err = generate_nonce(&renonce, ts, etag, NULL); + if (err) + goto out; + + if (str_casecmp(req_nonce, renonce)) + err = EAUTH; + +out: + mem_deref(renonce); + return err; +} + + +static int digest_verify(struct httpauth_digest_chall_req *req, + struct httpauth_digest_resp *resp, const struct pl *method, + const char *user, const char *passwd, const char *entitybody) +{ + uint8_t *hash1 = NULL; + uint8_t *hash2 = NULL; + struct mbuf *mb = NULL; + int err = 0; + + mb = mbuf_alloc(str_len(user) + str_len(passwd) + + str_len(req->realm) + 2); + hash1 = mem_zalloc(resp->hash_length, NULL); + hash2 = mem_zalloc(resp->hash_length, NULL); + if (!mb || !hash1 || !hash2) { + err = ENOMEM; + goto out; + } + + /* HASH H2 */ + if (pl_strstr(&resp->qop, "auth-int")) { + if (!str_isset(entitybody)) + resp->hashh((uint8_t *)"", str_len(""), hash1); + else + resp->hashh((uint8_t *)entitybody, str_len(entitybody), + hash1); + + err = mbuf_printf(mb, "%r:%r:%w", method, &resp->uri, hash1, + resp->hash_length); + } + else { + err = mbuf_printf(mb, "%r:%r", method, &resp->uri); + } + + if (err) + goto out; + + resp->hashh(mb->buf, mb->end, hash2); + mbuf_rewind(mb); + + /* HASH H1 */ + if (pl_strcmp(&resp->username, user) != 0) { + err = EACCES; + goto out; + } + + err = mbuf_printf(mb, "%s:%r:%s", user, &resp->realm, passwd); + if (err) + goto out; + + resp->hashh(mb->buf, mb->end, hash1); + mbuf_rewind(mb); + + if (pl_strstr(&resp->algorithm, "-sess")) { + err = mbuf_printf(mb, "%w:%r:%r", + hash1, resp->hash_length, &resp->nonce, &resp->cnonce); + if (err) + goto out; + + resp->hashh(mb->buf, mb->end, hash1); + mbuf_rewind(mb); + } + + /* DIGEST */ + if (pl_isset(&resp->qop)) { + err = mbuf_printf(mb, "%w:%r:%r:%r:%r:%w", hash1, + resp->hash_length, &resp->nonce, &resp->nc, + &resp->cnonce, &resp->qop, hash2, resp->hash_length); + } + else { + err = mbuf_printf(mb, "%w:%r:%w", hash1, resp->hash_length, + &resp->nonce, hash2, resp->hash_length); + } + + if (err) + goto out; + + resp->hashh(mb->buf, mb->end, hash1); + mbuf_rewind(mb); + + /* VERIFICATION */ + err = pl_hex(&resp->response, hash2, resp->hash_length); + if (err) + goto out; + + err = mem_seccmp(hash1, hash2, resp->hash_length) == 0 ? 0 : EACCES; + +out: + mem_deref(hash1); + mem_deref(hash2); + mem_deref(mb); + + return err; +} + + +int httpauth_digest_verify(struct httpauth_digest_chall_req *req, + const struct pl *hval, const struct pl *method, const char *etag, + const char *user, const char *passwd, const char *entitybody) +{ + struct httpauth_digest_resp resp; + int err = 0; + + if (!req || !hval || !method || !user || !passwd) + return EINVAL; + + err = httpauth_digest_response_decode(&resp, hval); + if (err) + return err; + + if (pl_strcasecmp(&resp.realm, req->realm)) + return EINVAL; + + err = check_nonce(req->nonce, &resp.nonce, etag); + if (err == ETIMEDOUT || err == EAUTH) { + req->stale = true; + return EAUTH; + } + else if (err) { + return err; + } + + return digest_verify(req, &resp, method, user, passwd, entitybody); +} + + /** * Prints / encodes an HTTP digest request challenge * @@ -889,7 +1071,7 @@ int httpauth_digest_response_full(struct httpauth_digest_enc_resp **presp, /* create cnonce & nonce count */ resp->cnonce = rand_u32(); - resp->nc = nc++; + resp->nc = (uint32_t) re_atomic_rlx_add(&nc, 1); /* copy fields */ err = pl_strdup(&resp->realm, &chall->realm); diff --git a/test/httpauth.c b/test/httpauth.c index 3606ada2f..950b7c64e 100644 --- a/test/httpauth.c +++ b/test/httpauth.c @@ -171,8 +171,8 @@ int test_httpauth_resp(void) PL("sip:creytiv.com;transport=udp"), PL("00000002"), PL("66a7a21e46ad8edd"), - PL("auth"), - NULL}, + PL("auth"), PL_INIT, PL_INIT, + PL_INIT, NULL, 0, NULL}, PL("REGISTER"), "\x1c\x0a\x98\x61\x5b\x7b\x37\xc6" "\x94\x51\xae\xb6\x4b\x2f\x11\x02", @@ -180,8 +180,9 @@ int test_httpauth_resp(void) }, { "Digest bogus tull", - {PL_INIT, PL_INIT, PL_INIT, PL_INIT, - PL_INIT, PL_INIT, PL_INIT, PL_INIT, NULL}, + {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, + PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, + PL_INIT, NULL, 0 , NULL}, PL_INIT, "", EBADMSG @@ -420,7 +421,7 @@ int test_httpauth_digest_request(void) mb_printed = mbuf_alloc(512); if (!mb_refval || !mb_printed) { err = ENOMEM; - goto for_out; + goto out; } err = httpauth_digest_chall_request_full(&req, testv[i].realm, @@ -428,7 +429,7 @@ int test_httpauth_digest_request(void) testv[i].stale, testv[i].algorithm, testv[i].qop, testv[i].charset, testv[i].userhash); if (err == ENOMEM) { - goto for_out; + goto out; } else if (err != testv[i].err) { DEBUG_WARNING("[%d]" @@ -443,7 +444,7 @@ int test_httpauth_digest_request(void) if (err) { DEBUG_WARNING("[%d]" " No reference created %m\n", i, err); - goto for_out; + goto out; } err = mbuf_printf(mb_printed, "%H", @@ -451,14 +452,14 @@ int test_httpauth_digest_request(void) if (err) { DEBUG_WARNING("[%d]" " Digest request print error %m\n", i, err); - goto for_out; + goto 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; + goto out; } if (memcmp(mb_refval->buf, mb_printed->buf, mb_refval->end)) { @@ -466,7 +467,7 @@ int test_httpauth_digest_request(void) mb_refval->buf, mb_refval->end, mb_printed->buf, mb_printed->end); err = EINVAL; - goto for_out; + goto out; } for_continue: @@ -475,7 +476,7 @@ int test_httpauth_digest_request(void) mem_deref(mb_printed); continue; -for_out: +out: mem_deref(req); mem_deref(mb_refval); mem_deref(mb_printed); @@ -599,7 +600,7 @@ int test_httpauth_digest_response(void) mb_printed = mbuf_alloc(512); if (!mb_printed) { err = ENOMEM; - goto for_out; + goto out; } err = httpauth_digest_response_full(&resp, &testv[i].chall, @@ -607,12 +608,12 @@ int test_httpauth_digest_response(void) testv[i].passwd, testv[i].qop, testv[i].entitybody, NULL, false); if (err == ENOMEM) { - goto for_out; + goto out; } else if (err) { DEBUG_WARNING("[%d]" " Could not generate response %m\n", i, err); - goto for_out; + goto out; } err = httpauth_digest_response_set_cnonce(resp, @@ -622,13 +623,13 @@ int test_httpauth_digest_response(void) if (err) { DEBUG_WARNING("[%d]" " Response recalculation failed %m\n", i, err); - goto for_out; + goto out; } err = mbuf_printf(mb_printed, "%H", httpauth_digest_response_print, resp); if (err) - goto for_out; + goto out; if (str_casecmp(resp->response, testv[i].precalc_digest) != 0) { @@ -637,7 +638,7 @@ int test_httpauth_digest_response(void) " Expected response %s, got %w\n", i, testv[i].precalc_digest, resp->response, resp->hash_length); - goto for_out; + goto out; } if (memcmp(testv[i].resp_hval, @@ -647,14 +648,14 @@ int test_httpauth_digest_response(void) " Expected header %s, got %b\n", i, testv[i].resp_hval, mb_printed->buf, mb_printed->end); - goto for_out; + goto out; } mb_printed = mem_deref (mb_printed); resp = mem_deref(resp); continue; -for_out: +out: mb_printed = mem_deref (mb_printed); resp = mem_deref(resp); break; @@ -662,3 +663,184 @@ int test_httpauth_digest_response(void) return err; } + + +int test_httpauth_digest_verification(void) +{ + static const struct { + const char *realm; + const char *domain; + const char *opaque; + const bool stale; + const char *algorithm; + const char *qop; + const char *charset; + const bool userhash; + + const char *etag; + const char *entitybody; + + const char *user; + const char *passwd; + const char *uri; + const struct pl method; + const char *huser; + } testv [] = { + /* qop=auth & normal algorithm */ + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "MD5", "auth", NULL, false, + "localhost:5060", NULL, "retest", "sec_passwd", + "example.com/my/home/something", PL("GET"), NULL, + }, + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "SHA-256", "auth", NULL, false, + "localhost:5060", NULL, "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + /* qop=auth & session algorithm */ + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "MD5-sess", "auth", NULL, false, + "localhost:5060", NULL, "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "SHA-256-sess", "auth", NULL, false, + "localhost:5060", NULL, "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + /* qop=auth-int & normal algorithm */ + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "MD5", "auth-int", NULL, false, + "localhost:5060", NULL, "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "SHA-256", "auth-int", NULL, false, + "localhost:5060", "Strange body with content", + "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + /* qop=auth-int & session algorithm */ + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "MD5-sess", "auth-int", NULL, false, + "localhost:5060", "NOT NULL", "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + { + "/my/home", "example.com", + "185803523d335c8fe52cf633391d47f7", + false, "SHA-256-sess", "auth-int", NULL, false, + "localhost:5060", "NULL as String :D", + "retest", "sec_passed", + "example.com/my/home/something", PL("GET"), NULL + }, + }; + + int err = 0; + for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { + struct httpauth_digest_chall_req *req = NULL; + struct httpauth_digest_enc_resp *resp = NULL; + struct httpauth_digest_chall chall; + struct mbuf *mb_req = NULL; + struct mbuf *mb_resp = NULL; + struct pl plreq; + struct pl plresp; + + mb_req = mbuf_alloc(512); + mb_resp = mbuf_alloc(512); + if (!mb_req || !mb_resp) { + err = ENOMEM; + DEBUG_WARNING("[%d]" + " Could not allocate memory buffers \n", i); + goto out; + } + + 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); + if (err) { + DEBUG_WARNING("[%d]" + " Could not generate request (%m)\n", i, err); + goto out; + } + + err = mbuf_printf(mb_req, "%H", + httpauth_digest_chall_req_print, req); + if (err) { + DEBUG_WARNING("[%d]" + " Could not write digest request (%m)", + i, err); + goto out; + } + + mbuf_set_pos(mb_req, 0); + pl_set_mbuf(&plreq, mb_req); + err = httpauth_digest_challenge_decode(&chall, &plreq); + if (err) { + DEBUG_WARNING("[%d] Could not" + " decode \"received\" challenge (%m)", + i, err); + goto out; + } + + err = httpauth_digest_response_full(&resp, &chall, + &testv[i].method, testv[i].uri, testv[i].user, + testv[i].passwd, testv[i].qop, testv[i].entitybody, + testv[i].charset, testv[i].userhash); + if (err) { + DEBUG_WARNING("[%d]" + " Could not generate response (%m)\n", i, err); + goto out; + } + + err = mbuf_printf(mb_resp, "%H", + httpauth_digest_response_print, resp); + if (err) { + DEBUG_WARNING("[%d] Could not" + " decode \"received\" response (%m)\n", + i, err); + goto out; + } + + mbuf_set_pos(mb_resp, 0); + pl_set_mbuf(&plresp, mb_resp); + err = httpauth_digest_verify(req, &plresp, + &testv[i].method, testv[i].etag, + testv[i].user, testv[i].passwd, testv[i].entitybody); + if (err) { + DEBUG_WARNING("[%d]" + " Verification failed (%m)\n", i, err); + goto out; + } + + mem_deref(req); + mem_deref(resp); + mem_deref(mb_req); + mem_deref(mb_resp); + continue; + +out: + mem_deref(req); + mem_deref(resp); + mem_deref(mb_req); + mem_deref(mb_resp); + break; + } + + return err; +} diff --git a/test/test.c b/test/test.c index 3597b9742..261c1d96f 100644 --- a/test/test.c +++ b/test/test.c @@ -125,6 +125,7 @@ static const struct test tests[] = { TEST(test_httpauth_basic_request), TEST(test_httpauth_digest_request), TEST(test_httpauth_digest_response), + TEST(test_httpauth_digest_verification), TEST(test_ice_cand), TEST(test_ice_loop), TEST(test_json), diff --git a/test/test.h b/test/test.h index 57817908a..68a681e2c 100644 --- a/test/test.h +++ b/test/test.h @@ -229,6 +229,7 @@ int test_httpauth_resp(void); int test_httpauth_basic_request(void); int test_httpauth_digest_request(void); int test_httpauth_digest_response(void); +int test_httpauth_digest_verification(void); int test_ice_loop(void); int test_ice_cand(void); int test_json(void);