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