From 6fc3201c3eda249daff4169cea660334c7ca29ca Mon Sep 17 00:00:00 2001 From: hecko Date: Sun, 12 Jul 2026 03:41:13 +0000 Subject: [PATCH] uac_auth: parse Authorization nonce-count as hex in checkAuthentication UACAuth::checkAuthentication() reads the digest "nc" (nonce-count) attribute from an incoming Authorization header and parses it with str2i(), which interprets the value as decimal. Per RFC 2617/7616 the nonce-count is an 8-digit hexadecimal number ("00000001", "0000000a", ...). SEMS then recomputes the expected response digest from that value via int2hex(nonce_count, true). For nonce-counts below 0x0a the two representations agree, but once a client using qop=auth/auth-int reaches its 10th request under one nonce it sends nc=0000000a: str2i() fails on the 'a' and the request is rejected; higher values such as nc=00000010 (decimal-parsed as 10 => "0000000a") produce a digest mismatch. Either way a correctly authenticating client starts getting spurious 401s once its nonce-count contains a hex digit a-f, breaking long-lived authenticated flows. Feed the client's original nc string straight into the digest instead of re-parsing and re-formatting it: uac_calc_response() now takes the nonce-count as a string; the UAS path passes the request's verbatim nc and the UAC (do_auth) path passes int2hex(nonce_count, true). This also makes the response comparison exact with respect to the client's formatting. Mirrors yeti-switch/sems commits a563fd5b and af6a7a16. Author: @hecko --- core/plug-in/uac_auth/UACAuth.cpp | 21 +++++++++++---------- core/plug-in/uac_auth/UACAuth.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/core/plug-in/uac_auth/UACAuth.cpp b/core/plug-in/uac_auth/UACAuth.cpp index 57bdca5c2..83bcc05b3 100644 --- a/core/plug-in/uac_auth/UACAuth.cpp +++ b/core/plug-in/uac_auth/UACAuth.cpp @@ -449,7 +449,7 @@ bool UACAuth::do_auth(const UACAuthDigestChallenge& challenge, /* do authentication */ uac_calc_HA1( challenge, credential, cnonce, ha1); uac_calc_HA2( method, uri, challenge, qop_auth_int ? hentity : NULL, ha2); - uac_calc_response( ha1, ha2, challenge, cnonce, qop_value, nonce_count, response); + uac_calc_response( ha1, ha2, challenge, cnonce, qop_value, int2hex(nonce_count,true), response); DBG("calculated response = %s\n", response); // compile auth response @@ -561,7 +561,7 @@ void UACAuth::uac_calc_hentity( const string& body, HASHHEX hentity ) */ void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2, const UACAuthDigestChallenge& challenge, const string& cnonce, - const string& qop_value, unsigned int nonce_count, HASHHEX response) + const string& qop_value, const string& nonce_count, HASHHEX response) { unsigned char hc[1]; hc[0]=':'; MD5_CTX Md5Ctx; @@ -576,7 +576,7 @@ void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2, if (!qop_value.empty()) { - w_MD5Update(&Md5Ctx, int2hex(nonce_count,true)); + w_MD5Update(&Md5Ctx, nonce_count); MD5Update(&Md5Ctx, hc, 1); w_MD5Update(&Md5Ctx, cnonce); MD5Update(&Md5Ctx, hc, 1); @@ -693,7 +693,7 @@ void UACAuth::checkAuthentication(const AmSipRequest* req, const string& realm, credential.user = user; credential.pwd = pwd; - unsigned int client_nonce_count = 1; + string client_nonce_count_str = "00000001"; HASHHEX ha1; HASHHEX ha2; @@ -712,14 +712,15 @@ void UACAuth::checkAuthentication(const AmSipRequest* req, const string& realm, if(qop_auth || qop_auth_int) { - // get nonce count from request - string nonce_count_str = find_attribute("nc", auth_hdr); - if (str2i(nonce_count_str, client_nonce_count)) { - DBG("Error parsing nonce_count '%s'\n", nonce_count_str.c_str()); + // get nonce count from request; the "nc" value is hex (RFC 2617/7616) + // and must be fed into the digest exactly as sent by the client + client_nonce_count_str = find_attribute("nc", auth_hdr); + if (client_nonce_count_str.empty()) { + DBG("missing nonce_count in request\n"); goto auth_end; } - DBG("got client_nonce_count %u\n", client_nonce_count); + DBG("got client nonce_count '%s'\n", client_nonce_count_str.c_str()); // auth-int? calculate hentity if(qop_auth_int){ @@ -736,7 +737,7 @@ void UACAuth::checkAuthentication(const AmSipRequest* req, const string& realm, uac_calc_HA1(r_challenge, &credential, r_cnonce, ha1); uac_calc_HA2(req->method, r_uri, r_challenge, qop_auth_int ? hentity : NULL, ha2); - uac_calc_response( ha1, ha2, r_challenge, r_cnonce, qop_value, client_nonce_count, response); + uac_calc_response( ha1, ha2, r_challenge, r_cnonce, qop_value, client_nonce_count_str, response); DBG("calculated our response vs request: '%s' vs '%s'", response, r_response.c_str()); if (tc_isequal((const char*)response, r_response.c_str(), HASHHEXLEN)) { diff --git a/core/plug-in/uac_auth/UACAuth.h b/core/plug-in/uac_auth/UACAuth.h index f8dcf3472..75a52ba96 100644 --- a/core/plug-in/uac_auth/UACAuth.h +++ b/core/plug-in/uac_auth/UACAuth.h @@ -133,7 +133,7 @@ class UACAuth : public AmSessionEventHandler static void uac_calc_response( HASHHEX ha1, HASHHEX ha2, const UACAuthDigestChallenge& challenge, const std::string& cnonce, const string& qop_value, - unsigned int nonce_count, + const std::string& nonce_count, HASHHEX response); /**