1use super::{Cipher, Tweak, UnwrappedKey, XtsInPlaceProcessor};
6use aes::Aes256;
7use aes::cipher::inout::InOutBuf;
8use aes::cipher::{
9 Block, BlockCipherDecrypt, BlockCipherEncrypt, BlockModeDecrypt, BlockModeEncrypt, KeyInit,
10 KeyIvInit,
11};
12use anyhow::{Context, Error, ensure};
13use siphasher::sip::SipHasher;
14use std::hash::Hasher;
15use storage_ptr_slice::MutPtrByteSlice;
16use zerocopy::IntoBytes;
17
18const BLOCK_SIZE: usize = 4096;
19const MAX_FILENAME_LEN: usize = 255;
20const MAX_SYMLINK_LEN: usize = 4093;
21const NAME_PADDING: usize = 16;
22
23#[derive(Debug)]
24pub(crate) struct FscryptInoLblk32DirCipher {
25 cts_key: [u8; 32],
26 ino_hash_key: [u8; 16],
27 dir_hash_key: [u8; 16],
28}
29impl FscryptInoLblk32DirCipher {
30 pub fn new(key: &UnwrappedKey) -> Self {
31 Self {
32 cts_key: key[..32].try_into().unwrap(),
33 ino_hash_key: key[32..48].try_into().unwrap(),
34 dir_hash_key: key[48..64].try_into().unwrap(),
35 }
36 }
37}
38impl Cipher for FscryptInoLblk32DirCipher {
39 fn encrypt(
40 &self,
41 _ino: u64,
42 _attribute_id: u64,
43 _device_offset: u64,
44 _file_offset: u64,
45 _buffer: MutPtrByteSlice<'_>,
46 ) -> Result<(), Error> {
47 Err(zx_status::Status::NOT_SUPPORTED).context("encrypt not supported for InoLblk32Dir")
48 }
49
50 fn decrypt(
51 &self,
52 _ino: u64,
53 _attribute_id: u64,
54 _device_offset: u64,
55 _file_offset: u64,
56 _buffer: MutPtrByteSlice<'_>,
57 ) -> Result<(), Error> {
58 Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk32Dir")
59 }
60
61 fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
62 self.encrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
63 }
64
65 fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
66 self.decrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
67 }
68
69 fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
70 self.encrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
71 }
72
73 fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
74 self.decrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
75 }
76
77 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
78 None
79 }
80
81 fn hash_code_casefold(&self, filename: &str) -> u32 {
82 fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
83 }
84
85 fn supports_inline_encryption(&self) -> bool {
86 false
87 }
88
89 fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u64, u8)> {
90 None
91 }
92}
93
94impl FscryptInoLblk32DirCipher {
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 ensure!(buffer.len() <= max_len, "Filename too long");
102
103 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
104 hasher.write(object_id.as_bytes());
105 let iv = [hasher.finish() as u32, 0, 0, 0];
106
107 buffer.resize(buffer.len().next_multiple_of(NAME_PADDING), 0);
108
109 let mut cbc = cbc::Encryptor::<aes::Aes256>::new(
110 (&self.cts_key).try_into().unwrap(),
111 iv.as_bytes().try_into().unwrap(),
112 );
113 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
114 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
115 let mut chunks = blocks.get_out();
116 cbc.encrypt_blocks(&mut chunks);
117 if chunks.len() >= 2 {
118 chunks.swap(chunks.len() - 1, chunks.len() - 2);
123 buffer.truncate(max_len);
124 }
125 Ok(())
126 }
127
128 fn decrypt_filename_with_max_len(
129 &self,
130 object_id: u64,
131 buffer: &mut Vec<u8>,
132 max_len: usize,
133 ) -> Result<(), Error> {
134 let alignment = buffer.len() % NAME_PADDING;
135 if alignment != 0 {
136 ensure!(buffer.len() == max_len, "Unexpected filename length");
140
141 let cipher = aes::Aes256::new((&self.cts_key).try_into().unwrap());
143 let mut out: Block<aes::Aes256> =
144 buffer[max_len - alignment - NAME_PADDING..max_len - alignment].try_into().unwrap();
145 cipher.decrypt_block(&mut out);
146
147 buffer.extend_from_slice(&out[alignment..]);
149 }
150
151 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
152 hasher.write(object_id.as_bytes());
153 let iv = [hasher.finish() as u32, 0, 0, 0];
154
155 let mut cbc = cbc::Decryptor::<aes::Aes256>::new(
156 (&self.cts_key).try_into().unwrap(),
157 iv.as_bytes().try_into().unwrap(),
158 );
159 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
160 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
161 let mut chunks = blocks.get_out();
162 if chunks.len() >= 2 {
163 chunks.swap(chunks.len() - 1, chunks.len() - 2);
164 }
165 cbc.decrypt_blocks(&mut chunks);
166
167 while let Some(0) = buffer.last() {
169 buffer.pop();
170 }
171 Ok(())
172 }
173}
174
175#[derive(Debug)]
176pub(super) struct FscryptInoLblk32FileCipher {
177 slot: u8,
178 ino_hash_key: [u8; 16],
179}
180
181impl FscryptInoLblk32FileCipher {
182 pub fn new(key: &UnwrappedKey) -> Self {
183 Self { slot: key[0], ino_hash_key: key[1..17].try_into().unwrap() }
184 }
185
186 #[inline(always)]
187 fn tweak(&self, ino: u64, block_num: u64) -> u32 {
188 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
189 hasher.write(ino.as_bytes());
190 (hasher.finish().wrapping_add(block_num)) as u32
191 }
192}
193
194impl Cipher for FscryptInoLblk32FileCipher {
197 fn encrypt(
198 &self,
199 _ino: u64,
200 _attribute_id: u64,
201 _device_offset: u64,
202 _file_offset: u64,
203 _buffer: MutPtrByteSlice<'_>,
204 ) -> Result<(), Error> {
205 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
206 Err(e.context("encrypt not supported for InoLblk32File"))
207 }
208
209 fn decrypt(
210 &self,
211 _ino: u64,
212 _attribute_id: u64,
213 _device_offset: u64,
214 _file_offset: u64,
215 _buffer: MutPtrByteSlice<'_>,
216 ) -> Result<(), Error> {
217 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
218 Err(e.context("decrypt not supported for InoLblk32File"))
219 }
220
221 fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
222 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
223 Err(e.context("encrypt_filename not supported for InoLblk32File"))
224 }
225
226 fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
227 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
228 Err(e.context("decrypt_filename not supported for InoLblk32File"))
229 }
230
231 fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
232 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
233 Err(e.context("encrypt_symlink not supported for InoLblk32File"))
234 }
235
236 fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
237 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
238 Err(e.context("decrypt_symlink not supported for InoLblk32File"))
239 }
240
241 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
242 debug_assert!(false, "hash_code called on file cipher");
243 None
244 }
245
246 fn hash_code_casefold(&self, _filename: &str) -> u32 {
247 debug_assert!(false, "hash_code_casefold called on file cipher");
248 0
249 }
250
251 fn supports_inline_encryption(&self) -> bool {
252 true
253 }
254
255 fn crypt_ctx(&self, ino: u64, _attribute_id: u64, file_offset: u64) -> Option<(u64, u8)> {
256 assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
257 let block_num = file_offset / BLOCK_SIZE as u64;
258 let tweak = self.tweak(ino, block_num);
259 Some((tweak as u64, self.slot))
260 }
261}
262
263#[derive(Debug)]
265pub struct FscryptSoftwareInoLblk32FileCipher {
266 xts_key1: Aes256,
267 xts_key2: Aes256,
268}
269
270impl FscryptSoftwareInoLblk32FileCipher {
271 pub fn new(key: &UnwrappedKey) -> Self {
272 Self {
273 xts_key1: Aes256::new((&key[..32]).try_into().unwrap()),
274 xts_key2: Aes256::new((&key[32..64]).try_into().unwrap()),
275 }
276 }
277
278 pub fn encrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
279 fxfs_trace::duration!("encrypt", "len" => buffer.len());
280 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
281
282 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
283 let mut block_tweak = tweak;
284 self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
285 self.xts_key1.encrypt_with_backend(XtsInPlaceProcessor::new(
286 Tweak(block_tweak),
287 MutPtrByteSlice::from(&mut block[..]),
288 ));
289 tweak += 1;
290 }
291 Ok(())
292 }
293
294 pub fn decrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
295 fxfs_trace::duration!("decrypt", "len" => buffer.len());
296 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
297 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
298 let mut block_tweak = tweak;
299 self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
300 self.xts_key1.decrypt_with_backend(XtsInPlaceProcessor::new(
301 Tweak(block_tweak),
302 MutPtrByteSlice::from(&mut block[..]),
303 ));
304 tweak += 1;
305 }
306 Ok(())
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::{
313 BLOCK_SIZE, FscryptInoLblk32DirCipher, FscryptSoftwareInoLblk32FileCipher, UnwrappedKey,
314 };
315 use crate::Cipher;
316 use crate::cipher::fscrypt_test_data;
317 use fscrypt::proxy_filename::ProxyFilename;
318 use std::sync::Arc;
319
320 #[test]
321 fn test_encrypt_filename() {
322 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
323 unwrapped_key.0[0] = 0x10;
324 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
325 let object_id = 2;
326
327 let mut text = b"filename".to_vec();
334 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
335 assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
336
337 let mut text = b"0123456789abcdef_filename".to_vec();
347 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
348 assert_eq!(
349 text,
350 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
351 .expect("decode failed")
352 );
353
354 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
379 let mut text = vec![];
380 for _ in 0..3 {
381 text.extend_from_slice(long_name_64);
382 }
383
384 let raw = hex::decode(concat!(
385 "f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87",
386 "c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf89",
387 "0bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1a",
388 "ef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20",
389 "119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd13587",
390 "08a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf"
391 ))
392 .expect("decode failed");
393 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
394 assert_eq!(text, raw);
395 }
396
397 #[test]
398 fn test_decrypt_filename() {
399 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
405 unwrapped_key.0[0] = 0x10;
406 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
407 let object_id = 2;
408
409 let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
411 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
412 assert_eq!(text, b"filename".to_vec());
413
414 let mut text =
416 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
417 .expect("decode failed");
418 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
419 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
420 }
421
422 #[test]
423 fn test_generated_filenames() {
424 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey(
425 fscrypt::to_directory_keys(
426 fscrypt_test_data::KEY,
427 fscrypt_test_data::UUID,
428 fscrypt_test_data::DIR_NONCE,
429 )
430 .to_unwrapped_key(),
431 )));
432
433 for file in fscrypt_test_data::FILES {
434 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
435 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
436 let proxy_name = ProxyFilename::new(&buffer);
437 let proxy_name_str: String = proxy_name.into();
438 assert_eq!(
439 proxy_name_str,
440 file.proxy_name,
441 "Proxy name mismatch for (len {}) {}",
442 file.unencrypted_name.len(),
443 file.unencrypted_name
444 );
445 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
446 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
447 }
448 }
449
450 #[test]
451 fn test_generated_casefold_filenames() {
452 let unwrapped = UnwrappedKey(
453 fscrypt::to_directory_keys(
454 fscrypt_test_data::KEY,
455 fscrypt_test_data::UUID,
456 fscrypt_test_data::CASEFOLD_DIR_NONCE,
457 )
458 .to_unwrapped_key(),
459 );
460 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
461 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
462
463 for file in fscrypt_test_data::CASEFOLD_FILES {
464 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
465 cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
466
467 let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
468 let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
469 if file.unencrypted_name.len() == 255 {
470 hash_code = expected_proxy.hash_code as u32;
474 }
475 let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
476
477 assert_eq!(
478 actual_proxy,
479 expected_proxy,
480 "Proxy name mismatch for (len {}) {}",
481 file.unencrypted_name.len(),
482 file.unencrypted_name
483 );
484 cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
485 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
486 }
487 }
488
489 #[test]
490 fn test_generated_casefold_symlinks() {
491 let unwrapped = UnwrappedKey(
492 fscrypt::to_directory_keys(
493 fscrypt_test_data::KEY,
494 fscrypt_test_data::UUID,
495 fscrypt_test_data::CASEFOLD_DIR_NONCE,
496 )
497 .to_unwrapped_key(),
498 );
499 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
500 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
501
502 for file in fscrypt_test_data::SYMLINKS {
503 let mut target_buffer = file.target.as_bytes().to_vec();
507 cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
508
509 let expected_proxy: ProxyFilename =
510 file.encrypted_target_proxy_name.try_into().unwrap();
511 let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
513
514 assert_eq!(
515 actual_proxy,
516 expected_proxy,
517 "Proxy name mismatch for symlink length {}",
518 file.target.len()
519 );
520
521 cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
522 assert_eq!(
523 String::from_utf8(target_buffer).unwrap(),
524 file.target,
525 "Decrypted target mismatch for symlink {}",
526 file.target
527 );
528 }
529 }
530
531 #[test]
532 fn test_software_file_cipher_multi_block() {
533 let key = UnwrappedKey::new((0..64).collect());
534 let cipher = FscryptSoftwareInoLblk32FileCipher::new(&key);
535 let base_tweak: u128 = 0x1234_5678;
536
537 let mut multi_block_buf = Vec::with_capacity(3 * BLOCK_SIZE);
539 for i in 0..3u8 {
540 multi_block_buf.extend(std::iter::repeat_n(i + 1, BLOCK_SIZE));
541 }
542 let original_plaintext = multi_block_buf.clone();
543
544 cipher.encrypt(&mut multi_block_buf, base_tweak).expect("multi-block encrypt failed");
546
547 for i in 0..3 {
552 let mut single_block =
553 original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
554 cipher
555 .encrypt(&mut single_block, base_tweak + i as u128)
556 .expect("single-block encrypt failed");
557 assert_eq!(
558 &multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
559 &single_block[..],
560 "Ciphertext mismatch at block {i}"
561 );
562 }
563
564 for i in 0..3 {
567 let mut single_block = multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
568 cipher
569 .decrypt(&mut single_block, base_tweak + i as u128)
570 .expect("single-block decrypt failed");
571 assert_eq!(
572 &single_block[..],
573 &original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
574 "Single-block decrypt mismatch at block {i}"
575 );
576 }
577
578 cipher.decrypt(&mut multi_block_buf, base_tweak).expect("multi-block decrypt failed");
580 assert_eq!(multi_block_buf, original_plaintext);
581 }
582}