Skip to main content

bitwarden_user_crypto_management/key_rotation/
mod.rs

1//! Client to manage the cryptographic machinery of a user account, including key-rotation
2mod crypto;
3mod data;
4mod partial_rotateable_keyset;
5mod sync;
6mod unlock;
7
8use bitwarden_api_api::models::RotateUserAccountKeysAndDataRequestModel;
9use bitwarden_core::{
10    UserId,
11    key_management::{MasterPasswordAuthenticationData, SymmetricKeyId},
12};
13use bitwarden_crypto::PublicKey;
14use bitwarden_error::bitwarden_error;
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17use tracing::{debug, info, info_span, warn};
18#[cfg(feature = "wasm")]
19use tsify::Tsify;
20#[cfg(feature = "wasm")]
21use wasm_bindgen::prelude::*;
22
23use crate::{
24    UserCryptoManagementClient,
25    key_rotation::{
26        crypto::rotate_account_cryptographic_state,
27        data::reencrypt_data,
28        unlock::{
29            ReencryptUnlockInput, V1EmergencyAccessMembership, V1OrganizationMembership,
30            reencrypt_unlock,
31        },
32    },
33};
34
35#[derive(Serialize, Deserialize, Debug, Clone)]
36#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
37#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
38pub enum MasterkeyUnlockMethod {
39    Password {
40        old_password: String,
41        password: String,
42        hint: Option<String>,
43    },
44    /// Unlock via key-connector.
45    /// NOTE: This is not yet implemented, and will panic
46    KeyConnector,
47    /// No masterkey-based unlock.
48    /// NOTE: This is not yet implemented, and will panic
49    None,
50}
51
52#[derive(Serialize, Deserialize, Clone)]
53#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
54#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
55pub struct RotateUserKeysRequest {
56    pub master_key_unlock_method: MasterkeyUnlockMethod,
57    pub trusted_emergency_access_public_keys: Vec<PublicKey>,
58    pub trusted_organization_public_keys: Vec<PublicKey>,
59}
60
61#[cfg_attr(feature = "wasm", wasm_bindgen)]
62impl UserCryptoManagementClient {
63    /// Rotates the user's encryption keys. The user must have a master-password.
64    pub async fn rotate_user_keys(
65        &self,
66        request: RotateUserKeysRequest,
67    ) -> Result<(), RotateUserKeysError> {
68        let api_client = &self
69            .client
70            .internal
71            .get_api_configurations()
72            .await
73            .api_client;
74
75        post_rotate_user_keys(
76            self,
77            api_client,
78            request.trusted_organization_public_keys.as_slice(),
79            request.trusted_emergency_access_public_keys.as_slice(),
80            request.master_key_unlock_method,
81        )
82        .await
83    }
84
85    /// Fetches the organization public keys for V1 organization memberships for the user for
86    /// organizations for which reset password is enrolled.
87    /// These have to be trusted manually be the user before rotating.
88    pub async fn get_untrusted_organization_public_keys(
89        &self,
90    ) -> Result<Vec<V1OrganizationMembership>, RotateUserKeysError> {
91        let api_client = &self
92            .client
93            .internal
94            .get_api_configurations()
95            .await
96            .api_client;
97        let organizations = sync::sync_orgs(api_client)
98            .await
99            .map_err(|_| RotateUserKeysError::ApiError)?;
100        Ok(organizations)
101    }
102
103    /// Fetches the emergency access public keys for V1 emergency access memberships for the user.
104    /// These have to be trusted manually be the user before rotating.
105    pub async fn get_untrusted_emergency_access_public_keys(
106        &self,
107    ) -> Result<Vec<V1EmergencyAccessMembership>, RotateUserKeysError> {
108        let api_client = &self
109            .client
110            .internal
111            .get_api_configurations()
112            .await
113            .api_client;
114        let emergency_access = sync::sync_emergency_access(api_client)
115            .await
116            .map_err(|_| RotateUserKeysError::ApiError)?;
117        Ok(emergency_access)
118    }
119}
120
121#[derive(Debug, Error)]
122#[bitwarden_error(flat)]
123pub enum RotateUserKeysError {
124    #[error("API error during key rotation")]
125    ApiError,
126    #[error("Cryptographic error during key rotation")]
127    CryptoError,
128    #[error("Invalid public key provided during key rotation")]
129    InvalidPublicKey,
130    #[error("Untrusted key encountered during key rotation")]
131    UntrustedKeyError,
132}
133
134struct UntrustedKeyError;
135
136fn filter_trusted_organization(
137    org: &[V1OrganizationMembership],
138    trusted_orgs: &[PublicKey],
139) -> Result<Vec<V1OrganizationMembership>, UntrustedKeyError> {
140    org.iter()
141        .map(|o| {
142            let is_trusted = trusted_orgs.iter().any(|tk| tk == &o.public_key);
143            if !is_trusted {
144                warn!(
145                    "Filtering out untrusted organization with id={}",
146                    o.organization_id
147                );
148                Err(UntrustedKeyError)
149            } else {
150                Ok(o.clone())
151            }
152        })
153        .collect::<Result<Vec<V1OrganizationMembership>, UntrustedKeyError>>()
154}
155
156fn filter_trusted_emergency_access(
157    ea: &[V1EmergencyAccessMembership],
158    trusted_emergency_access_user_public_keys: &[PublicKey],
159) -> Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError> {
160    ea.iter()
161        .map(|e| {
162            let is_trusted = trusted_emergency_access_user_public_keys
163                .iter()
164                .any(|tk| tk == &e.public_key);
165            if !is_trusted {
166                warn!(
167                    "Filtering out untrusted emergency access membership with id={}",
168                    e.id
169                );
170                Err(UntrustedKeyError)
171            } else {
172                Ok(e.to_owned())
173            }
174        })
175        .collect::<Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError>>()
176}
177
178async fn post_rotate_user_keys(
179    registration_client: &UserCryptoManagementClient,
180    api_client: &bitwarden_api_api::apis::ApiClient,
181
182    trusted_organization_public_keys: &[PublicKey],
183    trusted_emergency_access_public_keys: &[PublicKey],
184
185    master_key_unlock_method: MasterkeyUnlockMethod,
186) -> Result<(), RotateUserKeysError> {
187    let _span = info_span!("rotate_user_keys").entered();
188    let sync = sync::sync_current_account_data(api_client)
189        .await
190        .map_err(|_| RotateUserKeysError::ApiError)?;
191
192    let key_store = registration_client.client.internal.get_key_store();
193    // Create a separate scope so that the mutable context is not held across the await point
194    let request = {
195        let mut ctx = key_store.context_mut();
196
197        // Filter organization memberships and emergency access memberships to only include trusted
198        // keys
199        let v1_organization_memberships = filter_trusted_organization(
200            sync.organization_memberships.as_slice(),
201            trusted_organization_public_keys,
202        )
203        .map_err(|_| RotateUserKeysError::UntrustedKeyError)?;
204        let v1_emergency_access_memberships = filter_trusted_emergency_access(
205            sync.emergency_access_memberships.as_slice(),
206            trusted_emergency_access_public_keys,
207        )
208        .map_err(|_| RotateUserKeysError::UntrustedKeyError)?;
209
210        info!(
211            "Existing user cryptographic version {:?}",
212            sync.wrapped_account_cryptographic_state
213        );
214        let current_user_key_id = SymmetricKeyId::User;
215
216        debug!("Generating new xchacha20-poly1305 user key for key rotation");
217        let new_user_key_id =
218            ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::XChaCha20Poly1305);
219
220        info!("Rotating account cryptographic state for user key rotation");
221        let account_keys_model = rotate_account_cryptographic_state(
222            &sync.wrapped_account_cryptographic_state,
223            &current_user_key_id,
224            &new_user_key_id,
225            UserId::new(sync.user_id),
226            &mut ctx,
227        )
228        .map_err(|_| RotateUserKeysError::CryptoError)?;
229
230        info!("Re-encrypting account data for user key rotation");
231        let account_data_model = reencrypt_data(
232            sync.folders.as_slice(),
233            sync.ciphers.as_slice(),
234            sync.sends.as_slice(),
235            current_user_key_id,
236            new_user_key_id,
237            &mut ctx,
238        )
239        .map_err(|_| RotateUserKeysError::CryptoError)?;
240
241        info!("Re-encrypting account unlock data for user key rotation");
242        let unlock_data_model = reencrypt_unlock(
243            ReencryptUnlockInput {
244                master_key_unlock_method: match master_key_unlock_method {
245                    MasterkeyUnlockMethod::Password {
246                        old_password: _,
247                        ref password,
248                        ref hint,
249                    } => {
250                        let (kdf, salt) = sync
251                            .kdf_and_salt
252                            .clone()
253                            .ok_or(RotateUserKeysError::ApiError)?;
254                        unlock::MasterkeyUnlockMethod::Password {
255                            password: password.to_owned(),
256                            hint: hint.to_owned(),
257                            kdf,
258                            salt,
259                        }
260                    }
261                    MasterkeyUnlockMethod::KeyConnector => {
262                        unlock::MasterkeyUnlockMethod::KeyConnector
263                    }
264                    MasterkeyUnlockMethod::None => unlock::MasterkeyUnlockMethod::None,
265                },
266                trusted_devices: sync.trusted_devices,
267                webauthn_credentials: sync.passkeys,
268                trusted_organization_keys: v1_organization_memberships,
269                trusted_emergency_access_keys: v1_emergency_access_memberships,
270            },
271            current_user_key_id,
272            new_user_key_id,
273            &mut ctx,
274        )
275        .map_err(|_| RotateUserKeysError::CryptoError)?;
276
277        let old_masterpassword_authentication_data = match master_key_unlock_method {
278            MasterkeyUnlockMethod::Password {
279                old_password,
280                password: _,
281                hint: _,
282            } => {
283                let (kdf, salt) = sync
284                    .kdf_and_salt
285                    .clone()
286                    .ok_or(RotateUserKeysError::ApiError)?;
287                let authentication_data =
288                    MasterPasswordAuthenticationData::derive(&old_password, &kdf, &salt)
289                        .map_err(|_| RotateUserKeysError::CryptoError)?;
290                Some(authentication_data)
291            }
292            MasterkeyUnlockMethod::KeyConnector => {
293                tracing::error!("Key-connector based key rotation is not yet implemented");
294                None
295            }
296            MasterkeyUnlockMethod::None => {
297                tracing::error!(
298                    "Key-rotation without master-key based unlock is not supported yet"
299                );
300                None
301            }
302        }
303        .expect("Master password authentication data is required for password-based key rotation");
304        RotateUserAccountKeysAndDataRequestModel {
305            old_master_key_authentication_hash: Some(
306                old_masterpassword_authentication_data
307                    .master_password_authentication_hash
308                    .to_string(),
309            ),
310            account_keys: Box::new(account_keys_model),
311            account_data: Box::new(account_data_model),
312            account_unlock_data: Box::new(unlock_data_model),
313        }
314    };
315
316    info!("Posting rotated user account keys and data to server");
317    registration_client
318        .client
319        .internal
320        .get_api_configurations()
321        .await
322        .api_client
323        .accounts_key_management_api()
324        .rotate_user_account_keys(Some(request))
325        .await
326        .map_err(|_| RotateUserKeysError::ApiError)?;
327    info!("Successfully rotated user account keys and data");
328    Ok(())
329}