webauthn: lock the challenge maps and take a challenge once (V-581)
The RP kept its two in-flight challenge maps bare, and mavweb serves the four passkey endpoints from HTTP handlers. Two browsers beginning a challenge at once were a concurrent map write, which is a fatal runtime error rather than a recovered panic, so it takes the daemon down. The endpoint that reaches it answers before any credential is proven. Every read and write of regs and asserts is now under a mutex. Lookup and delete moved into takeReg and takeAssert so they happen under one hold, which is what makes a challenge single-use: separately, two replays of the same response both found it before either deleted it. The challenge in clientDataJSON is compared in constant time. It is the one secret in that blob, 32 bytes of crypto/rand the browser has to echo back, and a byte-at-a-time compare is the shape that leaks a guessed prefix. Also corrected the comment over ipc.codeOf, which claimed an unmatched error keeps its text server-side. rpcErr ships that text deliberately, and on a tcp seam it leaves the box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -131,9 +131,15 @@ const (
|
||||
codeInternal = "internal"
|
||||
)
|
||||
|
||||
// codeOf maps a server-side sentinel to its wire code. Anything not matched
|
||||
// is codeInternal — we never leak internal Go error text to a module; it
|
||||
// gets a generic "internal" and the daemon logs the real error server-side.
|
||||
// codeOf maps a server-side sentinel to its wire code. Anything not matched is
|
||||
// codeInternal.
|
||||
//
|
||||
// This used to claim the text of an unmatched error stays server-side. It does
|
||||
// not: rpcErr below ships err.Error() for codeInternal and codeBadParams,
|
||||
// deliberately, because on those two codes the text is the whole diagnostic and
|
||||
// a module has no other way to see it. Worth knowing before putting a secret in
|
||||
// an error string, and worth knowing twice on a tcp seam, where that string
|
||||
// leaves the box.
|
||||
func codeOf(err error) string {
|
||||
switch {
|
||||
case err == nil:
|
||||
|
||||
Reference in New Issue
Block a user