mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
A stolen attestation (within its 2-minute validity window) could be replayed against a different WebAuthn enrollment because the envelope only attested (userID, score, timestamp) — it said nothing about WHICH public key the user was actually enrolling. Fix: extend LivenessAttestation with two optional binding fields: credentialHash = sha256(publicKey) [base64-std] challengeId = the WebAuthn challenge being enrolled [base64url] handleBiometricEnroll computes both values from the real enrollment data (consumed challenge bytes + submitted public key) and passes them as ExpectedCredentialHash / ExpectedChallengeID to VerifyLiveness. Mismatches are always fatal. A missing binding is fatal in STRICT mode (the default) and warns-and-accepts in LAX. BindingMode is server-configured via MPC_LIVENESS_BINDING=strict|lax. Production mainnet runs STRICT — must ship the extended envelope as a deployment blocker. LAX exists solely for the rollout window; every permissive accept emits an `mpc.biometric.binding_warn` log line so operators can see rollout progress. Tests added in pkg/webauthn/liveness_test.go: - R38_AcceptsMatchingCredentialHash - R38_RejectsMismatchedCredentialHash - R38_RejectsMissingBindingStrict - R38_LaxWarnsOnMissing - R38_AcceptsMatchingChallengeID - R38_RejectsMismatchedChallengeID Full ./pkg/api/ suite green; the existing pre-R3-8 LAX-style liveness tests still pass (they supply no Expected* values → the binding path is a no-op for them).