1use super::{
6 BLOCK_SIZE, Cipher, MAX_FILENAME_LEN, MAX_SYMLINK_LEN, Tweak, UnwrappedKey,
7 XtsInPlaceProcessor, decrypt_filename_cts, encrypt_filename_cts,
8};
9use aes::Aes256;
10use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
11use anyhow::{Context, Error};
12use siphasher::sip::SipHasher;
13use std::hash::Hasher;
14use storage_ptr_slice::MutPtrByteSlice;
15use zerocopy::IntoBytes;
16
17#[derive(Debug)]
18pub(crate) struct FscryptInoLblk32DirCipher {
19 cts_key: [u8; 32],
20 ino_hash_key: [u8; 16],
21 dir_hash_key: [u8; 16],
22}
23impl FscryptInoLblk32DirCipher {
24 pub fn new(key: &UnwrappedKey) -> Self {
25 Self {
26 cts_key: key[..32].try_into().unwrap(),
27 ino_hash_key: key[32..48].try_into().unwrap(),
28 dir_hash_key: key[48..64].try_into().unwrap(),
29 }
30 }
31}
32impl Cipher for FscryptInoLblk32DirCipher {
33 fn encrypt(
34 &self,
35 _ino: u64,
36 _attribute_id: u64,
37 _device_offset: u64,
38 _file_offset: u64,
39 _buffer: MutPtrByteSlice<'_>,
40 ) -> Result<(), Error> {
41 Err(zx_status::Status::NOT_SUPPORTED).context("encrypt not supported for InoLblk32Dir")
42 }
43
44 fn decrypt(
45 &self,
46 _ino: u64,
47 _attribute_id: u64,
48 _device_offset: u64,
49 _file_offset: u64,
50 _buffer: MutPtrByteSlice<'_>,
51 ) -> Result<(), Error> {
52 Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk32Dir")
53 }
54
55 fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
56 self.encrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
57 }
58
59 fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
60 self.decrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
61 }
62
63 fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
64 self.encrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
65 }
66
67 fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
68 self.decrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
69 }
70
71 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
72 None
73 }
74
75 fn hash_code_casefold(&self, filename: &str) -> u32 {
76 fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
77 }
78
79 fn supports_inline_encryption(&self) -> bool {
80 false
81 }
82
83 fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u64, u8)> {
84 None
85 }
86}
87
88impl FscryptInoLblk32DirCipher {
89 fn iv(&self, object_id: u64) -> [u32; 4] {
90 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
91 hasher.write(object_id.as_bytes());
92 [hasher.finish() as u32, 0, 0, 0]
93 }
94
95 fn encrypt_filename_with_max_len(
96 &self,
97 object_id: u64,
98 buffer: &mut Vec<u8>,
99 max_len: usize,
100 ) -> Result<(), Error> {
101 let iv = self.iv(object_id);
102 encrypt_filename_cts(&self.cts_key, &iv, buffer, max_len)
103 }
104
105 fn decrypt_filename_with_max_len(
106 &self,
107 object_id: u64,
108 buffer: &mut Vec<u8>,
109 max_len: usize,
110 ) -> Result<(), Error> {
111 let iv = self.iv(object_id);
112 decrypt_filename_cts(&self.cts_key, &iv, buffer, max_len)
113 }
114}
115
116#[derive(Debug)]
117pub struct FscryptInoLblk32FileCipher {
118 slot: u8,
119 ino_hash_key: [u8; 16],
120}
121
122impl FscryptInoLblk32FileCipher {
123 pub fn new(key: &UnwrappedKey) -> Self {
124 Self { slot: key.slot().unwrap(), ino_hash_key: key[..16].try_into().unwrap() }
125 }
126
127 #[inline(always)]
128 fn tweak(&self, ino: u64, block_num: u64) -> u32 {
129 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
130 hasher.write(ino.as_bytes());
131 (hasher.finish().wrapping_add(block_num)) as u32
132 }
133}
134
135impl Cipher for FscryptInoLblk32FileCipher {
138 fn encrypt(
139 &self,
140 _ino: u64,
141 _attribute_id: u64,
142 _device_offset: u64,
143 _file_offset: u64,
144 _buffer: MutPtrByteSlice<'_>,
145 ) -> Result<(), Error> {
146 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
147 Err(e.context("encrypt not supported for InoLblk32File"))
148 }
149
150 fn decrypt(
151 &self,
152 _ino: u64,
153 _attribute_id: u64,
154 _device_offset: u64,
155 _file_offset: u64,
156 _buffer: MutPtrByteSlice<'_>,
157 ) -> Result<(), Error> {
158 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
159 Err(e.context("decrypt not supported for InoLblk32File"))
160 }
161
162 fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
163 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
164 Err(e.context("encrypt_filename not supported for InoLblk32File"))
165 }
166
167 fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
168 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
169 Err(e.context("decrypt_filename not supported for InoLblk32File"))
170 }
171
172 fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
173 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
174 Err(e.context("encrypt_symlink not supported for InoLblk32File"))
175 }
176
177 fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
178 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
179 Err(e.context("decrypt_symlink not supported for InoLblk32File"))
180 }
181
182 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
183 debug_assert!(false, "hash_code called on file cipher");
184 None
185 }
186
187 fn hash_code_casefold(&self, _filename: &str) -> u32 {
188 debug_assert!(false, "hash_code_casefold called on file cipher");
189 0
190 }
191
192 fn supports_inline_encryption(&self) -> bool {
193 true
194 }
195
196 fn crypt_ctx(&self, ino: u64, _attribute_id: u64, file_offset: u64) -> Option<(u64, u8)> {
197 assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
198 let block_num = file_offset / BLOCK_SIZE as u64;
199 let tweak = self.tweak(ino, block_num);
200 Some((tweak as u64, self.slot))
201 }
202}
203
204#[derive(Debug)]
206pub struct FscryptSoftwareInoLblk32FileCipher {
207 xts_key1: Aes256,
208 xts_key2: Aes256,
209}
210
211impl FscryptSoftwareInoLblk32FileCipher {
212 pub fn new(key: &UnwrappedKey) -> Self {
213 Self {
214 xts_key1: Aes256::new((&key[..32]).try_into().unwrap()),
215 xts_key2: Aes256::new((&key[32..64]).try_into().unwrap()),
216 }
217 }
218
219 pub fn encrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
220 fxfs_trace::duration!("encrypt", "len" => buffer.len());
221 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
222
223 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
224 let mut block_tweak = tweak;
225 self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
226 self.xts_key1.encrypt_with_backend(XtsInPlaceProcessor::new(
227 Tweak(block_tweak),
228 MutPtrByteSlice::from(&mut block[..]),
229 ));
230 tweak += 1;
231 }
232 Ok(())
233 }
234
235 pub fn decrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
236 fxfs_trace::duration!("decrypt", "len" => buffer.len());
237 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
238 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
239 let mut block_tweak = tweak;
240 self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
241 self.xts_key1.decrypt_with_backend(XtsInPlaceProcessor::new(
242 Tweak(block_tweak),
243 MutPtrByteSlice::from(&mut block[..]),
244 ));
245 tweak += 1;
246 }
247 Ok(())
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::{
254 BLOCK_SIZE, FscryptInoLblk32DirCipher, FscryptInoLblk32FileCipher,
255 FscryptSoftwareInoLblk32FileCipher, UnwrappedKey,
256 };
257 use crate::Cipher;
258 use crate::cipher::fscrypt_test_data;
259 use fscrypt::proxy_filename::ProxyFilename;
260 use std::sync::Arc;
261
262 #[test]
263 fn test_encrypt_filename() {
264 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
265 unwrapped_key[0] = 0x10;
266 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
267 let object_id = 2;
268
269 let mut text = b"filename".to_vec();
276 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
277 assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
278
279 let mut text = b"0123456789abcdef_filename".to_vec();
289 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
290 assert_eq!(
291 text,
292 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
293 .expect("decode failed")
294 );
295
296 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
321 let mut text = vec![];
322 for _ in 0..3 {
323 text.extend_from_slice(long_name_64);
324 }
325
326 let raw = hex::decode(concat!(
327 "f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87",
328 "c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf89",
329 "0bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1a",
330 "ef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20",
331 "119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd13587",
332 "08a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf"
333 ))
334 .expect("decode failed");
335 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
336 assert_eq!(text, raw);
337 }
338
339 #[test]
340 fn test_decrypt_filename() {
341 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
347 unwrapped_key[0] = 0x10;
348 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
349 let object_id = 2;
350
351 let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
353 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
354 assert_eq!(text, b"filename".to_vec());
355
356 let mut text =
358 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
359 .expect("decode failed");
360 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
361 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
362 }
363
364 #[test]
365 fn test_generated_filenames() {
366 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey::new(
367 fscrypt::to_directory_keys(
368 fscrypt_test_data::KEY,
369 fscrypt_test_data::UUID,
370 fscrypt_test_data::DIR_NONCE,
371 )
372 .to_unwrapped_key(),
373 )));
374
375 for file in fscrypt_test_data::FILES {
376 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
377 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
378 let proxy_name = ProxyFilename::new(&buffer);
379 let proxy_name_str: String = proxy_name.into();
380 assert_eq!(
381 proxy_name_str,
382 file.proxy_name,
383 "Proxy name mismatch for (len {}) {}",
384 file.unencrypted_name.len(),
385 file.unencrypted_name
386 );
387 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
388 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
389 }
390 }
391
392 #[test]
393 fn test_generated_casefold_filenames() {
394 let unwrapped = UnwrappedKey::new(
395 fscrypt::to_directory_keys(
396 fscrypt_test_data::KEY,
397 fscrypt_test_data::UUID,
398 fscrypt_test_data::CASEFOLD_DIR_NONCE,
399 )
400 .to_unwrapped_key(),
401 );
402 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
403 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
404
405 for file in fscrypt_test_data::CASEFOLD_FILES {
406 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
407 cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
408
409 let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
410 let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
411 if file.unencrypted_name.len() == 255 {
412 hash_code = expected_proxy.hash_code as u32;
416 }
417 let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
418
419 assert_eq!(
420 actual_proxy,
421 expected_proxy,
422 "Proxy name mismatch for (len {}) {}",
423 file.unencrypted_name.len(),
424 file.unencrypted_name
425 );
426 cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
427 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
428 }
429 }
430
431 #[test]
432 fn test_generated_casefold_symlinks() {
433 let unwrapped = UnwrappedKey::new(
434 fscrypt::to_directory_keys(
435 fscrypt_test_data::KEY,
436 fscrypt_test_data::UUID,
437 fscrypt_test_data::CASEFOLD_DIR_NONCE,
438 )
439 .to_unwrapped_key(),
440 );
441 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
442 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
443
444 for file in fscrypt_test_data::SYMLINKS {
445 let mut target_buffer = file.target.as_bytes().to_vec();
449 cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
450
451 let expected_proxy: ProxyFilename =
452 file.encrypted_target_proxy_name.try_into().unwrap();
453 let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
455
456 assert_eq!(
457 actual_proxy,
458 expected_proxy,
459 "Proxy name mismatch for symlink length {}",
460 file.target.len()
461 );
462
463 cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
464 assert_eq!(
465 String::from_utf8(target_buffer).unwrap(),
466 file.target,
467 "Decrypted target mismatch for symlink {}",
468 file.target
469 );
470 }
471 }
472
473 #[test]
474 fn test_software_file_cipher_multi_block() {
475 let key = UnwrappedKey::new((0..64).collect());
476 let cipher = FscryptSoftwareInoLblk32FileCipher::new(&key);
477 let base_tweak: u128 = 0x1234_5678;
478
479 let mut multi_block_buf = Vec::with_capacity(3 * BLOCK_SIZE);
481 for i in 0..3u8 {
482 multi_block_buf.extend(std::iter::repeat_n(i + 1, BLOCK_SIZE));
483 }
484 let original_plaintext = multi_block_buf.clone();
485
486 cipher.encrypt(&mut multi_block_buf, base_tweak).expect("multi-block encrypt failed");
488
489 for i in 0..3 {
494 let mut single_block =
495 original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
496 cipher
497 .encrypt(&mut single_block, base_tweak + i as u128)
498 .expect("single-block encrypt failed");
499 assert_eq!(
500 &multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
501 &single_block[..],
502 "Ciphertext mismatch at block {i}"
503 );
504 }
505
506 for i in 0..3 {
509 let mut single_block = multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
510 cipher
511 .decrypt(&mut single_block, base_tweak + i as u128)
512 .expect("single-block decrypt failed");
513 assert_eq!(
514 &single_block[..],
515 &original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
516 "Single-block decrypt mismatch at block {i}"
517 );
518 }
519
520 cipher.decrypt(&mut multi_block_buf, base_tweak).expect("multi-block decrypt failed");
522 assert_eq!(multi_block_buf, original_plaintext);
523 }
524
525 #[test]
526 fn test_file_cipher_uses_registered_slot() {
527 let ino_hash_key = [0xab; 16];
528 let unwrapped_with_slot = UnwrappedKey::new_with_slot(ino_hash_key.to_vec(), Some(42));
529 let cipher = FscryptInoLblk32FileCipher::new(&unwrapped_with_slot);
530
531 let (dun, slot) = cipher.crypt_ctx(7, 0, 3 * BLOCK_SIZE as u64).unwrap();
532 assert_eq!(slot, 42);
533 assert_eq!(dun, cipher.tweak(7, 3).into());
534 }
535
536 #[test]
537 fn test_fscrypt_ino_lblk32_file_cipher_dun() {
538 let ino = 0x1234_5678u64;
539 let file_offset = 10 * 4096;
540
541 let cipher_lblk32 = FscryptInoLblk32FileCipher::new(&UnwrappedKey::new_with_slot(
542 vec![0x42u8; 16],
543 Some(5),
544 ));
545 let (dun32, slot32) = cipher_lblk32.crypt_ctx(ino, 0, file_offset).unwrap();
546 assert_eq!(slot32, 5);
547 assert!(dun32 <= u32::MAX as u64);
548 }
549}