Skip to content

dialog: type digest auth failures apart from the response - #337

Open
Mliviu79 wants to merge 2 commits into
emiago:mainfrom
Mliviu79:pr/auth-sentinels
Open

dialog: type digest auth failures apart from the response#337
Mliviu79 wants to merge 2 commits into
emiago:mainfrom
Mliviu79:pr/auth-sentinels

Conversation

@Mliviu79

@Mliviu79 Mliviu79 commented Jul 19, 2026

Copy link
Copy Markdown

Fixes #336

Stacked on #330, which introduces the retry cap. Please take that first — the diff here is only the sentinels on top.

When a digest challenge cannot be answered, WaitAnswer falls through to the final response and returns ErrDialogResponse carrying the 401 or 407. That is byte-identical to what a caller gets when the peer simply rejects the route, so a blank local password and an exhausted retry cap are indistinguishable from a remote refusal.

Two sentinels separate them:

  • ErrAuthMissingCreds — a real challenge arrived and no password is configured
  • ErrAuthMaxRetry — the peer kept challenging past the cap

A genuine peer rejection still returns ErrDialogResponse, unchanged.

Four subtests added alongside the existing ones, covering both sentinels on the 401 and 407 arms.

Mliviu79 added 2 commits July 19, 2026 10:28
WaitAnswer guarded the digest retry on the absence of an Authorization header,
so after the first attempt a second 401 was never treated as a challenge. That
guard was also doing loop-termination duty, which is why it could not answer a
re-challenge.

A stale=true re-challenge (RFC 2617 3.2.1, adopted for SIP by RFC 3261 22.4)
asks the UAC to retry with the new nonce and the same credentials, so a
registrar rotating nonces failed the INVITE on a correct password.

Attempts are counted instead, capped at two and shared by the 401 and 407 arms
so a 407 followed by a 401 consumes both. Replacing credentials is safe because
digestAuthApply and digestProxyAuthApply remove the existing authorization
header before appending.

A 401/407 is now treated as a challenge only when it carries the matching
authenticate header, so a plain rejection reaches the caller as its own status
instead of a digest parse error. Exhausting the cap returns the final response
as the ErrDialogResponse it already returned, so no new API.

Refs emiago#329
A challenge that could not be answered was folded into the password and
attempt checks on the challenge condition itself, so it fell through to the
final response and reached the caller as a plain 401/407 ErrDialogResponse.
That is indistinguishable from a peer refusing the route, though the two want
opposite handling: a blank password is our own misconfiguration, and a peer
still challenging past the attempt cap will not accept the credentials at all.
Return ErrAuthMissingCreds and ErrAuthMaxRetry on those paths instead, keeping
the authenticate header as the only test for whether a response is a challenge
so a plain rejection still arrives with its real status.

Pre-existing gofmt findings in dialog_client.go and sip/request.go, and the
racy TestTransportLayerClientConnectionReuse and ...NoReuse in sip, are
unrelated to this change and unchanged by it.
Refs emiago#336
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sip: a blank password and an exhausted retry cap are indistinguishable from a peer rejection

1 participant