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