1use super::{Cipher, Tweak, UnwrappedKey, XtsProcessor};
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<(u32, 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<(u32, 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, 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], tweak: u128) -> Result<(), Error> {
279 fxfs_trace::duration!("encrypt", "len" => buffer.len());
280 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
281 let mut tweak = tweak;
282
283 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
284 self.xts_key2.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
285 self.xts_key1.encrypt_with_backend(XtsProcessor::new_in_place(
286 Tweak(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 self.xts_key2.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
299 self.xts_key1.decrypt_with_backend(XtsProcessor::new_in_place(
300 Tweak(tweak),
301 MutPtrByteSlice::from(&mut block[..]),
302 ));
303 tweak += 1;
304 }
305 Ok(())
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::{FscryptInoLblk32DirCipher, UnwrappedKey};
312 use crate::Cipher;
313 use crate::cipher::fscrypt_test_data;
314 use fscrypt::proxy_filename::ProxyFilename;
315 use std::sync::Arc;
316
317 #[test]
318 fn test_encrypt_filename() {
319 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
320 unwrapped_key.0[0] = 0x10;
321 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
322 let object_id = 2;
323
324 let mut text = b"filename".to_vec();
331 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
332 assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
333
334 let mut text = b"0123456789abcdef_filename".to_vec();
344 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
345 assert_eq!(
346 text,
347 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
348 .expect("decode failed")
349 );
350
351 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
376 let mut text = vec![];
377 for _ in 0..3 {
378 text.extend_from_slice(long_name_64);
379 }
380
381 let raw = hex::decode(concat!(
382 "f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87",
383 "c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf89",
384 "0bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1a",
385 "ef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20",
386 "119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd13587",
387 "08a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf"
388 ))
389 .expect("decode failed");
390 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
391 assert_eq!(text, raw);
392 }
393
394 #[test]
395 fn test_decrypt_filename() {
396 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
402 unwrapped_key.0[0] = 0x10;
403 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
404 let object_id = 2;
405
406 let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
408 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
409 assert_eq!(text, b"filename".to_vec());
410
411 let mut text =
413 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
414 .expect("decode failed");
415 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
416 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
417 }
418
419 #[test]
420 fn test_generated_filenames() {
421 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey(
422 fscrypt::to_directory_keys(
423 fscrypt_test_data::KEY,
424 fscrypt_test_data::UUID,
425 fscrypt_test_data::DIR_NONCE,
426 )
427 .to_unwrapped_key(),
428 )));
429
430 for file in fscrypt_test_data::FILES {
431 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
432 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
433 let proxy_name = ProxyFilename::new(&buffer);
434 let proxy_name_str: String = proxy_name.into();
435 assert_eq!(
436 proxy_name_str,
437 file.proxy_name,
438 "Proxy name mismatch for (len {}) {}",
439 file.unencrypted_name.len(),
440 file.unencrypted_name
441 );
442 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
443 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
444 }
445 }
446
447 #[test]
448 fn test_generated_casefold_filenames() {
449 let unwrapped = UnwrappedKey(
450 fscrypt::to_directory_keys(
451 fscrypt_test_data::KEY,
452 fscrypt_test_data::UUID,
453 fscrypt_test_data::CASEFOLD_DIR_NONCE,
454 )
455 .to_unwrapped_key(),
456 );
457 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
458 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
459
460 for file in fscrypt_test_data::CASEFOLD_FILES {
461 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
462 cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
463
464 let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
465 let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
466 if file.unencrypted_name.len() == 255 {
467 hash_code = expected_proxy.hash_code as u32;
471 }
472 let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
473
474 assert_eq!(
475 actual_proxy,
476 expected_proxy,
477 "Proxy name mismatch for (len {}) {}",
478 file.unencrypted_name.len(),
479 file.unencrypted_name
480 );
481 cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
482 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
483 }
484 }
485
486 #[test]
487 fn test_generated_casefold_symlinks() {
488 let unwrapped = UnwrappedKey(
489 fscrypt::to_directory_keys(
490 fscrypt_test_data::KEY,
491 fscrypt_test_data::UUID,
492 fscrypt_test_data::CASEFOLD_DIR_NONCE,
493 )
494 .to_unwrapped_key(),
495 );
496 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
497 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
498
499 for file in fscrypt_test_data::SYMLINKS {
500 let mut target_buffer = file.target.as_bytes().to_vec();
504 cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
505
506 let expected_proxy: ProxyFilename =
507 file.encrypted_target_proxy_name.try_into().unwrap();
508 let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
510
511 assert_eq!(
512 actual_proxy,
513 expected_proxy,
514 "Proxy name mismatch for symlink length {}",
515 file.target.len()
516 );
517
518 cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
519 assert_eq!(
520 String::from_utf8(target_buffer).unwrap(),
521 file.target,
522 "Decrypted target mismatch for symlink {}",
523 file.target
524 );
525 }
526 }
527}