Skip to content

NA-0670: constant-time bearer-token comparison in the relay (audit C-2) - #64

Merged
Tebbens4832 merged 1 commit into
mainfrom
na0670-c2-constant-time-bearer
Jul 23, 2026
Merged

NA-0670: constant-time bearer-token comparison in the relay (audit C-2)#64
Tebbens4832 merged 1 commit into
mainfrom
na0670-c2-constant-time-bearer

Conversation

@Tebbens4832

Copy link
Copy Markdown
Member

What

Fixes the last unfixed HIGH (C-2) from the 2026-07-22 independent audit — the only one outside the governance spine. One function: auth_ok in src/lib.rs no longer uses provided == token (str::eq, which short-circuits on the first differing byte — a remote timing oracle on the network-exposed shared bearer token). Both sides are reduced to a fixed 32-byte SHA-256 digest and folded with an XOR-accumulate loop over all 32 bytes in a new private ct_eq_secret helper — the same shape as the qsc client's hs_ct_eq_32 (ENG-0003) and consistent with this file's existing route_key_for, which already hashes the other secret (the route token). Hashing first normalizes both inputs to 32 bytes, so the fold does identical work for every input — this also closes the length leak.

The two non-secret return false guards (missing / malformed Authorization header) are retained: they branch on attacker-known request shape, not on the secret.

Not changed

  • No dependency changesha2 is already a direct dependency; subtle stays dev-only (rustls under the reqwest dev-dependency) and is deliberately not added to the production graph. Cargo.toml / Cargo.lock diff is empty.
  • No wire, API, protocol, schema, or env change. auth_ok remains the first statement of every gated handler, so reject-before-mutation is undisturbed.

Tests

Adds auth_enabled_wrong_token_same_length_401_no_mutation ("topsecreX" vs "topsecret", both 9 bytes). The pre-existing wrong-token test differs in length, so == rejected before comparing a byte and passed against the buggy code; the same-length case is the only behavioural test that exercises the fold. It proves the fold returns the right answer (reject + no mutation) — the constant-time property is structural and read-verified, not a measured claim.

Why this PR's green is evidence

Unlike the docs-only satellite/governance PRs around this lane — whose green proves nothing because the required rust check has no src/ to exercise — this PR changes src/lib.rs, so the required rust check compiles and runs the test suite, including the new same-length reject. A green here is real evidence for the behavioural half of acceptance. (The constant-time property itself remains a structural claim verified by reading, per the directive; no timing measurement is claimed.)

Governance

qsl-protocol NA-0670 / QSL-DIR-2026-07-23-606 (D606) / D-1297. Records qsl-server Decision D-0014.

🤖 Generated with Claude Code

Replace `provided == token` (str::eq, which short-circuits on the first
differing byte -- a remote timing oracle on the network-exposed shared
bearer token) with a new private `ct_eq_secret` helper that reduces both
sides to a fixed 32-byte SHA-256 digest and folds them with an
XOR-accumulate loop over all 32 bytes. This is the same shape as the qsc
client handshake's hs_ct_eq_32 (ENG-0003) and consistent with this file's
existing route_key_for, which already hashes the route token. Hashing
first normalizes both inputs to 32 bytes, so the fold does identical work
for every input, closing the length leak as well. The two non-secret
`return false` guards (missing/malformed Authorization header) are kept --
they branch on attacker-known request shape, not on the secret.

No new dependency: sha2 is already a direct dependency; subtle stays
dev-only (rustls under the reqwest dev-dependency) and is not added to the
production graph. No wire, API, protocol, schema, or env change.

Add auth_enabled_wrong_token_same_length_401_no_mutation ("topsecreX" vs
"topsecret", both 9 bytes): the pre-existing wrong-token test differs in
length, so `==` rejected before comparing a byte and passed against the
buggy code; the same-length case is the only behavioural test exercising
the fold.

Fixes the last unfixed HIGH (C-2) from the 2026-07-22 independent audit.
Governance: qsl-protocol NA-0670 / D606 / D-1297. Records D-0014.
@Tebbens4832
Tebbens4832 force-pushed the na0670-c2-constant-time-bearer branch from 88cf678 to 82df531 Compare July 23, 2026 22:07
@Tebbens4832
Tebbens4832 merged commit 5235c2b into main Jul 23, 2026
1 check passed
@Tebbens4832
Tebbens4832 deleted the na0670-c2-constant-time-bearer branch July 23, 2026 22:19
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.

1 participant