Skip to content

Conversation

@iinuwa
Copy link
Contributor

@iinuwa iinuwa commented Dec 10, 2025

🎟️ Tracking

PM-17241

📔 Objective

This updates the SDK to point to a new version of our passkey-rs fork that has been synced with upstream. (The remaining differences between upstream and our fork are documented in the README of our fork.)

If approved, we should update passkey-rs's main branch to point to the version referenced in this PR (bitwarden/passkey-rs@2e5c0ba).

🚨 Breaking Changes

  • is_user_verification_enabled() is no longer asynchronous. Clients should implement any dynamic checks for UV availability in the check_user() implementation.
  • user_handle has been added to the find_credential()method as an optional parameter. If the incoming request contains the user handle, then clients should pass it on. Otherwise, pass anull` value, and the previous behavior.

iOS PR: bitwarden/ios#2190

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation
    team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed
    issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@iinuwa iinuwa requested a review from a team as a code owner December 10, 2025 19:31
@iinuwa iinuwa requested a review from dani-garcia December 10, 2025 19:31
@claude
Copy link

claude bot commented Dec 10, 2025

Claude finished @iinuwa's task —— View job


Code Review Complete

Tasks

  • Analyze PR changes and context
  • Review code changes for issues and improvements
  • Provide inline comments on specific concerns
  • Summarize overall findings

Code Review Summary

Overall Assessment

Status: Approved with Minor Concerns

This PR successfully updates the passkey-rs fork to sync with upstream changes. The implementation of the two main breaking changes is correct and consistent across the codebase.

Breaking Changes Implemented

1. is_verification_enabled() - Now Synchronous ✅

  • Change: Method signature changed from async fn to fn
  • Implementation: Correctly updated in all locations:
    • crates/bitwarden-fido/src/traits.rs:35
    • crates/bitwarden-fido/src/authenticator.rs:313, 669
    • crates/bitwarden-uniffi/src/platform/fido2.rs:235, 359
    • Swift demo app correctly updated (line 411)
  • Impact: All async/await calls removed appropriately

2. user_handle Parameter Added to find_credentials()

  • Change: New optional parameter user_handle: Option<Vec<u8>> added
  • Implementation: Consistently added across all layers:
    • Trait definition in traits.rs:45
    • Authenticator implementation in authenticator.rs:258, 263, 360, 385
    • UniFFI bindings (lines 245, 265, 268)
    • Swift demo app (line 421)
  • Impact: Breaking change to trait interface - all implementations properly updated

Code Quality & Correctness

Strengths:

  1. ✅ Consistent application of breaking changes across all layers
  2. ✅ Proper handling of CredentialExtensions initialization with hmac_secret: None
  3. ✅ Correct API method change from as_bytes() to as_webauthn_bytes()
  4. ✅ Swift demo app updated to demonstrate proper usage
  5. ✅ All affected trait implementations properly updated
  6. ✅ UniFFI bindings correctly bridge the new interfaces

Concerns:

📝 Asset Link URL Construction (Minor) - crates/bitwarden-fido/src/types.rs:473-478

  • The logic now always generates a URL from the host, potentially changing behavior when an explicitly provided asset_link_url differs from the generated one
  • Previous: Used provided URL or None
  • Current: Always generates URL, using provided or defaulting to well-known path
  • Recommendation: Verify this behavioral change aligns with passkey-rs expectations and Android asset link validation requirements

📝 Attestation Object Format Change (Informational) - crates/bitwarden-fido/src/authenticator.rs:178

  • Changed from as_bytes() to as_webauthn_bytes()
  • Returns WebAuthn-formatted bytes vs raw CBOR
  • Recommendation: Verify downstream consumers expect WebAuthn format

📝 User Handle Filtering (Informational) - crates/bitwarden-fido/src/authenticator.rs:263

  • New parameter allows credential filtering by user handle
  • Client implementations must implement proper filtering logic
  • Recommendation: Ensure mobile/web client implementations properly filter credentials when user_handle is provided

Security Implications

No security concerns identified

  • No sensitive data exposure
  • Proper handling of cryptographic materials maintained
  • No unsafe code introduced
  • Backward compatibility preserved where user_handle is None

Dependency Updates

  • passkey-rs: 0.2.00.5.0
  • passkey-authenticator: 0.2.00.5.0
  • passkey-client: 0.2.00.5.0
  • passkey-types: 0.2.10.5.0
  • public-suffix: 0.1.10.1.3
  • Rust version: 1.851.85.1

Testing Recommendations

  1. Verify asset link validation works correctly on Android
  2. Test credential discovery with and without user_handle filtering
  3. Confirm attestation object format is compatible with relying parties
  4. Validate synchronous is_verification_enabled() behavior in UI flows

Conclusion

The PR correctly implements the passkey-rs breaking changes with proper propagation through all layers of the codebase. The Swift demo app changes confirm the updates work as expected. The minor concerns noted are informational and suggest areas for verification rather than blocking issues.

Recommendation: APPROVE


@github-actions
Copy link
Contributor

github-actions bot commented Dec 10, 2025

Logo
Checkmarx One – Scan Summary & Detailsfce88719-782e-4579-8d74-4332c19ec3e1

Great job! No new security vulnerabilities introduced in this pull request

@github-actions
Copy link
Contributor

github-actions bot commented Dec 10, 2025

🔍 SDK Breaking Change Detection Results

SDK Version: iinuwa/update-passkey-rs (2576075)
Completed: 2025-12-11 17:53:33 UTC
Total Time: 247s

Client Status Details
typescript ✅ No breaking changes detected TypeScript compilation passed with new SDK version - View Details

Breaking change detection completed. View SDK workflow

@dani-garcia
Copy link
Member

I've noticed that your patches on top of 1Password's passkey-rs are bumping rust-version = "1.85.1" to 1.91.1, is that really needed? That's putting a bound on the minimum version that the crate supports which is causing CI to fail on the SDK as we're pinning 1.91.0 for now.

Ideally that version should be as low as it can to increase compatibility, and none of the changes look like they need the bump.

@codecov
Copy link

codecov bot commented Dec 10, 2025

Codecov Report

❌ Patch coverage is 0% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.57%. Comparing base (ec0231c) to head (2576075).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
crates/bitwarden-fido/src/authenticator.rs 0.00% 18 Missing ⚠️
crates/bitwarden-uniffi/src/platform/fido2.rs 0.00% 6 Missing ⚠️
crates/bitwarden-fido/src/types.rs 0.00% 5 Missing ⚠️
crates/bitwarden-fido/src/client.rs 0.00% 1 Missing ⚠️
crates/bitwarden-fido/src/lib.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #606      +/-   ##
==========================================
- Coverage   78.61%   78.57%   -0.04%     
==========================================
  Files         281      283       +2     
  Lines       29131    29187      +56     
==========================================
+ Hits        22901    22934      +33     
- Misses       6230     6253      +23     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@iinuwa
Copy link
Contributor Author

iinuwa commented Dec 10, 2025

@dani-garcia, no, that's not required. I had seen the recent bump in the SDK's Rust version to 1.91.1, so I assumed I should make this the same in our fork to unify the Rust versions across crates we owned. I can decrease it!

@dani-garcia
Copy link
Member

Ah, yeah updating the rust-toolchain file and the workflow YAML makes sense to match the versions we use in CI, but the rust-version = field in the Cargo.toml doesn't indicate the version that will be used to build, but the minimum version that is allowed to build. So in that sense, the best option for us would be:

  • rust-toolchain -> 1.91.1
  • CI workflows -> 1.91.1
  • rust-version in Cargo.toml -> 1.85.1 (for compatibility with older versions, even if we use latest on CI)

This ensures we use the latest version but allow users to build with older versions. You can see that we do the same thing ourselves, we bumped to 1.91.1, but our rust-version is still 1.85:

rust-version = "1.85"

Sidenote, you may need to bump that file from 1.85 to 1.85.1 to match passkey-rs, otherwise CI might complain

@iinuwa iinuwa force-pushed the iinuwa/update-passkey-rs branch from 0d43370 to 9536365 Compare December 10, 2025 20:34
@iinuwa iinuwa changed the title Iinuwa/update passkey rs Update passkey-rs Dec 10, 2025
@iinuwa iinuwa force-pushed the iinuwa/update-passkey-rs branch from 9536365 to 4e5cb4a Compare December 10, 2025 21:02
@dani-garcia
Copy link
Member

Nice, thanks for the changes! Happy to know we're almost at the end goal of removing the need for our fork. Two more things that are causing problems in CI:

  • You need to update the swift demo app, as it has a demo implementation of the fido2 traits:

    class Fido2UserInterfaceImpl: Fido2UserInterface {
    func pickCredentialForAuthentication(availableCredentials: [BitwardenSdk.CipherView]) async throws -> BitwardenSdk.CipherViewWrapper {
    abort()
    }
    func checkUserAndPickCredentialForCreation(options: BitwardenSdk.CheckUserOptions, newCredential: BitwardenSdk.Fido2CredentialNewView) async throws -> BitwardenSdk.CheckUserAndPickCredentialForCreationResult {
    abort()
    }
    func checkUser(options: BitwardenSdk.CheckUserOptions, hint: UiHint) async throws -> BitwardenSdk.CheckUserResult {
    return CheckUserResult(userPresent: true, userVerified: true)
    }
    func isVerificationEnabled() async -> Bool {
    true
    }
    }
    class Fido2CredentialStoreImpl: Fido2CredentialStore {
    func allCredentials() async throws -> [BitwardenSdk.CipherListView] {
    abort()
    }
    func findCredentials(ids: [Data]?, ripId: String) async throws -> [BitwardenSdk.CipherView] {
    abort()
    }
    func saveCredential(cred: BitwardenSdk.EncryptionContext) async throws {
    print("SAVED CREDENTIAL")
    }
    }

  • So apparently that minimum 1.85.1 version that the passkey-rs crate supports is not correct, since this let chain:
    https://github.com/bitwarden/passkey-rs/blob/873a4b983ef3fd7b24402a27da044739968ccbbd/passkey-client/src/extensions/prf.rs#L202-L203
    is not supported until rust 1.88. This seems like an upstream problem that we're hitting because they're not testing their minimum supported rust version, so it might be worth reporting it upstream (so they can either remove the let-chain or bump their minimum rust version to 1.88)

This function is intended to signal whether user verification may potentially
be available for the authenticator, not whether user verification will be
available for a particular request. Since this can be known ahead of time for
our clients, there is no need for this function to be async. Making this async
also aligns us closer with upstream.

Per-request availability checks should be done in the caller's implementation
of check_user().
@iinuwa iinuwa force-pushed the iinuwa/update-passkey-rs branch from 4e5cb4a to 2576075 Compare December 11, 2025 17:40
@iinuwa
Copy link
Contributor Author

iinuwa commented Dec 11, 2025

Thanks for pointing all that out. I think I've addressed all of the concerns now:

  • I fixed the MSRV issue in our passkey-rs fork and created a PR to upstream: Fix and enforce MSRV in CI 1Password/passkey-rs#81
  • I kept the MSRV in our fork as 1.85.1, but updated CI to use our standard Rust, Action, and OS runner versions.
  • I updated the Swift demo.

let asset_link_url = {
let url = value
.asset_link_url
.unwrap_or_else(|| format!("https://{}/.well-known/assetlinks.json", value.host));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The asset link URL construction has been changed to always generate a URL from the host, even when asset_link_url is provided. This is a breaking behavioral change that differs from the previous implementation.

Previous behavior: Used the provided asset_link_url if available, otherwise returned None.
New behavior: Always generates a URL from the host, using the provided asset_link_url or falling back to the default well-known path.

While this change makes the URL always available (which passkey-rs now expects), it may silently ignore an explicitly provided asset_link_url that differs from the host-derived URL. Consider whether this is the intended behavior or if there should be validation to ensure consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unwrap_or_else() will take the provided asset link URL or compute a fallback value from the value.host parameter. It is incorrect to say that this always generates a URL from the host.

This performs the same functionality as before: passkey-rs was doing this internally when None is passed, but has shifted that responsibility to the caller.

rp_id: value.rp_id.clone(),
user_handle: user_handle.map(|u| u.into_bytes().into()),
counter,
extensions: CredentialExtensions { hmac_secret: None },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: The new extensions field is properly initialized with hmac_secret: None. This ensures compatibility with the updated passkey-rs structure for Passkey.

};

let attestation_object = response.as_bytes().to_vec();
let attestation_object = response.as_webauthn_bytes().to_vec();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method call has been updated from as_bytes() to as_webauthn_bytes(). This is correct for the new passkey-rs API, but note that this returns WebAuthn format bytes which may have different encoding than the raw CBOR bytes. Verify that downstream consumers expect WebAuthn-formatted attestation objects.

let result = self.credential_store.find_credentials(None, rp_id).await?;
let result = self
.credential_store
.find_credentials(None, rp_id, user_handle)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user_handle parameter has been added to find_credentials as required by the updated passkey-rs API. However, this is a silent breaking change to the trait interface:

  1. All implementations of Fido2CredentialStore must now handle this parameter
  2. The filtering logic for credentials now potentially includes user_handle matching
  3. Passing None preserves backward compatibility, but implementations should validate whether user_handle filtering is properly implemented

Consider whether the credential store implementations in client apps have been updated to properly filter by user_handle when provided.

async fn convert_requested_uv(&self, uv: UV) -> bool {
let verification_enabled = self.user_interface.is_verification_enabled().await;
fn convert_requested_uv(&self, uv: UV) -> bool {
let verification_enabled = self.user_interface.is_verification_enabled();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: The method signature change from async fn to fn for convert_requested_uv is correct. Since is_verification_enabled() is now synchronous, this method no longer needs to be async.

) -> Result<Vec<CipherView>, BitFido2CallbackError> {
self.0
.find_credentials(ids, rip_id)
.find_credentials(ids, rip_id, user_handle)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user_handle parameter is correctly passed through from the trait implementation to the internal call. The implementation properly maintains the signature compatibility with the updated trait definition.

))

let _ = try await authenticator.silentlyDiscoverCredentials(rpId: "")
let _ = try await authenticator.silentlyDiscoverCredentials(rpId: "", userHandle: nil)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: The Swift demo app has been correctly updated to pass the new userHandle parameter (as nil in this test case). This demonstrates proper handling of the breaking change.

}

func findCredentials(ids: [Data]?, ripId: String) async throws -> [BitwardenSdk.CipherView] {
func findCredentials(ids: [Data]?, ripId: String, userHandle: Data?) async throws -> [BitwardenSdk.CipherView] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: The findCredentials method signature has been updated to include the userHandle parameter. This correctly implements the breaking change from the trait definition.

}

func isVerificationEnabled() async -> Bool {
func isVerificationEnabled() -> Bool {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: The method signature has been correctly updated from async func to func for isVerificationEnabled(), reflecting the synchronous nature of this method in the updated passkey-rs API.

@iinuwa iinuwa enabled auto-merge (squash) December 11, 2025 17:44
Copy link
Member

@dani-garcia dani-garcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@iinuwa iinuwa merged commit fc6d317 into main Dec 11, 2025
115 checks passed
@iinuwa iinuwa deleted the iinuwa/update-passkey-rs branch December 11, 2025 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants