Skip to main content

bitwarden_auth/registration/
post_keys_for_tde_registration.rs

1//! Initializes a new cryptographic state for a user and posts it to the server; enrolls in
2//! admin password reset and finally enrolls the user to TDE unlock.
3use bitwarden_api_api::models::{
4    DeviceKeysRequestModel, KeysRequestModel, OrganizationUserResetPasswordEnrollmentRequestModel,
5};
6use bitwarden_core::{
7    OrganizationId, UserId,
8    key_management::account_cryptographic_state::WrappedAccountCryptographicState,
9};
10use bitwarden_encoding::B64;
11use tracing::info;
12#[cfg(feature = "wasm")]
13use wasm_bindgen::prelude::*;
14
15use crate::registration::{RegistrationClient, RegistrationError};
16
17/// Request parameters for TDE (Trusted Device Encryption) registration.
18#[cfg_attr(
19    feature = "wasm",
20    derive(tsify::Tsify),
21    tsify(into_wasm_abi, from_wasm_abi)
22)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
24#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
25pub struct TdeRegistrationRequest {
26    /// Organization ID to enroll in
27    pub org_id: OrganizationId,
28    /// Organization's public key for encrypting the reset password key. This should be verified by
29    /// the client and not verifying may compromise the security of the user's account.
30    pub org_public_key: B64,
31    /// User ID for the account being initialized
32    pub user_id: UserId,
33    /// Device identifier for TDE enrollment
34    pub device_identifier: String,
35    /// Whether to trust this device for TDE
36    pub trust_device: bool,
37}
38
39/// Result of TDE registration process.
40#[cfg_attr(
41    feature = "wasm",
42    derive(tsify::Tsify),
43    tsify(into_wasm_abi, from_wasm_abi)
44)]
45#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
46#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
47pub struct TdeRegistrationResponse {
48    /// The account cryptographic state of the user
49    pub account_cryptographic_state: WrappedAccountCryptographicState,
50    /// The device key
51    pub device_key: B64,
52    /// The decrypted user key. This can be used to get the consuming client to an unlocked state.
53    pub user_key: B64,
54}
55
56#[cfg_attr(feature = "wasm", wasm_bindgen)]
57impl RegistrationClient {
58    /// Initializes a new cryptographic state for a user and posts it to the server; enrolls in
59    /// admin password reset and finally enrolls the user to TDE unlock.
60    pub async fn post_keys_for_tde_registration(
61        &self,
62        request: TdeRegistrationRequest,
63    ) -> Result<TdeRegistrationResponse, RegistrationError> {
64        let client = &self.client.internal;
65        let api_client = &client.get_api_configurations().api_client;
66        internal_post_keys_for_tde_registration(self, api_client, request).await
67    }
68}
69
70async fn internal_post_keys_for_tde_registration(
71    registration_client: &RegistrationClient,
72    api_client: &bitwarden_api_api::apis::ApiClient,
73    request: TdeRegistrationRequest,
74) -> Result<TdeRegistrationResponse, RegistrationError> {
75    // First call crypto API to get all keys
76    info!("Initializing account cryptography");
77    let tde_registration_crypto_result = registration_client
78        .client
79        .crypto()
80        .make_user_tde_registration(request.org_public_key.clone())
81        .map_err(|_| RegistrationError::Crypto)?;
82
83    // Post the generated keys to the API here. The user now has keys and is "registered", but
84    // has no unlock method.
85    let keys_request = KeysRequestModel {
86        account_keys: Some(Box::new(
87            tde_registration_crypto_result.account_keys_request.clone(),
88        )),
89        // Note: This property is deprecated and will be removed
90        public_key: tde_registration_crypto_result
91            .account_keys_request
92            .account_public_key
93            .ok_or(RegistrationError::Crypto)?,
94        // Note: This property is deprecated and will be removed
95        encrypted_private_key: tde_registration_crypto_result
96            .account_keys_request
97            .user_key_encrypted_account_private_key
98            .ok_or(RegistrationError::Crypto)?,
99    };
100    info!("Posting user account cryptographic state to server");
101    api_client
102        .accounts_api()
103        .post_keys(Some(keys_request))
104        .await
105        .map_err(|e| {
106            tracing::error!("Failed to post account keys: {e:?}");
107            RegistrationError::Api
108        })?;
109
110    // Next, enroll the user for reset password using the reset password key generated above.
111    info!("Enrolling into admin account recovery");
112    api_client
113        .organization_users_api()
114        .put_reset_password_enrollment(
115            request.org_id.into(),
116            request.user_id.into(),
117            Some(OrganizationUserResetPasswordEnrollmentRequestModel {
118                reset_password_key: Some(
119                    tde_registration_crypto_result
120                        .reset_password_key
121                        .to_string(),
122                ),
123                master_password_hash: None,
124            }),
125        )
126        .await
127        .map_err(|e| {
128            tracing::error!("Failed to enroll for reset password: {e:?}");
129            RegistrationError::Api
130        })?;
131
132    if request.trust_device {
133        // Next, enroll the user for TDE unlock
134        info!("Enrolling into trusted device decryption");
135        api_client
136            .devices_api()
137            .put_keys(
138                request.device_identifier.as_str(),
139                Some(DeviceKeysRequestModel {
140                    encrypted_user_key: tde_registration_crypto_result
141                        .trusted_device_keys
142                        .protected_user_key
143                        .to_string(),
144                    encrypted_public_key: tde_registration_crypto_result
145                        .trusted_device_keys
146                        .protected_device_public_key
147                        .to_string(),
148                    encrypted_private_key: tde_registration_crypto_result
149                        .trusted_device_keys
150                        .protected_device_private_key
151                        .to_string(),
152                }),
153            )
154            .await
155            .map_err(|e| {
156                tracing::error!("Failed to enroll device for TDE: {e:?}");
157                RegistrationError::Api
158            })?;
159    }
160
161    info!("User initialized!");
162    // Note: This passing out of state and keys is temporary. Once SDK state management is more
163    // mature, the account cryptographic state and keys should be set directly here.
164    Ok(TdeRegistrationResponse {
165        account_cryptographic_state: tde_registration_crypto_result.account_cryptographic_state,
166        device_key: tde_registration_crypto_result
167            .trusted_device_keys
168            .device_key,
169        user_key: tde_registration_crypto_result
170            .user_key
171            .to_encoded()
172            .to_vec()
173            .into(),
174    })
175}
176
177#[cfg(test)]
178mod tests {
179    use std::str::FromStr;
180
181    use bitwarden_api_api::{
182        apis::ApiClient,
183        models::{DeviceResponseModel, KeysResponseModel},
184    };
185    use bitwarden_core::Client;
186    use bitwarden_crypto::EncString;
187
188    use super::*;
189
190    const TEST_USER_ID: &str = "060000fb-0922-4dd3-b170-6e15cb5df8c8";
191    const TEST_ORG_ID: &str = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8";
192    const TEST_DEVICE_ID: &str = "test-device-id";
193
194    const TEST_ORG_PUBLIC_KEY: &[u8] = &[
195        48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0,
196        48, 130, 1, 10, 2, 130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148,
197        115, 86, 140, 129, 74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250,
198        229, 242, 156, 67, 109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134,
199        243, 44, 243, 79, 84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92,
200        88, 22, 115, 135, 63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100,
201        76, 75, 118, 202, 252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88,
202        195, 88, 150, 230, 111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130,
203        14, 72, 123, 140, 47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39,
204        80, 143, 100, 194, 37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213,
205        211, 84, 255, 14, 205, 114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16,
206        12, 122, 72, 227, 95, 160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144,
207        255, 162, 248, 50, 28, 47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190,
208        248, 60, 114, 218, 32, 209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108,
209        201, 41, 160, 81, 133, 171, 205, 221, 2, 3, 1, 0, 1,
210    ];
211
212    #[tokio::test]
213    async fn test_post_keys_for_tde_registration_success() {
214        let client = Client::new(None);
215        let registration_client = RegistrationClient::new(client);
216
217        let api_client = ApiClient::new_mocked(|mock| {
218            mock.accounts_api
219                .expect_post_keys()
220                .once()
221                .returning(move |_body| {
222                    Ok(KeysResponseModel {
223                        object: None,
224                        key: None,
225                        public_key: None,
226                        private_key: None,
227                        account_keys: None,
228                    })
229                });
230            mock.organization_users_api
231                .expect_put_reset_password_enrollment()
232                .once()
233                .returning(move |_org_id, _user_id, _body| Ok(()));
234            mock.devices_api
235                .expect_put_keys()
236                .once()
237                .returning(move |_device_id, body| {
238                    let body = body.unwrap();
239                    assert!(matches!(
240                        EncString::from_str(body.encrypted_private_key.as_str()).unwrap(),
241                        EncString::Aes256Cbc_HmacSha256_B64 { .. }
242                    ));
243                    assert!(matches!(
244                        EncString::from_str(body.encrypted_public_key.as_str()).unwrap(),
245                        EncString::Cose_Encrypt0_B64 { .. }
246                    ));
247
248                    Ok(DeviceResponseModel {
249                        object: None,
250                        id: None,
251                        name: None,
252                        r#type: None,
253                        identifier: None,
254                        creation_date: None,
255                        last_activity_date: None,
256                        is_trusted: None,
257                        encrypted_user_key: None,
258                        encrypted_public_key: None,
259                    })
260                });
261        });
262
263        let request = TdeRegistrationRequest {
264            org_id: TEST_ORG_ID.parse().unwrap(),
265            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
266            user_id: TEST_USER_ID.parse().unwrap(),
267            device_identifier: TEST_DEVICE_ID.to_string(),
268            trust_device: true,
269        };
270
271        let result =
272            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
273                .await;
274
275        assert!(result.is_ok());
276        // Assert that the mock expectations were met
277        if let ApiClient::Mock(mut mock) = api_client {
278            mock.accounts_api.checkpoint();
279            mock.organization_users_api.checkpoint();
280            mock.devices_api.checkpoint();
281        }
282    }
283
284    #[tokio::test]
285    async fn test_post_keys_for_tde_registration_trust_device_false() {
286        let client = Client::new(None);
287        let registration_client = RegistrationClient::new(client);
288
289        let api_client = ApiClient::new_mocked(|mock| {
290            mock.accounts_api
291                .expect_post_keys()
292                .once()
293                .returning(move |_body| {
294                    Ok(KeysResponseModel {
295                        object: None,
296                        key: None,
297                        public_key: None,
298                        private_key: None,
299                        account_keys: None,
300                    })
301                });
302            mock.organization_users_api
303                .expect_put_reset_password_enrollment()
304                .once()
305                .returning(move |_org_id, _user_id, _body| Ok(()));
306            // Explicitly expect that put_keys is never called when trust_device is false
307            mock.devices_api.expect_put_keys().never();
308        });
309
310        let request = TdeRegistrationRequest {
311            org_id: TEST_ORG_ID.parse().unwrap(),
312            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
313            user_id: TEST_USER_ID.parse().unwrap(),
314            device_identifier: TEST_DEVICE_ID.to_string(),
315            trust_device: false, // trust_device is false
316        };
317
318        let result =
319            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
320                .await;
321
322        assert!(result.is_ok());
323        // Assert that the mock expectations were met (put_keys should not have been called)
324        if let ApiClient::Mock(mut mock) = api_client {
325            mock.accounts_api.checkpoint();
326            mock.organization_users_api.checkpoint();
327            mock.devices_api.checkpoint();
328        }
329    }
330
331    #[tokio::test]
332    async fn test_post_keys_for_tde_registration_post_keys_failure() {
333        let client = Client::new(None);
334        let registration_client = RegistrationClient::new(client);
335
336        let api_client = ApiClient::new_mocked(|mock| {
337            mock.accounts_api
338                .expect_post_keys()
339                .once()
340                .returning(move |_body| {
341                    Err(serde_json::Error::io(std::io::Error::other("API error")).into())
342                });
343            // Subsequent API calls should not be made if post_keys fails
344            mock.organization_users_api
345                .expect_put_reset_password_enrollment()
346                .never();
347            mock.devices_api.expect_put_keys().never();
348        });
349
350        let request = TdeRegistrationRequest {
351            org_id: TEST_ORG_ID.parse().unwrap(),
352            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
353            user_id: TEST_USER_ID.parse().unwrap(),
354            device_identifier: TEST_DEVICE_ID.to_string(),
355            trust_device: true,
356        };
357
358        let result =
359            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
360                .await;
361
362        assert!(result.is_err());
363        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
364
365        // Assert that the mock expectations were met
366        if let ApiClient::Mock(mut mock) = api_client {
367            mock.accounts_api.checkpoint();
368            mock.organization_users_api.checkpoint();
369            mock.devices_api.checkpoint();
370        }
371    }
372
373    #[tokio::test]
374    async fn test_post_keys_for_tde_registration_reset_password_enrollment_failure() {
375        let client = Client::new(None);
376        let registration_client = RegistrationClient::new(client);
377
378        let api_client = ApiClient::new_mocked(|mock| {
379            mock.accounts_api
380                .expect_post_keys()
381                .once()
382                .returning(move |_body| {
383                    Ok(KeysResponseModel {
384                        object: None,
385                        key: None,
386                        public_key: None,
387                        private_key: None,
388                        account_keys: None,
389                    })
390                });
391            mock.organization_users_api
392                .expect_put_reset_password_enrollment()
393                .once()
394                .returning(move |_org_id, _user_id, _body| {
395                    Err(serde_json::Error::io(std::io::Error::other("API error")).into())
396                });
397            // Device key enrollment should not be made if reset password enrollment fails
398            mock.devices_api.expect_put_keys().never();
399        });
400
401        let request = TdeRegistrationRequest {
402            org_id: TEST_ORG_ID.parse().unwrap(),
403            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
404            user_id: TEST_USER_ID.parse().unwrap(),
405            device_identifier: TEST_DEVICE_ID.to_string(),
406            trust_device: true,
407        };
408
409        let result =
410            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
411                .await;
412
413        assert!(result.is_err());
414        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
415
416        // Assert that the mock expectations were met
417        if let ApiClient::Mock(mut mock) = api_client {
418            mock.accounts_api.checkpoint();
419            mock.organization_users_api.checkpoint();
420            mock.devices_api.checkpoint();
421        }
422    }
423
424    #[tokio::test]
425    async fn test_post_keys_for_tde_registration_device_keys_failure() {
426        let client = Client::new(None);
427        let registration_client = RegistrationClient::new(client);
428
429        let api_client = ApiClient::new_mocked(|mock| {
430            mock.accounts_api
431                .expect_post_keys()
432                .once()
433                .returning(move |_body| {
434                    Ok(KeysResponseModel {
435                        object: None,
436                        key: None,
437                        public_key: None,
438                        private_key: None,
439                        account_keys: None,
440                    })
441                });
442            mock.organization_users_api
443                .expect_put_reset_password_enrollment()
444                .once()
445                .returning(move |_org_id, _user_id, _body| Ok(()));
446            mock.devices_api
447                .expect_put_keys()
448                .once()
449                .returning(move |_device_id, _body| {
450                    Err(serde_json::Error::io(std::io::Error::other("API error")).into())
451                });
452        });
453
454        let request = TdeRegistrationRequest {
455            org_id: TEST_ORG_ID.parse().unwrap(),
456            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
457            user_id: TEST_USER_ID.parse().unwrap(),
458            device_identifier: TEST_DEVICE_ID.to_string(),
459            trust_device: true, // trust_device is true, so device enrollment should be attempted
460        };
461
462        let result =
463            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
464                .await;
465
466        assert!(result.is_err());
467        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
468
469        // Assert that the mock expectations were met
470        if let ApiClient::Mock(mut mock) = api_client {
471            mock.accounts_api.checkpoint();
472            mock.organization_users_api.checkpoint();
473            mock.devices_api.checkpoint();
474        }
475    }
476}