bitwarden_user_crypto_management/key_rotation/
mod.rs1mod 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 KeyConnector,
44 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 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 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 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 let request = {
177 let mut ctx = key_store.context_mut();
178
179 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 ¤t_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}