Use constant-time comparison for token and credential verification#506
Use constant-time comparison for token and credential verification#506
Conversation
Replace standard == comparisons with subtle::ConstantTimeEq in the three places that verify secrets: tstoken signature in proxy.rs, clear-URL signature in http_util.rs, and Basic Auth credentials in auth.rs. The auth fix also removes the && short-circuit that created a username- existence oracle — both username and password are now always evaluated using bitwise & on subtle::Choice values. Adds log::warn on auth failure (path only, no credentials) and five targeted tests covering tampered tokens, wrong-username-right-password, and empty tokens. Closes #410
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Verdict: Clean, well-focused security hardening PR. Ready to merge with minor improvements.
Excellent anti-oracle design in auth.rs — bitwise & on subtle::Choice instead of && prevents username-existence oracle. Minimal, focused changes — only touches the 3 comparison sites + targeted tests. Good test coverage (5 new tests). subtle v2 is the right dependency choice.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Approved — ready to merge
This PR correctly hardens the three token/credential comparison sites with constant-time operations from the subtle crate. The changes are minimal, focused, and well-documented.
Highlights
- Excellent anti-oracle design in
auth.rs: using bitwise&onsubtle::Choiceinstead of&&prevents username-existence oracles. The inline comment explaining why is exactly what future maintainers need. - Well-chosen tests covering tampered tokens (same length, wrong bytes), wrong-username/right-password, correct-username/wrong-password, and empty tokens.
- Good doc comments explaining security invariants on
enforce_basic_auth,verify_clear_url_signature, and inline inproxy.rs. log::warn!on auth failure logs path only (no credentials) — correct observability without leaking secrets.- Minimal, focused changes — only the 3 comparison sites plus targeted tests. No unnecessary refactoring.
- All CI checks pass (fmt, test, clippy, vitest, CodeQL).
Minor note (not actionable for this PR)
extract_credentialsinauth.rs(lines 49-73) has pre-existing early returns that leak timing about credential format (missing header, wrong scheme, malformed base64). This is not introduced by this PR and is fine — the CT comparison correctly protects credential values, not header format parsing.
aram356
left a comment
There was a problem hiding this comment.
Summary
This PR replaces == comparisons with subtle::ConstantTimeEq in all three secret-verification sites (tstoken, clear-URL signature, Basic Auth), fixing timing side-channel vulnerabilities. The bitwise & in auth to eliminate the username-existence oracle is textbook. One structural issue to address before merge.
Blocking
🔧 wrench
Redacted::PartialEqre-introduces the timing oracle this PR fixes:PartialEq for Redacted<T>(inredacted.rs:69-72, not in this PR's diff but directly relevant) delegates toT::PartialEq, which forStringis a short-circuiting byte comparison. This is the type wrappingproxy_secret,username, andpassword. While the call sites in this PR correctly useexpose()+ct_eq(), thePartialEqimpl creates an attractive nuisance — a future developer comparingRedactedvalues directly would unknowingly bypass constant-time. Consider removing thePartialEqimpls or implementing viact_eqin a follow-up.
❓ question
reconstruct_and_validate_signed_targetreturns 502 for invalid tstoken (proxy.rs:1084): A tampered or forged tstoken is a client error, not a gateway error. Should this be 400 Bad Request or 403 Forbidden instead of 502 Bad Gateway?
Non-blocking
♻️ refactor
- Duplicated constant-time comparison pattern: The
len == len && bool::from(ct_eq)pattern appears inauth.rs:39,http_util.rs:331, andproxy.rs:1082. Extract act_str_eq(a, b) -> boolhelper to prevent future copies from forgetting the length check orbool::fromconversion. (auth.rs:39)
🌱 seedling
sign_clear_urlis SHA-256(prefix || secret || message), not HMAC (http_util.rs:320): The doc comment acknowledges this. The construction is safe here because URLs are validated against the signature (not extended), butSHA-256(prefix || secret || msg)is vulnerable to length-extension if reused elsewhere. Consider migrating tohmac::Hmac<Sha256>in a future PR —sha2is already a dependency.
⛏ nitpick
- PR body checklist says "Uses
tracingmacros (notprintln!)" but the codebase useslog, nottracing— minor template inconsistency.
CI Status
- cargo fmt: PASS
- cargo clippy: PASS
- cargo test: PASS
- vitest: PASS
- integration tests: PASS
- browser integration tests: PASS
- CodeQL: PASS
Summary
==comparisons withsubtle::ConstantTimeEqin all three secret-verification sites (tstoken, clear-URL signature, Basic Auth), eliminating timing side-channel attacks&&allowed an attacker to distinguish wrong-username from wrong-password via timing — replaced with bitwise&onsubtle::Choiceso both fields are always evaluatedlog::warn!on auth failure (path only, no credentials) and five new targeted tests covering each fixChanges
Cargo.tomlsubtle = "2"to workspace dependenciescrates/common/Cargo.tomlsubtleas direct dependencycrates/common/src/auth.rs&instead of&&;log::warn!on failure; 2 new testscrates/common/src/http_util.rsverify_clear_url_signature; 2 new testscrates/common/src/proxy.rsreconstruct_and_validate_signed_target; 1 new testCloses
Closes #410
Test plan
cargo test --workspacecargo clippy --all-targets --all-features -- -D warningscargo fmt --all -- --checkcd crates/js/lib && npx vitest runcd crates/js/lib && npm run formatcd docs && npm run formatcargo build --bin trusted-server-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)