Skip to content

[PM-43134] Match the WebAuthn spec when parsing passkey creation options - #7360

Open
psolvy wants to merge 1 commit into
bitwarden:mainfrom
psolvy:fix/passkey-creation-options-spec-conformance
Open

[PM-43134] Match the WebAuthn spec when parsing passkey creation options#7360
psolvy wants to merge 1 commit into
bitwarden:mainfrom
psolvy:fix/passkey-creation-options-spec-conformance

Conversation

@psolvy

@psolvy psolvy commented Sep 5, 2026

Copy link
Copy Markdown

🎟️ Tracking

#7359

📔 Objective

PasskeyAttestationOptions does not match PublicKeyCredentialCreationOptionsJSON. There are four mismatches. One breaks registration outright, the other three lose data without an error.

authenticatorSelection was required. The spec marks only rp, user, challenge and pubKeyCredParams as required. A request that omits authenticatorSelection currently fails to deserialize with MissingFieldException, so getPasskeyAttestationOptionsOrNull returns null and registration ends in Error.MissingHostUrl for an unprivileged caller, or Error.InternalError for a privileged one. VaultAddEditViewModel also falls back to the package name and an empty username. explicitNulls = false does not cover this, since it only relaxes nullable properties, and coerceInputValues only rewrites values that are present but invalid.

Three name mismatches. excludedCredentials should be excludeCredentials, cross_platform should be cross-platform, and ResidentKeyRequirement was missing discouraged. These are not only parsing details, because registerFido2CredentialInternal re-serializes this model and passes the result to the SDK. Anything the model cannot name is dropped before the authenticator sees it, so the relying party's exclusion list never arrives and duplicate passkeys are not prevented. ignoreUnknownKeys hides the first one and coerceInputValues hides the other two, which is why none of this surfaced as an error.

Fixtures in this repo already use the spec spelling excludeCredentials, at RelyingPartyParserTest.kt:105 and :139 and BitwardenCredentialManagerTest.kt:1520, but always with an empty array. That is why the existing tests stayed green.

Nothing else needed to change. There are no main source construction sites for this model, both test construction sites use named arguments, and the only read of authenticatorSelection is already a null safe call in BitwardenCredentialManagerImpl.getUserVerificationRequirement, which falls back to the same value as before.

Testing

New PasskeyAttestationOptionsTest with 7 tests. I checked each fix separately rather than all at once:

  • reverting only the two @SerialName renames fails exactly the 4 tests that cover them and leaves the other 3 passing
  • authenticatorSelection is covered by the decode test, which fails with expected: not <null> on the old model
  • the DISCOURAGED member does not exist on the old model, so the test file does not compile against it

The two serialization tests build the model directly instead of decoding first, so they exercise the encoder rather than passing for the wrong reason.

:app:testStandardDebugUnitTest for data.credentials.*, ui.vault.feature.addedit.* and ui.vault.feature.itemlisting.* is green at 956 tests, and detekt reports no findings.

Out of scope

The round trip through this model is lossy beyond the fields above. attestation, timeout, extensions and hints are all dropped before the SDK sees the request, and real requests do carry them. That needs a different fix than renaming members, so I left it alone here.

PasskeyAttestationOptions differs from PublicKeyCredentialCreationOptionsJSON
in four places. One breaks registration, the other three lose data quietly.

authenticatorSelection is optional in the spec but was declared non-nullable
with no default, so a request without it failed to deserialize with
MissingFieldException. Registration then ended in MissingHostUrl for an
unprivileged caller and InternalError for a privileged one.

The remaining three are name mismatches, and they matter beyond parsing:
registerFido2CredentialInternal re-serializes this model and sends the result
to the SDK, so anything the model cannot name never reaches the authenticator.

  excludedCredentials -> excludeCredentials, so the relying party's exclusion
  list is no longer replaced by an empty one and duplicate passkeys can be
  prevented again

  cross_platform -> cross-platform, so the attachment survives instead of
  being coerced to null

  ResidentKeyRequirement gains discouraged, which the spec defines and which
  was being coerced to null the same way

The fixtures in this repo already used the spec spelling excludeCredentials
with empty arrays, which is why none of this showed up in the tests.
@psolvy
psolvy requested review from a team and david-livefront as code owners September 5, 2026 22:23
@CLAassistant

CLAassistant commented Sep 5, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@bitwarden-bot

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! We've added this to our internal tracking system for review.
ID: PM-43134
Link: https://bitwarden.atlassian.net/browse/PM-43134

Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process.

@bitwarden-bot bitwarden-bot changed the title Match the WebAuthn spec when parsing passkey creation options [PM-43134] Match the WebAuthn spec when parsing passkey creation options Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants