Skip to content

fix(server): accept $2x$ in hash check and equalize unusable-hash timing - #357

Merged
inureyes merged 2 commits into
mainfrom
fix/issue-356-hash-warning-timing
Nov 9, 2026
Merged

inureyes merged 2 commits into
mainfrom
fix/issue-356-hash-warning-timing

Conversation

@inureyes

@inureyes inureyes commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #354 / #355. Users with a $2x$ bcrypt hash can log in but triggered the load-time "cannot log in" warning, and users with an unusable stored hash returned faster than users with a real hash, which the 100 ms floor in verify only hides on fast hosts. Login outcomes are unchanged for every input; verify_internal's $2 dispatch is untouched.

  • is_supported_hash_format, the load-time warning text and the UserDefinition::password_hash rustdoc now list $2x$.
  • verify_dummy_hash now runs, matching the invalid-username and unknown-user branches, for: unsupported-format hashes (*, !, empty, $6$), malformed Argon2 hashes (the original Err is still returned, so verify_password still logs it and rejects), and malformed bcrypt hashes (bad version, truncated).
  • Argon2 PHC strings that parse but lack a salt or hash output (for example $argon2id or $argon2id$v=19$m=19456,t=2,p=1) are now treated as malformed: is_supported_hash_format rejects them, so they get the load-time warning, and verify_argon2 returns the malformed-hash error, so they take the dummy-hash path. Before, argon2 reported them as a wrong password without hashing anything.
  • A #[cfg(test)] call counter on PasswordVerifier lets tests assert exactly one dummy hash per unusable-hash or unknown-user attempt and none for valid users, with no wall-clock assertions. It is absent from non-test builds.

Changes during review

The incomplete-Argon2 case was found by the security review and folded in here since it is the same unusable-hash timing gap #356 targets. Both tests covering it fail with the check removed.

Verification

cargo fmt --all -- --check, cargo clippy --all-targets --all-features -- -D warnings and cargo clippy --lib -- -D warnings are clean. cargo test --no-fail-fast passes every target except the four tests/integration_test.rs tests, which fail identically on clean main locally (localhost SSH sandbox) and are left to CI.

Closes #356

…le hashes

reload_users warned that users with a $2x$ bcrypt hash cannot log in, although verify_internal dispatches every $2 hash to bcrypt, which accepts version 2x. Add $2x$ to is_supported_hash_format, the warning text, and the UserDefinition::password_hash docs. Dispatch is unchanged.

Users whose stored hash is unusable (unsupported prefix, malformed Argon2, malformed bcrypt) returned without any hash work, unlike unknown users. Call verify_dummy_hash on those three paths so they cost the same as other failures; the malformed Argon2 case still returns the same Err and the auth provider still rejects.

A cfg(test) counter on verify_dummy_hash lets tests assert this deterministically instead of by wall-clock time.

Tests: cargo test --lib server::auth::password, cargo clippy --lib --tests and --lib with -D warnings, cargo fmt --check.

Closes #356
@inureyes inureyes added status:review Under review type:bug Something isn't working priority:low Low priority issue labels Nov 9, 2026
An Argon2 PHC string such as `$argon2id` or one missing the salt or hash output parses, so it passed the load-time format check with no warning, and argon2 reports PasswordInvalid for it without hashing, so it also skipped the dummy hash added for unusable hashes. Reject such hashes in `is_supported_hash_format` and return the malformed-hash error from `verify_argon2`, which routes them through the dummy-hash path. Login outcome is unchanged: these users still cannot authenticate.

Found by the security review of this PR. Tests cover the three incomplete forms in the format check and the dummy-hash counter test, and both fail without the fix. Also rename the bad-version bcrypt fixture variable to match what it holds.

Refs #356
@inureyes inureyes added status:done Completed and removed status:review Under review labels Nov 9, 2026
@inureyes
inureyes merged commit f0d3cd8 into main Nov 9, 2026
5 checks passed
@inureyes
inureyes deleted the fix/issue-356-hash-warning-timing branch November 9, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:low Low priority issue status:done Completed type:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(server): accept $2x$ in load-time hash check and equalize timing for unusable password hashes

1 participant