Fix F-07: pre-compute real Argon2 dummy hash via sync.Once

- auth/auth.go: add DummyHash() which uses sync.Once to compute
  HashPassword("dummy-password-for-timing-only", DefaultArgonParams())
  on first call; subsequent calls return the cached PHC string;
  add sync to imports
- auth/auth_test.go: TestDummyHashIsValidPHC verifies the hash
  parses and verifies correctly; TestDummyHashIsCached verifies
  sync.Once behaviour; TestDummyHashMatchesDefaultParams verifies
  embedded m/t/p match DefaultArgonParams()
- server/server.go, grpcserver/auth.go, ui/ui.go: replace five
  hardcoded PHC strings with auth.DummyHash() calls
- AUDIT.md: mark F-07 as fixed
Security: the previous hardcoded hash used a 6-byte salt and
  6-byte output ("testsalt"/"testhash" in base64), which Argon2id
  verifies faster than a real 16-byte-salt / 32-byte-output hash.
  This timing gap was measurable and could aid user enumeration.
  auth.DummyHash() uses identical parameters and full-length salt
  and output, so dummy verification timing matches real timing
  exactly, regardless of future parameter changes.
This commit is contained in:
2026-03-11 20:37:27 -07:00
parent 005e734842
commit 6e690c4435
6 changed files with 91 additions and 11 deletions

View File

@@ -27,6 +27,7 @@ import (
"sync"
"time"
"git.wntrmute.dev/kyle/mcias/internal/auth"
"git.wntrmute.dev/kyle/mcias/internal/config"
"git.wntrmute.dev/kyle/mcias/internal/db"
"git.wntrmute.dev/kyle/mcias/internal/model"
@@ -96,13 +97,11 @@ func (u *UIServer) consumeTOTPNonce(nonce string) (int64, bool) {
return pl.accountID, true
}
// dummyHash returns a hardcoded Argon2id PHC string used for constant-time
// dummy password verification when the account is unknown or inactive.
// Security: the dummy hash uses OWASP-recommended parameters (m=65536,t=3,p=4)
// to match real verification timing. F-07 will replace this with a
// sync.Once-computed real hash for exact parameter parity.
// dummyHash returns the pre-computed Argon2id PHC hash for constant-time dummy
// verification when an account is unknown or inactive (F-07).
// Delegates to auth.DummyHash() which uses sync.Once for one-time computation.
func (u *UIServer) dummyHash() string {
return "$argon2id$v=19$m=65536,t=3,p=4$dGVzdHNhbHQ$dGVzdGhhc2g"
return auth.DummyHash()
}
// New constructs a UIServer, parses all templates, and returns it.