sync: three defects in the vendored Digest handler
The Basic arm returned out of the challenge loop as soon as it found a Basic challenge it had already tried. The handler is a network interceptor, so Basic is always primed preemptively over HTTPS — the abort therefore fired on the first 401, and a Digest challenge later in the same header was never read. A Baikal or Apache front end offering both and rejecting Basic at the app layer got no Digest answer at all, and the account was marked as needing sign-in for good. Every challenge is read before anything is decided now; giving up still happens, just after the whole header has been looked at. clientNonce and nonceCount sat on the companion object while SyncEngine builds one handler per account, so two accounts syncing at once interleaved their nc values. They are instance state now, and a new server nonce restarts the count — RFC 7616 3.4.1 counts requests sent with that nonce, starting at 1, and Apache's AuthDigestNcCheck answers 401 for a carried-over count. qop values were split on "," and compared untrimmed, so qop="auth, auth-int" quietly downgraded to auth and qop=" auth" matched nothing — falling into the RFC 2069 branch, which emits no qop, nc or cnonce and which an RFC 7616 server rejects outright. Documented as changes 10-12 in PROVENANCE. The four static assignments in upstream's digest tests now address the handler; nothing else in that file changes.
This commit is contained in:
@@ -247,3 +247,46 @@ cleartext cases it used to cover are pinned explicitly by
|
||||
`cleartextBasicIsSentWhenExplicitlyAllowed` and `cleartextDigestIsStillAnswered`.
|
||||
This is the one upstream test this port deliberately changes rather than
|
||||
inherits.
|
||||
|
||||
## Change 10 — a Basic challenge no longer hides the Digest one beside it
|
||||
|
||||
The 401 branch scanned `response.challenges()` in a loop and did a non-local
|
||||
`return null` the moment it saw a `Basic` challenge it had already tried. Because
|
||||
the handler is installed as a **network interceptor**, `basicAuth` is always
|
||||
primed preemptively over HTTPS — so that fired on the *first* 401, and a
|
||||
`Digest` challenge later in the same header was never read at all.
|
||||
|
||||
A Baïkal or Apache front end that advertises both and rejects Basic at the
|
||||
application layer therefore never got a Digest answer, and `SyncEngine` marked
|
||||
the account as needing sign-in permanently.
|
||||
|
||||
The loop now reads every challenge before anything is decided; a scheme already
|
||||
known not to work is simply not re-offered. Giving up is still the outcome when
|
||||
nothing usable is left — it happens after the whole header has been looked at
|
||||
rather than in the middle of it.
|
||||
|
||||
## Change 11 — the digest counter is per handler, and per nonce
|
||||
|
||||
`clientNonce` and `nonceCount` lived on the companion object while `SyncEngine`
|
||||
builds **one handler per account**, so two accounts syncing at once interleaved
|
||||
their `nc` values against each other's nonces. They are instance state now.
|
||||
|
||||
The count was also never reset. RFC 7616 §3.4.1 defines `nc` as the count of
|
||||
requests sent *with that nonce*, starting at 1, so a server that enforces it
|
||||
(Apache `AuthDigestNcCheck On`, several NAS stacks) answers 401 for a rotated
|
||||
nonce that arrives with a carried-over count. A new server nonce now restarts
|
||||
it. The client nonce stays put: it is ours, one per handler, and pairing it with
|
||||
a restarted count is what the RFC describes.
|
||||
|
||||
The tell upstream left behind is that every digest test has to reset the counter
|
||||
by hand — those four assignments now address the handler rather than the class,
|
||||
which is the only change to that file.
|
||||
|
||||
## Change 12 — `qop` list values are trimmed
|
||||
|
||||
`paramValue.split(",")` with no `trim()`. HTTP list syntax allows space around
|
||||
the separator, so `qop="auth, auth-int"` silently downgraded to `auth`, and a
|
||||
single spaced value (`qop=" auth"`) matched nothing at all — dropping into the
|
||||
RFC 2069 legacy branch, which emits a response with no `qop`, `nc` or `cnonce`.
|
||||
An RFC 7616 server rejects that outright: a permanent 401 against a server
|
||||
behaving perfectly legally. Values are trimmed and compared case-insensitively.
|
||||
|
||||
Reference in New Issue
Block a user