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| 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: SymmetricKeyId,
82 new_key: SymmetricKeyId,
83 ctx: &mut KeyStoreContext<KeyIds>,
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: SymmetricKeyId,
103 new_key: SymmetricKeyId,
104 ctx: &mut KeyStoreContext<KeyIds>,
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: SymmetricKeyId,
140 new_key: SymmetricKeyId,
141 ctx: &mut KeyStoreContext<KeyIds>,
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::KeyIds;
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<KeyIds> = 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 permissions: None,
207 view_password: false,
208 creation_date: Utc::now(),
209 archived_date: None,
210 edit: false,
211 password_history: None,
212 };
213 let encrypted_cipher = cipher.encrypt_composite(&mut ctx, user_key_old).unwrap();
214
215 let ciphers = vec![encrypted_cipher];
217 let reencrypted_ciphers =
218 super::reencrypt_ciphers(ciphers.as_slice(), user_key_old, user_key_new, &mut ctx)
219 .unwrap();
220
221 let decrypted_cipher: CipherView = reencrypted_ciphers[0]
223 .decrypt(&mut ctx, user_key_new)
224 .unwrap();
225 assert_eq!(cipher.name, decrypted_cipher.name);
226 assert_eq!(cipher.notes, decrypted_cipher.notes);
227 assert_eq!(cipher.r#type, decrypted_cipher.r#type);
228 assert_eq!(
229 cipher.login.as_ref().unwrap().username,
230 decrypted_cipher.login.as_ref().unwrap().username
231 );
232 assert_eq!(
233 cipher.login.as_ref().unwrap().password,
234 decrypted_cipher.login.as_ref().unwrap().password
235 );
236 }
237
238 #[test]
239 fn test_folders() {
240 let store: KeyStore<KeyIds> = KeyStore::default();
241 let mut ctx = store.context_mut();
242
243 let user_key_old =
244 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
245 let user_key_new =
246 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
247
248 let folder = bitwarden_vault::FolderView {
250 id: None,
251 name: "Test Folder".to_string(),
252 revision_date: Utc::now(),
253 };
254 let encrypted_folder = folder.encrypt_composite(&mut ctx, user_key_old).unwrap();
255
256 let folders = vec![encrypted_folder];
258 let reencrypted_folders =
259 super::reencrypt_folders(folders.as_slice(), user_key_old, user_key_new, &mut ctx)
260 .unwrap();
261
262 let decrypted_folder = reencrypted_folders[0]
264 .decrypt(&mut ctx, user_key_new)
265 .unwrap();
266 assert_eq!(folder, decrypted_folder);
267 }
268
269 #[test]
270 fn test_sends() {
271 let store: KeyStore<KeyIds> = KeyStore::default();
272 let mut ctx = store.context_mut();
273
274 let user_key_old =
275 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
276 let user_key_new =
277 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
278
279 let send = bitwarden_send::SendView {
281 id: None,
282 access_id: None,
283 name: "Test Send".to_string(),
284 notes: Some("Some notes".to_string()),
285 key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
286 text: Some(bitwarden_send::SendTextView {
287 text: Some("This is a test send".to_string()),
288 hidden: false,
289 }),
290 r#type: bitwarden_send::SendType::Text,
291 max_access_count: None,
292 access_count: 0,
293 disabled: false,
294 hide_email: false,
295 revision_date: Utc::now(),
296 deletion_date: Utc::now(),
297 expiration_date: None,
298 new_password: None,
299 has_password: false,
300 file: None,
301 emails: vec![],
302 auth_type: bitwarden_send::AuthType::None,
303 };
304 let encrypted_send = send.encrypt_composite(&mut ctx, user_key_old).unwrap();
305
306 let sends = vec![encrypted_send];
308 let reencrypted_sends =
309 super::reencrypt_sends(sends.as_slice(), user_key_old, user_key_new, &mut ctx).unwrap();
310
311 let decrypted_send: SendView = reencrypted_sends[0]
313 .decrypt(&mut ctx, user_key_new)
314 .unwrap();
315
316 assert_eq!(send.key, decrypted_send.key);
318 }
319}