Skip to content

Conversation

@quexten
Copy link
Contributor

@quexten quexten commented Dec 2, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-27232
bitwarden/server#6671

📔 Objective

Implements registration for TDE users. This uses v2 encryption.

Note

The API changes here are not final and need an update to the automatic API bindings, after the corresponding server change is merged. This PR will only be merged once the binding changes are already in main and removed from this PR.

🚨 Breaking Changes

⏰ 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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Logo
Checkmarx One – Scan Summary & Details52a95d4d-a40d-4b52-8cc2-8ec6f79bab7c

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

@quexten quexten changed the base branch from main to km/account-cryptographic-state December 2, 2025 17:22
@quexten quexten changed the title km/tde registration Implement Registration for TDE Users Dec 2, 2025
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 2, 2025

Base automatically changed from km/account-cryptographic-state to main December 3, 2025 11:16
@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

🔍 SDK Breaking Change Detection Results

SDK Version: km/tde-registration (df619e4)
Completed: 2025-12-11 18:25:53 UTC
Total Time: 249s

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

Breaking change detection completed. View SDK workflow

@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 55.68862% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.51%. Comparing base (a004d82) to head (df619e4).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
crates/bitwarden-auth/src/registration.rs 0.00% 73 Missing ⚠️
...rden-auth/src/send_access/access_token_response.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #596      +/-   ##
==========================================
- Coverage   78.67%   78.51%   -0.17%     
==========================================
  Files         283      283              
  Lines       29285    29345      +60     
==========================================
- Hits        23041    23039       -2     
- Misses       6244     6306      +62     

☔ 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.

@quexten quexten changed the title Implement Registration for TDE Users [PM-27232] Implement Registration for TDE Users Dec 9, 2025
@quexten quexten force-pushed the km/tde-registration branch from 7e40878 to 7fe5d69 Compare December 9, 2025 14:40
@quexten quexten marked this pull request as ready for review December 11, 2025 13:40
@quexten quexten requested review from a team as code owners December 11, 2025 13:40
@quexten quexten marked this pull request as draft December 11, 2025 13:40
@claude

This comment was marked as resolved.

Copy link
Contributor

@Thomas-Avery Thomas-Avery left a comment

Choose a reason for hiding this comment

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

Looking good. A few things to take a look at and some questions from my side.

Also I think claude is wanting to have a chat on this one 😆

Comment on lines +55 to +61
org_id: String,
org_public_key: B64,
// Note: Ideally these would be set for the register client, however no such functionality
// exists at the moment
user_id: String,
device_id: String,
trust_device: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

For these ID parameters can we use the OrganizationId UserId etc., from crates/bitwarden-core/src/ids.rs, as the input types? Or is there some limitation for those?

Comment on lines +52 to +59
&self,
org_id: String,
org_public_key: B64,
// Note: Ideally these would be set for the register client, however no such functionality
// exists at the moment
user_id: String,
device_id: String,
trust_device: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Jared on this, mostly just a style/preference thing having a bunch of primitives is hard to keep track of when reading the method.

Vs just seeing request.<some_input_name> lets me know right away it's coming from the request input.

Comment on lines +86 to +90
// Note: This property is deprecated and will be removed
public_key: account_cryptographic_state_request
.account_public_key
.ok_or(UserRegistrationError::Crypto)?,
// Note: This property is deprecated and will be removed
Copy link
Contributor

Choose a reason for hiding this comment

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

For these notes do we have a ticket tracking that, that we can reference here in the comments?

Comment on lines +157 to +159
pub device_key: String,
/// The decrypted user key. This can be used to get the consuming client to an unlocked state.
pub user_key: B64,
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Why do we use String for device_key and B64 for user_key?

Since they're both symmetric crypto keys I would expect the same return type.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for unit tests

Comment on lines +229 to +264
let mut ctx = self.client.internal.get_key_store().context_mut();
let (user_key, wrapped_state) =
WrappedAccountCryptographicState::make(&mut ctx, user_id)
.map_err(MakeKeysError::AccountCryptographyInitialization)?;
#[expect(deprecated)]
let user_key = ctx.dangerous_get_symmetric_key(user_key)?;

// TDE unlock method
let device_key = DeviceKey::trust_device(user_key)?;

// Account recovery enrollment
let public_key =
AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
.map_err(MakeKeysError::Crypto)?;
let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(user_key, &public_key)
.map_err(MakeKeysError::Crypto)?;

let store = KeyStore::default();
let mut ctx = store.context_mut();
let user_key_id = ctx.add_local_symmetric_key(user_key.to_owned());
let security_state = RwLock::new(None);
wrapped_state
.set_to_context(&security_state, user_key_id, &store, ctx)
.map_err(MakeKeysError::AccountCryptographyInitialization)?;

let cryptography_state_request_model = wrapped_state
.to_request_model(&store)
.map_err(|_| MakeKeysError::RequestModelCreation)?;

Ok((
wrapped_state,
user_key.to_owned(),
cryptography_state_request_model,
device_key,
admin_reset,
))
Copy link
Contributor

@Thomas-Avery Thomas-Avery Dec 11, 2025

Choose a reason for hiding this comment

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

Suggested change
let mut ctx = self.client.internal.get_key_store().context_mut();
let (user_key, wrapped_state) =
WrappedAccountCryptographicState::make(&mut ctx, user_id)
.map_err(MakeKeysError::AccountCryptographyInitialization)?;
#[expect(deprecated)]
let user_key = ctx.dangerous_get_symmetric_key(user_key)?;
// TDE unlock method
let device_key = DeviceKey::trust_device(user_key)?;
// Account recovery enrollment
let public_key =
AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
.map_err(MakeKeysError::Crypto)?;
let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(user_key, &public_key)
.map_err(MakeKeysError::Crypto)?;
let store = KeyStore::default();
let mut ctx = store.context_mut();
let user_key_id = ctx.add_local_symmetric_key(user_key.to_owned());
let security_state = RwLock::new(None);
wrapped_state
.set_to_context(&security_state, user_key_id, &store, ctx)
.map_err(MakeKeysError::AccountCryptographyInitialization)?;
let cryptography_state_request_model = wrapped_state
.to_request_model(&store)
.map_err(|_| MakeKeysError::RequestModelCreation)?;
Ok((
wrapped_state,
user_key.to_owned(),
cryptography_state_request_model,
device_key,
admin_reset,
))
let mut ctx = self.client.internal.get_key_store().context_mut();
let (user_key_id, wrapped_state) =
WrappedAccountCryptographicState::make(&mut ctx, user_id)
.map_err(MakeKeysError::AccountCryptographyInitialization)?;
#[expect(deprecated)]
let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?;
// TDE unlock method
let device_key = DeviceKey::trust_device(user_key)?;
// Account recovery enrollment
let public_key =
AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
.map_err(MakeKeysError::Crypto)?;
let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(user_key, &public_key)
.map_err(MakeKeysError::Crypto)?;
let cryptography_state_request_model = wrapped_state
.to_request_model(self.client.internal.get_key_store())
.map_err(|_| MakeKeysError::RequestModelCreation)?;
Ok((
wrapped_state,
user_key.to_owned(),
cryptography_state_request_model,
device_key,
admin_reset,
))

💭 It would seem like we would want to continue using the ctx and key store from the client?

Maybe I'm missing something important or a side effect making the other key store is desired for. If so could we add a comment about what that is.

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.

4 participants