Skip to content

[PM-27238] feat: Support v2 encryption on key connector signups#2648

Open
matt-livefront wants to merge 1 commit into
mainfrom
matt/PM-27238-key-connector-v2
Open

[PM-27238] feat: Support v2 encryption on key connector signups#2648
matt-livefront wants to merge 1 commit into
mainfrom
matt/PM-27238-key-connector-v2

Conversation

@matt-livefront
Copy link
Copy Markdown
Collaborator

@matt-livefront matt-livefront commented May 14, 2026

🎟️ Tracking

PM-27238

📔 Objective

Enables support for V2 encryption on key connector signups. Most of the existing logic that was implemented in the app, including API requests, has been moved into the SDK (via the postKeysForKeyConnectorRegistration(keyConnectorUrl:ssoOrgIdentifier:) function).

These changes are behind the enable-account-encryption-v2-key-connector-registration feature flag.

@matt-livefront matt-livefront requested a review from quexten May 14, 2026 18:25
@matt-livefront matt-livefront requested a review from a team as a code owner May 14, 2026 18:25
@matt-livefront matt-livefront added the ai-review Request a Claude code review label May 14, 2026
@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context t:feature labels May 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 2026

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR adds V2 account encryption support to the Key Connector new-user registration flow, gated behind the enable-account-encryption-v2-key-connector-registration feature flag. The V2 path delegates the multi-step key generation and API posting to the SDK's postKeysForKeyConnectorRegistration function. The vault-unlock step is consolidated into AuthRepository.convertNewUserToKeyConnector (eliminating a redundant unlockVaultWithKeyConnectorKey call in SingleSignOnProcessor), and a loading overlay is now shown during migration. Test coverage spans both V1 (legacy) and V2 paths, including SDK-failure and unlock-failure scenarios.

Code Review Details

No findings.

Copy link
Copy Markdown
Contributor

@quexten quexten left a comment

Choose a reason for hiding this comment

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

@matt-livefront It's fine if that's out of scope for this PR, but the way iOS is storing the account cryptographic state as a custom model which is different from the public API surface of the SDK is weird and seems brittle / likely to introduce bugs. Is it not possible to just store the SDK's model, or even a string serialized version of it?

Specifically, mobile should not care about the internals of that object or individually set fields on it (i guess with the exception of sync, though we should work on moving that part to the SDK too).

Comment thread BitwardenShared/Core/Auth/Repositories/AuthRepository.swift
switch accountCryptographicState {
case let .v1(privateKey):
self.init(
accountKeys: nil,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AccountKeys should not be nil. It should be populated with a PrivateKeysResponseModel with with signature key pair and security state set to nil, but the publicKeyEncryptionKeyPair present.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do you know where that could come from in this key connector registration flow? It isn't returned in KeyConnectorRegistrationResult when calling postKeysForKeyConnectorRegistration? Given that the SDK is generating V2 encryption keys, is it possible to end up in this v1 case? Maybe the answer is, as you suggested, to persist WrappedAccountCryptographicState directly, but I was trying to make this work with we we already have in place to avoid additional changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As mentioned, persisting the WrappedAccountCryptographicState is the correct solution.

IF needed, then you can make an accountKeys model in the same way you make the V2 model, only leave out the security state and signature key pair, and signed public key.

self.init(
accountKeys: PrivateKeysResponseModel(
publicKeyEncryptionKeyPair: PublicKeyEncryptionKeyPairResponseModel(
publicKey: "", // Not returned by SDK at registration; will populate on next sync.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This feels like it has a high risk of introducing bugs. Can we not just store the accountCryptographicState? That's what the SDK takes for init, and KM expects clients to just store it instead of the response model.

),
signatureKeyPair: SignatureKeyPairResponseModel(
wrappedSigningKey: signingKey,
verifyingKey: "", // Not returned by SDK at registration; will populate on next sync.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same comment as above, this seems like a high risk.

/// - accountCryptographicState: The SDK's wrapped account cryptographic state.
/// - encryptedUserKey: An optional encrypted user key to store alongside the cryptographic state.
///
init(accountCryptographicState: WrappedAccountCryptographicState, encryptedUserKey: String? = nil) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't quite understand what we are doing here. We should just store the WrappedAccountCryptographicState, not a response model. On sync, the response model should get converted to WrappedAccountCryptographicState.

@matt-livefront
Copy link
Copy Markdown
Collaborator Author

@matt-livefront It's fine if that's out of scope for this PR, but the way iOS is storing the account cryptographic state as a custom model which is different from the public API surface of the SDK is weird and seems brittle / likely to introduce bugs. Is it not possible to just store the SDK's model, or even a string serialized version of it?

Specifically, mobile should not care about the internals of that object or individually set fields on it (i guess with the exception of sync, though we should work on moving that part to the SDK too).

@quexten Thanks for your feedback! In Swift, the easiest way to support persistence is via Codable. The Swift bindings from the SDK don't include this, and it's a fair bit of extra work for us to implement it manually on our side. So I suspect that's how we ended up in this position; our response models are already Codable (for decoding JSON) so it's straightforward to persist those and then transform into the SDK types as needed. But you make a good point, do you know if the SDK could include the Codable conformance? It seems like it could just be a configuration option, but maybe there's more to it. https://mozilla.github.io/uniffi-rs/latest/swift/configuration.html#:~:text=generate_codable_conformance

@quexten
Copy link
Copy Markdown
Contributor

quexten commented May 18, 2026

@matt-livefront I don't see a reason not to enable codable on the SDK. We want to keep the amount mobile implements to a minimum, so if this reduces it I support it. I'm opening a PR to enable it for crypto / core, which should cover anything on the key-management front.

quexten added a commit to bitwarden/sdk-internal that referenced this pull request May 18, 2026
## 🎟️ Tracking

<!-- Paste the link to the Jira or GitHub issue or otherwise describe /
point to where this change is coming from. -->
bitwarden/ios#2648 (comment)

## 📔 Objective

<!-- Describe what the purpose of this PR is, for example what bug
you're fixing or new feature you're adding. -->
As per mobile's request, we are enabling codable for the uniffi bindings
so that they can directly serialize the structs they get returned from
the SDK without having to maintain a seprate copy. Maintaining a
separate copy in the past has lead to bugs / incorrect state being saved
(bitwarden/ios#2303).

This is safer than mobiles current approach of persisting server request
/ response models, which do not match what the SDK returns.

## 🚨 Breaking Changes

<!-- Does this PR introduce any breaking changes? If so, please describe
the impact and migration path for clients.

If you're unsure, the automated TypeScript compatibility check will run
when you open/update this PR and provide feedback.

For breaking changes:
1. Describe what changed in the client interface
2. Explain why the change was necessary
3. Provide migration steps for client developers
4. Link to any paired client PRs if needed

Otherwise, you can remove this section. -->
@quexten
Copy link
Copy Markdown
Contributor

quexten commented May 18, 2026

@matt-livefront Merged the PR, codable should now be available. Please note I did not test the UNIFFI build locally. I have a separate PR adding swift integration tests, after which we could add those tests into there. Until then, please LMK if there are any issues on this front.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review app:password-manager Bitwarden Password Manager app context t:feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants