fix(core/fido2): use a static counter when returning a bogus signature
What changed, and why it matters
This commit changes how the Trezor hardware wallet handles FIDO2/WebAuthn 'silent authentication' attempts. When a website asks for authentication without user confirmation (user presence=false), Trezor now returns a fake signature and a fixed counter value of 0, instead of advancing the real signature counter. Previously, the code advanced the real counter even when returning a fake signature. The change prevents the real counter from leaking or being consumed during silent authentication attempts, which is a minor privacy and state-consistency improvement.
No urgent action needed. Review whether any relying parties or test suites depend on the previous behavior of receiving an incremented counter during silent authentication, and consider adding a changelog entry for transparency.
Security signals we found
Counter state no longer advanced on bogus-signature silent-authentication path
Static counter (0) used for non-user-present responses
Explicit 'Spec deviation' comment indicating intentional behavior
No changelog entry, suggesting low-severity internal cleanup
Evidence from the diff
In core/src/apps/webauthn/fido2.py, cbor_get_assertion_sign() previously called cred.next_signature_counter() once, built authenticator_data with that counter, and then either signed it (if user presence was true) or returned a bogus signature (if user presence was false). The patch refactors authenticator_data construction into a local _build_auth_data(ctr) helper and uses a static counter of 0 when user presence is false. This ensures the credential’s real signature counter is not incremented during silent/bogus-signature responses.
Changed components
core/src/apps/webauthn/fido2.pyFIDO2 authenticatorGetAssertion response pathCredential signature counter logicInspect captured patch +7 / −8
### core/src/apps/webauthn/fido2.py
@@ -1902,23 +1902,22 @@ def cbor_get_assertion_sign(
flags |= _AUTH_FLAG_ED
encoded_extensions = cbor.encode(extensions)
- ctr = cred.next_signature_counter()
-
- authenticator_data = (
- rp_id_hash + bytes([flags]) + ctr.to_bytes(4, "big") + encoded_extensions
- )
+ def _build_auth_data(ctr: int) -> bytes:
+ return rp_id_hash + bytes([flags]) + ctr.to_bytes(4, "big") + encoded_extensions
# Sign the authenticator data and the client data hash.
if user_presence:
- sig = cred.sign((authenticator_data, client_data_hash))
+ auth_data = _build_auth_data(ctr=cred.next_signature_counter())
+ sig = cred.sign((auth_data, client_data_hash))
else:
- # Spec deviation: Use a bogus signature during silent authentication.
+ # Spec deviation: Use a bogus signature and a static counter during silent authentication.
+ auth_data = _build_auth_data(ctr=0)
sig = cred.bogus_signature()
# Encode the authenticatorGetAssertion response data.
response = {
_GETASSERT_RESP_CREDENTIAL: {"type": "public-key", "id": cred.id},
- _GETASSERT_RESP_AUTH_DATA: authenticator_data,
+ _GETASSERT_RESP_AUTH_DATA: auth_data,
_GETASSERT_RESP_SIGNATURE: sig,
}
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.