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
inureyes added a commit that referenced this pull request Nov 10, 2026
Bump the crate, lockfile, and manpages to 3.1.2 and add release entries to README, CHANGELOG, and debian/changelog covering #355 and #357: the load-time warning for unusable server password hashes, the dummy-hash timing fix for unusable and malformed stored hashes, and the hash format documentation.
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