1use bitwarden_api_api::models::{
4 AccountDataRequestModel, CipherWithIdRequestModel, SendWithIdRequestModel,
5};
6use bitwarden_core::{
7 UserId,
8 key_management::{KeySlotIds, SymmetricKeySlotId},
9};
10use bitwarden_crypto::{CompositeEncryptable, Decryptable, KeyStoreContext};
11use bitwarden_send::SendView;
12use bitwarden_vault::{CipherView, EncryptionContext, FolderView};
13use tracing::{debug, debug_span, instrument};
14use uuid::Uuid;
15
16#[derive(Debug)]
18pub(crate) enum DataReencryptionError {
19 Decryption,
21 Encryption,
23 DataConversion,
25 CipherKeyRewrap,
27}
28
29#[instrument(name = "reencrypt_data", skip(folders, ciphers, sends, ctx))]
33pub(super) fn reencrypt_data(
34 folders: &[bitwarden_vault::Folder],
35 ciphers: &[bitwarden_vault::Cipher],
36 sends: &[bitwarden_send::Send],
37 current_user_key_id: SymmetricKeySlotId,
38 new_user_key_id: SymmetricKeySlotId,
39 ctx: &mut KeyStoreContext<KeySlotIds>,
40) -> Result<AccountDataRequestModel, DataReencryptionError> {
41 let reencrypted_folders =
43 reencrypt_folders(folders, current_user_key_id, new_user_key_id, ctx)?;
44 let reencrypted_ciphers =
45 reencrypt_ciphers(ciphers, current_user_key_id, new_user_key_id, ctx)?;
46 let reencrypted_sends = reencrypt_sends(sends, current_user_key_id, new_user_key_id, ctx)?;
47 Ok(AccountDataRequestModel {
48 folders: Some(
49 reencrypted_folders
50 .into_iter()
51 .map(|folder| (&folder).into())
52 .collect(),
53 ),
54 ciphers: Some(
55 reencrypted_ciphers
56 .into_iter()
57 .map(|cipher| {
58 EncryptionContext {
59 encrypted_for: UserId::new(Uuid::nil()),
62 cipher,
63 }
64 .try_into()
65 .map_err(|_| DataReencryptionError::DataConversion)
66 })
67 .collect::<Result<Vec<CipherWithIdRequestModel>, DataReencryptionError>>()?,
68 ),
69 sends: Some(
70 reencrypted_sends
71 .into_iter()
72 .map(|send| Ok(send.into()))
73 .collect::<Result<Vec<SendWithIdRequestModel>, DataReencryptionError>>()?,
74 ),
75 })
76}
77
78#[instrument(name = "reencrypt_folders", skip(folders, ctx))]
79fn reencrypt_folders(
80 folders: &[bitwarden_vault::Folder],
81 current_key: SymmetricKeySlotId,
82 new_key: SymmetricKeySlotId,
83 ctx: &mut KeyStoreContext<KeySlotIds>,
84) -> Result<Vec<bitwarden_vault::Folder>, DataReencryptionError> {
85 folders
86 .iter()
87 .map(|folder| {
88 let _span = debug_span!("reencrypt_folder", folder_id = ?folder.id).entered();
89 let folder_view: FolderView = folder
90 .decrypt(ctx, current_key)
91 .map_err(|_| DataReencryptionError::Decryption)?;
92 folder_view
93 .encrypt_composite(ctx, new_key)
94 .map_err(|_| DataReencryptionError::Encryption)
95 })
96 .collect::<Result<Vec<bitwarden_vault::Folder>, DataReencryptionError>>()
97}
98
99#[instrument(name = "reencrypt_ciphers", skip(ciphers, ctx))]
100fn reencrypt_ciphers(
101 ciphers: &[bitwarden_vault::Cipher],
102 current_key: SymmetricKeySlotId,
103 new_key: SymmetricKeySlotId,
104 ctx: &mut KeyStoreContext<KeySlotIds>,
105) -> Result<Vec<bitwarden_vault::Cipher>, DataReencryptionError> {
106 ciphers
107 .iter()
108 .map(|cipher| {
109 let _span = debug_span!("reencrypt_cipher", cipher_id = ?cipher.id).entered();
110
111 if cipher.key.is_some() {
114 debug!("Re-wrapping cipher key without decrypting cipher");
115 let mut cipher = cipher.clone();
116 cipher
117 .rewrap_cipher_key(current_key, new_key, ctx)
118 .map_err(|_| DataReencryptionError::CipherKeyRewrap)?;
119 Ok(cipher)
120
121 } else {
124 debug!("Cipher has no cipher key, decrypting and re-encrypting entire cipher");
125 let cipher_view: CipherView = cipher
126 .decrypt(ctx, current_key)
127 .map_err(|_| DataReencryptionError::Decryption)?;
128 cipher_view
129 .encrypt_composite(ctx, new_key)
130 .map_err(|_| DataReencryptionError::Encryption)
131 }
132 })
133 .collect::<Result<Vec<bitwarden_vault::Cipher>, DataReencryptionError>>()
134}
135
136#[instrument(name = "reencrypt_sends", skip(sends, ctx))]
137fn reencrypt_sends(
138 sends: &[bitwarden_send::Send],
139 current_key: SymmetricKeySlotId,
140 new_key: SymmetricKeySlotId,
141 ctx: &mut KeyStoreContext<KeySlotIds>,
142) -> Result<Vec<bitwarden_send::Send>, DataReencryptionError> {
143 sends
144 .iter()
145 .map(|send| {
146 let _span = debug_span!("reencrypt_send", send_id = ?send.id).entered();
147 let send_view: SendView = send
148 .decrypt(ctx, current_key)
149 .map_err(|_| DataReencryptionError::Decryption)?;
150 send_view
151 .encrypt_composite(ctx, new_key)
152 .map_err(|_| DataReencryptionError::Encryption)
153 })
154 .collect::<Result<Vec<bitwarden_send::Send>, DataReencryptionError>>()
155}
156
157#[cfg(test)]
158mod tests {
159 use bitwarden_core::key_management::KeySlotIds;
160 use bitwarden_crypto::{CompositeEncryptable, Decryptable, KeyStore};
161 use bitwarden_send::SendView;
162 use chrono::Utc;
163
164 #[test]
165 fn test_ciphers() {
166 use bitwarden_vault::{CipherType, CipherView, LoginView};
167 let store: KeyStore<KeySlotIds> = KeyStore::default();
168 let mut ctx = store.context_mut();
169
170 let user_key_old =
171 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
172 let user_key_new =
173 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
174
175 let cipher = CipherView {
176 id: None,
177 organization_id: None,
178 folder_id: None,
179 r#type: CipherType::Login,
180 name: "Test Cipher".to_string(),
181 notes: Some("Some cipher notes".to_string()),
182 favorite: false,
183 revision_date: Utc::now(),
184 deleted_date: None,
185 fields: None,
186 login: Some(LoginView {
187 username: Some("user".to_string()),
188 password: Some("pass".to_string()),
189 totp: None,
190 uris: None,
191 autofill_on_page_load: None,
192 fido2_credentials: None,
193 password_revision_date: None,
194 }),
195 card: None,
196 identity: None,
197 secure_note: None,
198 attachments: None,
199 attachment_decryption_failures: None,
200 organization_use_totp: false,
201 collection_ids: vec![],
202 reprompt: bitwarden_vault::CipherRepromptType::None,
203 local_data: None,
204 key: None,
205 ssh_key: None,
206 bank_account: None,
207 permissions: None,
208 view_password: false,
209 creation_date: Utc::now(),
210 archived_date: None,
211 edit: false,
212 password_history: None,
213 };
214 let encrypted_cipher = cipher.encrypt_composite(&mut ctx, user_key_old).unwrap();
215
216 let ciphers = vec![encrypted_cipher];
218 let reencrypted_ciphers =
219 super::reencrypt_ciphers(ciphers.as_slice(), user_key_old, user_key_new, &mut ctx)
220 .unwrap();
221
222 let decrypted_cipher: CipherView = reencrypted_ciphers[0]
224 .decrypt(&mut ctx, user_key_new)
225 .unwrap();
226 assert_eq!(cipher.name, decrypted_cipher.name);
227 assert_eq!(cipher.notes, decrypted_cipher.notes);
228 assert_eq!(cipher.r#type, decrypted_cipher.r#type);
229 assert_eq!(
230 cipher.login.as_ref().unwrap().username,
231 decrypted_cipher.login.as_ref().unwrap().username
232 );
233 assert_eq!(
234 cipher.login.as_ref().unwrap().password,
235 decrypted_cipher.login.as_ref().unwrap().password
236 );
237 }
238
239 #[test]
240 fn test_folders() {
241 let store: KeyStore<KeySlotIds> = KeyStore::default();
242 let mut ctx = store.context_mut();
243
244 let user_key_old =
245 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
246 let user_key_new =
247 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
248
249 let folder = bitwarden_vault::FolderView {
251 id: None,
252 name: "Test Folder".to_string(),
253 revision_date: Utc::now(),
254 };
255 let encrypted_folder = folder.encrypt_composite(&mut ctx, user_key_old).unwrap();
256
257 let folders = vec![encrypted_folder];
259 let reencrypted_folders =
260 super::reencrypt_folders(folders.as_slice(), user_key_old, user_key_new, &mut ctx)
261 .unwrap();
262
263 let decrypted_folder = reencrypted_folders[0]
265 .decrypt(&mut ctx, user_key_new)
266 .unwrap();
267 assert_eq!(folder, decrypted_folder);
268 }
269
270 #[test]
271 fn test_sends() {
272 let store: KeyStore<KeySlotIds> = KeyStore::default();
273 let mut ctx = store.context_mut();
274
275 let user_key_old =
276 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
277 let user_key_new =
278 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
279
280 let send = bitwarden_send::SendView {
282 id: None,
283 access_id: None,
284 name: "Test Send".to_string(),
285 notes: Some("Some notes".to_string()),
286 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
287 text: Some(bitwarden_send::SendTextView {
288 text: Some("This is a test send".to_string()),
289 hidden: false,
290 }),
291 r#type: bitwarden_send::SendType::Text,
292 max_access_count: None,
293 access_count: 0,
294 disabled: false,
295 hide_email: false,
296 revision_date: Utc::now(),
297 deletion_date: Utc::now(),
298 expiration_date: None,
299 new_password: None,
300 has_password: false,
301 file: None,
302 emails: vec![],
303 auth_type: bitwarden_send::AuthType::None,
304 };
305 let encrypted_send = send.encrypt_composite(&mut ctx, user_key_old).unwrap();
306
307 let sends = vec![encrypted_send];
309 let reencrypted_sends =
310 super::reencrypt_sends(sends.as_slice(), user_key_old, user_key_new, &mut ctx).unwrap();
311
312 let decrypted_send: SendView = reencrypted_sends[0]
314 .decrypt(&mut ctx, user_key_new)
315 .unwrap();
316
317 assert_eq!(send.key, decrypted_send.key);
319 }
320}