Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes related to digest authorisation #9074

Closed
wants to merge 3 commits into from

Conversation

Karlson2k
Copy link
Contributor

@Karlson2k Karlson2k commented Jun 30, 2022

This PR fixes a few things related to "session" digest HTTP authorisation:

  • remember generated H(A1) value and reuse it for the next requests
  • always generate new cnonce when cnonce is used in calculation (for both session and non-session)

Some commits were moved to separate PRs:


I've made some investigation.
See #9074 (comment) and #9074 (comment)

@dfandrich
Copy link
Contributor

dfandrich commented Jun 30, 2022 via email

@Karlson2k
Copy link
Contributor Author

Is there a security impact with this change? The second two points especially sounds like there might be.

  • remember generated H(A1) value and reuse it for the next requests - currently it is the same just because cnonce is the same
  • always generate new cnonce when cnonce is used in calculation (for both session and non-session) - there is no security breach in current form, it only improves security by making it harder to decode username and password by sniffing traffic

It is mostly improvements, except the last item (reset auth), but currently it breaks tests 2024, 2027, 2030.
I could split this PR into several independent PRs

@Karlson2k
Copy link
Contributor Author

PR has been simplified by moving some commits to separate PRs

@Karlson2k
Copy link
Contributor Author

I've checked popular web server software and found out:

  • Apache: MD5-sess is not correctly implemented yet from the module page.
  • NGINX: the module does not support any algo except "MD5".
  • OpenLiteSpeed: the latest version 1.7.16 does not support even RFC 2617, only the old RFC 2069 (see the source code here and here).
  • IIS: supports MD5-sess (unsurprisingly seems to be implemented with SSPI).

Firefox bugtracker has related bug opened 18 years ago and still not closed: https://bugzilla.mozilla.org/show_bug.cgi?id=270447

@Karlson2k
Copy link
Contributor Author

Karlson2k commented Jul 7, 2022

As IIS is the only server, which supports -sess algo, I had to setup IIS (to use it I had to setup Windows Server and configure AD domain controller, otherwise Digest auth does not work with IIS).
I've tested IIS with several client.
In each client I tries to open a single page with cache disabled and reload the page two times.


The old, already abandoned IE works fine. IE uses the same cnonce value.

IIS <-> IE log
GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 10.5.5.5:8088
Connection: Keep-Alive
Cache-Control: no-cache

HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5103c4b6970a92d801dab86d2114140c85569f682b22a27145199871b80da2c307630d5d105e0fc4f3",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 14:05:20 GMT
Content-Length: 5941

[skipped error response body]
GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 10.5.5.5:8088
Connection: Keep-Alive
Cache-Control: no-cache
Authorization: Digest username="testuser",realm="testrealm",nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5103c4b6970a92d801dab86d2114140c85569f682b22a27145199871b80da2c307630d5d105e0fc4f3",uri="/index.html",cnonce="+Upgraded+v1086f43f60f6c2479a5feb1f7ca68e950991bcb85cb6fc53febefdd094ae6733c",nc=00000001,algorithm=MD5-sess,response="cd8ecaee1bbdd6c677aafe03c855a087",qop="auth",charset=utf-8,hashed-dirs="service-name,channel-binding",service-name="HTTP/10.5.5.5",channel-binding="00000000000000000000000000000000"

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 14:05:23 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 10.5.5.5:8088
Connection: Keep-Alive
Authorization: Digest username="testuser",realm="testrealm",nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5103c4b6970a92d801dab86d2114140c85569f682b22a27145199871b80da2c307630d5d105e0fc4f3",uri="/index.html",cnonce="+Upgraded+v1086f43f60f6c2479a5feb1f7ca68e950991bcb85cb6fc53febefdd094ae6733c",nc=00000002,algorithm=MD5-sess,response="a332601adda65532bb7c6fff558408d9",qop="auth",charset=utf-8

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 14:05:26 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 10.5.5.5:8088
Connection: Keep-Alive
Authorization: Digest username="testuser",realm="testrealm",nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5103c4b6970a92d801dab86d2114140c85569f682b22a27145199871b80da2c307630d5d105e0fc4f3",uri="/index.html",cnonce="+Upgraded+v1086f43f60f6c2479a5feb1f7ca68e950991bcb85cb6fc53febefdd094ae6733c",nc=00000003,algorithm=MD5-sess,response="2f261c6631a2912fe36620db52d37290",qop="auth",charset=utf-8

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 14:05:28 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>

EDGE gets stale=true every time and second attempt succeed (stale might be produced because it takes time to enter the password). However, every page reload requests the password from user again and authorisation restarted with the new nonce.

IIS <-> EDGE log
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51d12ce1200a92d801f083e72fe9cf556ec18cfdbada130ad72fcf2fa370b9bbd2358057cf403f100d",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 14:02:00 GMT
Content-Length: 5941

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51d12ce1200a92d801f083e72fe9cf556ec18cfdbada130ad72fcf2fa370b9bbd2358057cf403f100d", uri="/index.html", algorithm=MD5-sess, response="8fc6359268b6e543d4f89e0d74417028", qop=auth, nc=00000002, cnonce="1bc88e02dd1a0909"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5145e6bf240a92d8011eea0fba4a3f8bec50a2257b4b4c9b4b0ea74a7a64fe66deb4900de259a2c792",charset=utf-8,realm="testrealm",stale=true
Date: Thu, 07 Jul 2022 14:02:07 GMT
Content-Length: 6179

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5145e6bf240a92d8011eea0fba4a3f8bec50a2257b4b4c9b4b0ea74a7a64fe66deb4900de259a2c792", uri="/index.html", algorithm=MD5-sess, response="bf9f0c513377f329eafa66b514e402a5", qop=auth, nc=00000001, cnonce="0f812304c8233586"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 14:02:07 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>

Chrome behaviour is similar to EDGE's one (to be more precise: actually Edge behaves like Chrome). stale, new request for password each time when page is reloaded and authorisation restarted.

IIS <-> Chrome log
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51d55346a62092d801678163219f88dcb3f18ba2d31eb2f71e6673117bce765f554a8ca109a1bf302a",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:43:13 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51d55346a62092d801678163219f88dcb3f18ba2d31eb2f71e6673117bce765f554a8ca109a1bf302a", uri="/index.html", algorithm=MD5-sess, response="0629bc3db1d5a25daf7160a11f90c535", qop=auth, nc=00000002, cnonce="a6f7fc26d87f902b"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5142d085a72092d8010a11ee14af5d8d16d07ddd95362a07a2eebc57502a0b37bf913dc93a02b602f2",charset=utf-8,realm="testrealm",stale=true
Date: Thu, 07 Jul 2022 16:43:15 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5142d085a72092d8010a11ee14af5d8d16d07ddd95362a07a2eebc57502a0b37bf913dc93a02b602f2", uri="/index.html", algorithm=MD5-sess, response="5390db5f9991e072fedc5c6bbf5b439f", qop=auth, nc=00000001, cnonce="857f4dc494594d51"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:43:15 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e5142d085a72092d8010a11ee14af5d8d16d07ddd95362a07a2eebc57502a0b37bf913dc93a02b602f2", uri="/index.html", algorithm=MD5-sess, response="964b676116ee88e97c5c9f670d421d49", qop=auth, nc=00000002, cnonce="f00204b1cd247c0c"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51b2f546a82092d8013832b26f0e9303a7ae0feafd0b212aface41197b815fe91e0231b705d8de4b53",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:43:16 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51b2f546a82092d8013832b26f0e9303a7ae0feafd0b212aface41197b815fe91e0231b705d8de4b53", uri="/index.html", algorithm=MD5-sess, response="e8019dd7f98d4620d3d2bd6310173932", qop=auth, nc=00000002, cnonce="645e44fb23e7b308"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51e739e4a82092d801b3113d90d91dc36f12a7f7015612f6884c329ba90d8919bfea89029e613d2374",charset=utf-8,realm="testrealm",stale=true
Date: Thu, 07 Jul 2022 16:43:17 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51e739e4a82092d801b3113d90d91dc36f12a7f7015612f6884c329ba90d8919bfea89029e613d2374", uri="/index.html", algorithm=MD5-sess, response="11c123d0af33f6bb4446f1abfdc89886", qop=auth, nc=00000001, cnonce="ec44bf5b3b04a5e6"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:43:17 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51e739e4a82092d801b3113d90d91dc36f12a7f7015612f6884c329ba90d8919bfea89029e613d2374", uri="/index.html", algorithm=MD5-sess, response="3ebac2aba77993730c198e7978578efc", qop=auth, nc=00000002, cnonce="72ca963ae4baf2bb"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e516d165fa92092d801bcaa5789b79e1ca0384ca9790a1d1c3a432e7c5822d164210b546ac2af0a7fa4",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:43:17 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e516d165fa92092d801bcaa5789b79e1ca0384ca9790a1d1c3a432e7c5822d164210b546ac2af0a7fa4", uri="/index.html", algorithm=MD5-sess, response="da6b49509c21cc2b895a73d334fd4928", qop=auth, nc=00000002, cnonce="a3f88463499821b1"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51e415faa92092d80165be9650b7aef657b3f4aa0dad8172384e793bb9d61b1f2fa2cf6a5f9652c03c",charset=utf-8,realm="testrealm",stale=true
Date: Thu, 07 Jul 2022 16:43:19 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51e415faa92092d80165be9650b7aef657b3f4aa0dad8172384e793bb9d61b1f2fa2cf6a5f9652c03c", uri="/index.html", algorithm=MD5-sess, response="3275ff200699095f1192443ae72f3466", qop=auth, nc=00000001, cnonce="7ab1bb59964e073b"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru,en-US;q=0.9,en-GB;q=0.8,en;q=0.7

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:43:19 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>

Firefox re-requests password each page reload (however there is no extra responses with stale=true, probably because I was fast with password entering). cnonce is different every time.

IIS <-> Firefox log
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51686853821c92d8010d9ff85cb3539bd45389516b4f1c76625b48b550fe38fb635bc0ec483a563a5d",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:13:35 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51686853821c92d8010d9ff85cb3539bd45389516b4f1c76625b48b550fe38fb635bc0ec483a563a5d", uri="/index.html", algorithm=MD5-sess, response="2dcf87d508a072710c83684f3ea599dd", qop="auth", nc=00000001, cnonce="7578dd9e10d94940"

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:13:36 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51686853821c92d8010d9ff85cb3539bd45389516b4f1c76625b48b550fe38fb635bc0ec483a563a5d", uri="/index.html", algorithm=MD5-sess, response="2249c9ce76ec2abf0fa5c8c1e0260fd7", qop=auth, nc=00000002, cnonce="744a7c11c60730eb"
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51899229841c92d80174f9fb4060ee51a9582f746075d89313dbc13cfffacb2a54e9c05f1e3c2421c5",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:13:38 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51899229841c92d80174f9fb4060ee51a9582f746075d89313dbc13cfffacb2a54e9c05f1e3c2421c5", uri="/index.html", algorithm=MD5-sess, response="f8cbf37481e066553f1edbf3e571815b", qop="auth", nc=00000001, cnonce="60579a61538ac9b7"

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:13:39 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51899229841c92d80174f9fb4060ee51a9582f746075d89313dbc13cfffacb2a54e9c05f1e3c2421c5", uri="/index.html", algorithm=MD5-sess, response="8a3fbe5c26c15235fd4286deadc723c8", qop=auth, nc=00000002, cnonce="5ac719aa591c6cbc"
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e514d4577851c92d801b55b0391e17831c51e2e91ca595aa812e2b5a2ee690ea0f3f9e9773a31defb80",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:13:40 GMT
Content-Length: 1293

[skipped error response body]
GET /index.html HTTP/1.1
Host: 10.5.5.5:8088
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e514d4577851c92d801b55b0391e17831c51e2e91ca595aa812e2b5a2ee690ea0f3f9e9773a31defb80", uri="/index.html", algorithm=MD5-sess, response="3bbcd1e65a27d29b7228cb2d58c1ddf5", qop="auth", nc=00000001, cnonce="81504915601161a3"

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:59:41 GMT
Accept-Ranges: bytes
ETag: "46caa9cd992d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:13:41 GMT
Content-Length: 120

<html>
<head>
<title>Empty doc</title>
<link rel="icon" href="data:,">
</head>
<body>Nothing here</body>
</html>

I've tested libcurl (with the new libauthrepeat tool from PR #9094), no Windows SSPI, unpatched (static cnonce).
Works perfect, not restarted authorisations.

IIS <-> libcurl (unpatched, static 'cnonce') log
GET /0100 HTTP/1.1
Host: 10.5.5.5:8088
Accept: */*

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 1293

[skipped error response body]
GET /0100 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0100", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000001, qop=auth, response="d9a3e817b88fcbf9726cbb066b3b8961", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:32 GMT
Accept-Ranges: bytes
ETag: "20b8592692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 83

<html><head><title>Empty doc</title></head><body>Nothing here - 100</body></html>
GET /0200 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0200", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000002, qop=auth, response="b8184d2e1e56a9af8115f663972b1944", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:43 GMT
Accept-Ranges: bytes
ETag: "a3ab9998692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 83

<html><head><title>Empty doc</title></head><body>Nothing here - 200</body></html>
GET /0300 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0300", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000003, qop=auth, response="1f9541a13033e7e21dd1878fca2438e3", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:52 GMT
Accept-Ranges: bytes
ETag: "2e4f309e692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0300</body></html>
GET /0310 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0310", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000004, qop=auth, response="898ed0d9dd5aab4d4b3ad94008885089", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:01 GMT
Accept-Ranges: bytes
ETag: "b15864a3692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0310</body></html>
GET /0400 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0400", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000005, qop=auth, response="b689521235b287fbd1480e8d946c533a", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:08 GMT
Accept-Ranges: bytes
ETag: "391b1a7692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0400</body></html>
GET /0410 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e51fdb9fb071992d8019dd2ce837f939848824f64936d6caf85ae79f7d4eec26d0e8ec4e09bc843c7d3", uri="/0410", cnonce="Y2YzMTJjZWIyNjQwYzI0Yjk4ZWUxZmZjOWZkMjQzNzk=", nc=00000006, qop=auth, response="6b17e538f0ac48e8843192f33efbf1b0", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:18 GMT
Accept-Ranges: bytes
ETag: "29f62cad692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 15:48:41 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0410</body></html>

And finally I've tested patched libcurl (new cnonce every time, H(A1) is calculated for the first cnonce, then calculated value H(A1) value is reused for the next requests).
Works perfect as well, no auth restarts.

IIS <-> libcurl (patched, variable 'cnonce') log
GET /0100 HTTP/1.1
Host: 10.5.5.5:8088
Accept: */*

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e",charset=utf-8,realm="testrealm"
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 1293

[skipped error response body]
GET /0100 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0100", cnonce="MzU0ZjFjZGFjNmQ5NmMyYTcwMzVhNTMzYTFiZjI1MmI=", nc=00000001, qop=auth, response="931b98cab3647fe5d141b306cfc1431f", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:32 GMT
Accept-Ranges: bytes
ETag: "20b8592692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 83

<html><head><title>Empty doc</title></head><body>Nothing here - 100</body></html>
GET /0200 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0200", cnonce="Y2UwMTQ1ZTcwMjZkOTE0MTgxNzM5ZTkzNGVlZGRhNzQ=", nc=00000002, qop=auth, response="22d3e1c38aa7157432eb7badc19c2aca", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:43 GMT
Accept-Ranges: bytes
ETag: "a3ab9998692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 83

<html><head><title>Empty doc</title></head><body>Nothing here - 200</body></html>
GET /0300 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0300", cnonce="NTQwYmE3NzZhMjU1YTY5YjQ4ZWIwNjlkYTlkYTY5Y2M=", nc=00000003, qop=auth, response="2c9f28ff2f08f859ed07f4703fa623e4", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:36:52 GMT
Accept-Ranges: bytes
ETag: "2e4f309e692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0300</body></html>
GET /0310 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0310", cnonce="M2QwYjhlYjI0OTMyODljN2FlNTIyNmRlMjNkYTM1MTQ=", nc=00000004, qop=auth, response="6b0e4b2bc991b0921307da0c1489d31e", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:01 GMT
Accept-Ranges: bytes
ETag: "b15864a3692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0310</body></html>
GET /0400 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0400", cnonce="OTQwZDkxNmFjMTRhNGE5NDA5MjBlZTY5MGIyMmE0NjA=", nc=00000005, qop=auth, response="f7762d22e552f4859c56bbbf14d31c7d", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:08 GMT
Accept-Ranges: bytes
ETag: "391b1a7692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0400</body></html>
GET /0410 HTTP/1.1
Host: 10.5.5.5:8088
Authorization: Digest username="testuser", realm="testrealm", nonce="+Upgraded+v18bdb7daaaf885e3a7b1e39039d976e512ff3246a1b92d80155c15e4fbd5870ccb7ed388c9f44648e00a2a0b48703914b45344f6ad5d5544e", uri="/0410", cnonce="OGRmZjgxNzQ0ZDllYWJmM2JiOTAwYzViZjc3NGY2ZDY=", nc=00000006, qop=auth, response="a3aa24ccc0fb96a75366ba9a7b038986", algorithm=MD5-sess
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 07 Jul 2022 13:37:18 GMT
Accept-Ranges: bytes
ETag: "29f62cad692d81:0"
Server: Microsoft-IIS/10.0
Date: Thu, 07 Jul 2022 16:05:45 GMT
Content-Length: 84

<html><head><title>Empty doc</title></head><body>Nothing here - 0410</body></html>

Probably stale=true were produced because I was too slow with entering the password, so stale might be not relevant.

Summary: seems that IIS implemented correctly MD5-sess (to my surprise). libcurl works fine with IIS (seems to be the only one Web server implementation from TOP 6, which supports session algorithm). libcurl with variable cnonce (and the same H(A1) based on the first cnonce) works fine as well.

Full logs are available. I can upload them somewhere if needed.

@Karlson2k
Copy link
Contributor Author

Karlson2k commented Jul 7, 2022

Obviously, Chrome/Chromium implemented MD5-sess incorrectly.
https://github.com/chromium/chromium/blob/0e821d933d43801777d733f71aa4f6b814456d4d/net/http/http_auth_handler_digest.cc#L337-L350
The same cnonce is used for H(A1) (for MD5-sess) and for response as well. It is not the first cnonce and it is not remembered as class member, so every time when the new cnonce is used, the new H(A1) is generated, even for session algorithm, which is not correct.
The cnonce is generated each time when client Digest headers are produced. https://github.com/chromium/chromium/blob/0e821d933d43801777d733f71aa4f6b814456d4d/net/http/http_auth_handler_digest.cc#L121-L138


Firefox seems to have the same bug (or, most probably, the bug from Firefox was copied to Chrome).
H(A1) is generated each time when cnonce is generated (and new cnonce value is used for A1). https://github.com/mozilla/gecko-dev/blob/54571ce8b0e25ad9be9feb6587a775454616c8ac/netwerk/protocol/http/nsHttpDigestAuth.cpp#L323-L336
The calculated H(A1) is not stored anywhere. https://github.com/mozilla/gecko-dev/blob/54571ce8b0e25ad9be9feb6587a775454616c8ac/netwerk/protocol/http/nsHttpDigestAuth.cpp#L492-L534


Both major browsers have the same bug.
libcurl works fine, but only because it uses the same cnonce for all requests.

The patch brings the best combination: variable cnonce for better security, full compliance with RFCs and correct support for the only popular Web server with Digest session support.

@Karlson2k Karlson2k force-pushed the digest_auth_fix_02 branch 2 times, most recently from 9350ff0 to 6a3a97f Compare July 9, 2022 11:03
@Karlson2k Karlson2k marked this pull request as ready for review July 9, 2022 11:03
@Karlson2k Karlson2k changed the title Fixes related to digest authorisation [WIP] Fixes related to digest authorisation Jul 9, 2022
@Karlson2k
Copy link
Contributor Author

To simplify review, I've moved some commits to other PRs and removed from this one.

@Karlson2k
Copy link
Contributor Author

Rebased

@Karlson2k
Copy link
Contributor Author

Karlson2k commented Jul 15, 2022

Rebased again.

I opened partially related Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1344488
Please note the issues in Chrome/Chromium and libcurl are a bit different: both do not re-use A1 (and H(A1)) calculation, but libcurl uses static cnonce so resulting response value is valid, while Chromium generates cnonce each time (which is right) and recalculates A1 every time with the new cnonce value (which is wrong), so resulting response value is incorrect and always rejected by IIS if it is not the first request (with the first cnonce) after a new server nonce.

RFC 7616 (and 2617) requires to use the first nonce and cnonce values
for A1 calculation until the next Digest challenge received.
@jay
Copy link
Member

jay commented Oct 3, 2022

I've rebased your branch on upstream/master. Regarding the last commit I don't understand what it fixes. Can you please add a commit message that explains why it is necessary? I don't see the advantage to changing it, and I don't see that the RFC requires it. Could there be a compatibility issue from doing this?

digest: generate new random cnonce every time when cnonce is used

@Karlson2k
Copy link
Contributor Author

Karlson2k commented Oct 3, 2022

The last commit adds cnonce variation for every request.

Unfortunately seems that RFC assumes that cnonce will be read as client nonce and skipped detailed cnonce value description (assuming that basic nonce ideas will be applied, but on the client side).

From https://datatracker.ietf.org/doc/html/rfc7616#section-3.3:
nonce A server-specified string which should be uniquely generated each time .... response is made. It is advised that this string be Base64 or hexadecimal data. Specifically, since the string is passed in the header field lines as a quoted string, the double-quote character is not allowed, unless suitably escaped.
followed with more details on how nonce could be generated.

From https://datatracker.ietf.org/doc/html/rfc7616#section-3.4:
The cnonce value is an opaque quoted ASCII-only string value provided by the client... No more details on the format or on the generation. Looks like it was assumed that nonce basic ideas used here as well.
However, as nonce could be used in several requests, RFC defines nc and other rules that limit number of nonce re-uses, but nothing defines limit of cnonce re-use, most probably it was never assumed that cnonce will re-used ever.

Man-In-The-Middle may collect many requests with the same cnonce just by sniffing. Man-In-The-Middle with alternation of the traffic may collect unlimited number of requests with the same cnonce which may help password guessing. To emphasize: there are several mechanisms preventing too large nonce re-use and no mechanisms to limit cnonce re-use, while both nonce and cnonce served the same idea: add randomness/uniqueness to avoid password guessing. I think this is because it was never assumed that cnonce could be used more than one time.

In contract, RFC defines how and when nonce could be re-used, but cnonce re-use is not mentioned anywhere.

Similar discussion was summirised nicely at the FireFox bug description: https://bugzilla.mozilla.org/show_bug.cgi?id=270447#c10

cnonce is supposed to be unique for every request. Reuse of
cnonce makes password protection weaker.
@Karlson2k
Copy link
Contributor Author

Karlson2k commented Oct 3, 2022

Could there be a compatibility issue from doing this?

Currently (without this PR merged) libcurl requests with <algo>-session accepted by servers only because H(A1) stays the same (server nonce is updated only with the new challenge, cnonce is re-used), therefore despite curl does not follow RFC (H(A1) is re-calculated each time) server accepts generated response.

With cnonce variation without remembering the first H(A1), curl would succeed only for the first request and would fail for every request which uses nonce not for the first time for <algo>-session (where H(A1) must be calculated with the first cnonce after the new nonce).

As this PR fixes H(A1) generation for <algo>-session, there is no need to keep weaker static cnonce.

Every algorithm which uses cnonce also uses nc value. Server-side software must recalculate response based on client-provided nc and cnonce, and nc clearly cannot be re-used, therefore server must always use values from the current request. I can hardly imagine any reason for server to remember previously used cnonce values (besides rejecting of already used cnonce as an additional prevention of replay attack).

In short: any valid implementation of HTTP Digest Auth must work just fine with variated cnonce.

@Karlson2k
Copy link
Contributor Author

@dfandrich
Copy link
Contributor

dfandrich commented Oct 11, 2022 via email

@Karlson2k
Copy link
Contributor Author

If it's harder to sniff with this patch, then that makes it easier to sniff now, in the current code. The question is, how easy?

Yes, it is not simple, but the idea of nonce is to add huge difference to the source data. When cnonce is static there is no huge difference.
The only secret component is the password. The rest is visible in clear text (username, URI, realm, nonce, cnonce, nc etc.)
Repeating requests may differ only in nc. Data analysis is definitely easer when having many values generated with known very small differences in original data.
Any possible practical use is a separate topic and discussable However, it is clear that static cnonce gives weaker protection than variable cnonce (and weaker compared to how it was designed by the RFC).

@bagder
Copy link
Member

bagder commented Aug 6, 2023

Similar to the other Digest PR: Can we start with the problem situation so that we all can understand what the change is needed for? Then we get a common base and can discuss what alternatives we have to fix the problem. This PR immediately got deep into the weeds and I fear we miss details then.

@bagder bagder closed this Aug 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

4 participants