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