1use bitwarden_api_api::models::{
4 AccountDataRequestModel, CipherWithIdRequestModel, SendWithIdRequestModel,
5};
6use bitwarden_core::{
7 UserId,
8 key_management::{KeyIds, SymmetricKeyId},
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: SymmetricKeyId,
38 new_user_key_id: SymmetricKeyId,
39 ctx: &mut KeyStoreContext<KeyIds>,
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| {
73 Ok(SendWithIdRequestModel {
74 id: send.id.ok_or(DataReencryptionError::DataConversion)?,
75 key: send.key.to_string(),
76 ..Default::default()
79 })
80 })
81 .collect::<Result<Vec<SendWithIdRequestModel>, DataReencryptionError>>()?,
82 ),
83 })
84}
85
86#[instrument(name = "reencrypt_folders", skip(folders, ctx))]
87fn reencrypt_folders(
88 folders: &[bitwarden_vault::Folder],
89 current_key: SymmetricKeyId,
90 new_key: SymmetricKeyId,
91 ctx: &mut KeyStoreContext<KeyIds>,
92) -> Result<Vec<bitwarden_vault::Folder>, DataReencryptionError> {
93 folders
94 .iter()
95 .map(|folder| {
96 let _span = debug_span!("reencrypt_folder", folder_id = ?folder.id).entered();
97 let folder_view: FolderView = folder
98 .decrypt(ctx, current_key)
99 .map_err(|_| DataReencryptionError::Decryption)?;
100 folder_view
101 .encrypt_composite(ctx, new_key)
102 .map_err(|_| DataReencryptionError::Encryption)
103 })
104 .collect::<Result<Vec<bitwarden_vault::Folder>, DataReencryptionError>>()
105}
106
107#[instrument(name = "reencrypt_ciphers", skip(ciphers, ctx))]
108fn reencrypt_ciphers(
109 ciphers: &[bitwarden_vault::Cipher],
110 current_key: SymmetricKeyId,
111 new_key: SymmetricKeyId,
112 ctx: &mut KeyStoreContext<KeyIds>,
113) -> Result<Vec<bitwarden_vault::Cipher>, DataReencryptionError> {
114 ciphers
115 .iter()
116 .map(|cipher| {
117 let _span = debug_span!("reencrypt_cipher", cipher_id = ?cipher.id).entered();
118
119 if cipher.key.is_some() {
122 debug!("Re-wrapping cipher key without decrypting cipher");
123 let mut cipher = cipher.clone();
124 cipher
125 .rewrap_cipher_key(current_key, new_key, ctx)
126 .map_err(|_| DataReencryptionError::CipherKeyRewrap)?;
127 Ok(cipher)
128
129 } else {
132 debug!("Cipher has no cipher key, decrypting and re-encrypting entire cipher");
133 let cipher_view: CipherView = cipher
134 .decrypt(ctx, current_key)
135 .map_err(|_| DataReencryptionError::Decryption)?;
136 cipher_view
137 .encrypt_composite(ctx, new_key)
138 .map_err(|_| DataReencryptionError::Encryption)
139 }
140 })
141 .collect::<Result<Vec<bitwarden_vault::Cipher>, DataReencryptionError>>()
142}
143
144#[instrument(name = "reencrypt_sends", skip(sends, ctx))]
145fn reencrypt_sends(
146 sends: &[bitwarden_send::Send],
147 current_key: SymmetricKeyId,
148 new_key: SymmetricKeyId,
149 ctx: &mut KeyStoreContext<KeyIds>,
150) -> Result<Vec<bitwarden_send::Send>, DataReencryptionError> {
151 sends
152 .iter()
153 .map(|send| {
154 let _span = debug_span!("reencrypt_send", send_id = ?send.id).entered();
155 let send_view: SendView = send
156 .decrypt(ctx, current_key)
157 .map_err(|_| DataReencryptionError::Decryption)?;
158 send_view
159 .encrypt_composite(ctx, new_key)
160 .map_err(|_| DataReencryptionError::Encryption)
161 })
162 .collect::<Result<Vec<bitwarden_send::Send>, DataReencryptionError>>()
163}
164
165#[cfg(test)]
166mod tests {
167 use bitwarden_core::key_management::KeyIds;
168 use bitwarden_crypto::{CompositeEncryptable, Decryptable, KeyStore};
169 use bitwarden_send::SendView;
170 use chrono::Utc;
171
172 #[test]
173 fn test_ciphers() {
174 use bitwarden_vault::{CipherType, CipherView, LoginView};
175 let store: KeyStore<KeyIds> = KeyStore::default();
176 let mut ctx = store.context_mut();
177
178 let user_key_old =
179 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
180 let user_key_new =
181 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
182
183 let cipher = CipherView {
184 id: None,
185 organization_id: None,
186 folder_id: None,
187 r#type: CipherType::Login,
188 name: "Test Cipher".to_string(),
189 notes: Some("Some cipher notes".to_string()),
190 favorite: false,
191 revision_date: Utc::now(),
192 deleted_date: None,
193 fields: None,
194 login: Some(LoginView {
195 username: Some("user".to_string()),
196 password: Some("pass".to_string()),
197 totp: None,
198 uris: None,
199 autofill_on_page_load: None,
200 fido2_credentials: None,
201 password_revision_date: None,
202 }),
203 card: None,
204 identity: None,
205 secure_note: None,
206 attachments: None,
207 attachment_decryption_failures: None,
208 organization_use_totp: false,
209 collection_ids: vec![],
210 reprompt: bitwarden_vault::CipherRepromptType::None,
211 local_data: None,
212 key: None,
213 ssh_key: None,
214 permissions: None,
215 view_password: false,
216 creation_date: Utc::now(),
217 archived_date: None,
218 edit: false,
219 password_history: None,
220 };
221 let encrypted_cipher = cipher.encrypt_composite(&mut ctx, user_key_old).unwrap();
222
223 let ciphers = vec![encrypted_cipher];
225 let reencrypted_ciphers =
226 super::reencrypt_ciphers(ciphers.as_slice(), user_key_old, user_key_new, &mut ctx)
227 .unwrap();
228
229 let decrypted_cipher: CipherView = reencrypted_ciphers[0]
231 .decrypt(&mut ctx, user_key_new)
232 .unwrap();
233 assert_eq!(cipher.name, decrypted_cipher.name);
234 assert_eq!(cipher.notes, decrypted_cipher.notes);
235 assert_eq!(cipher.r#type, decrypted_cipher.r#type);
236 assert_eq!(
237 cipher.login.as_ref().unwrap().username,
238 decrypted_cipher.login.as_ref().unwrap().username
239 );
240 assert_eq!(
241 cipher.login.as_ref().unwrap().password,
242 decrypted_cipher.login.as_ref().unwrap().password
243 );
244 }
245
246 #[test]
247 fn test_folders() {
248 let store: KeyStore<KeyIds> = KeyStore::default();
249 let mut ctx = store.context_mut();
250
251 let user_key_old =
252 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
253 let user_key_new =
254 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
255
256 let folder = bitwarden_vault::FolderView {
258 id: None,
259 name: "Test Folder".to_string(),
260 revision_date: Utc::now(),
261 };
262 let encrypted_folder = folder.encrypt_composite(&mut ctx, user_key_old).unwrap();
263
264 let folders = vec![encrypted_folder];
266 let reencrypted_folders =
267 super::reencrypt_folders(folders.as_slice(), user_key_old, user_key_new, &mut ctx)
268 .unwrap();
269
270 let decrypted_folder = reencrypted_folders[0]
272 .decrypt(&mut ctx, user_key_new)
273 .unwrap();
274 assert_eq!(folder, decrypted_folder);
275 }
276
277 #[test]
278 fn test_sends() {
279 let store: KeyStore<KeyIds> = KeyStore::default();
280 let mut ctx = store.context_mut();
281
282 let user_key_old =
283 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
284 let user_key_new =
285 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
286
287 let send = bitwarden_send::SendView {
289 id: None,
290 access_id: None,
291 name: "Test Send".to_string(),
292 notes: Some("Some notes".to_string()),
293 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
294 text: Some(bitwarden_send::SendTextView {
295 text: Some("This is a test send".to_string()),
296 hidden: false,
297 }),
298 r#type: bitwarden_send::SendType::Text,
299 max_access_count: None,
300 access_count: 0,
301 disabled: false,
302 hide_email: false,
303 revision_date: Utc::now(),
304 deletion_date: Utc::now(),
305 expiration_date: None,
306 new_password: None,
307 has_password: false,
308 file: None,
309 emails: vec![],
310 auth_type: bitwarden_send::AuthType::None,
311 };
312 let encrypted_send = send.encrypt_composite(&mut ctx, user_key_old).unwrap();
313
314 let sends = vec![encrypted_send];
316 let reencrypted_sends =
317 super::reencrypt_sends(sends.as_slice(), user_key_old, user_key_new, &mut ctx).unwrap();
318
319 let decrypted_send: SendView = reencrypted_sends[0]
321 .decrypt(&mut ctx, user_key_new)
322 .unwrap();
323
324 assert_eq!(send.key, decrypted_send.key);
326 }
327}