1use super::{Cipher, Tweak, UnwrappedKey, XtsProcessor};
5use aes::Aes256;
6use aes::cipher::generic_array::GenericArray;
7use aes::cipher::inout::InOutBuf;
8use aes::cipher::typenum::consts::U16;
9use aes::cipher::{
10 Block, BlockDecrypt, BlockDecryptMut, BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit,
11};
12use anyhow::{Context, Error, ensure};
13use siphasher::sip::SipHasher;
14use std::hash::Hasher;
15use zerocopy::IntoBytes;
16
17const BLOCK_SIZE: usize = 4096;
18const MAX_FILENAME_LEN: usize = 255;
19const MAX_SYMLINK_LEN: usize = 4093;
20const NAME_PADDING: usize = 16;
21
22#[derive(Debug)]
23pub(crate) struct FscryptInoLblk32DirCipher {
24 cts_key: [u8; 32],
25 ino_hash_key: [u8; 16],
26 dir_hash_key: [u8; 16],
27}
28impl FscryptInoLblk32DirCipher {
29 pub fn new(key: &UnwrappedKey) -> Self {
30 Self {
31 cts_key: key[..32].try_into().unwrap(),
32 ino_hash_key: key[32..48].try_into().unwrap(),
33 dir_hash_key: key[48..64].try_into().unwrap(),
34 }
35 }
36}
37impl Cipher for FscryptInoLblk32DirCipher {
38 fn encrypt(
39 &self,
40 _ino: u64,
41 _device_offset: u64,
42 _file_offset: u64,
43 _buffer: &mut [u8],
44 ) -> Result<(), Error> {
45 Err(zx_status::Status::NOT_SUPPORTED).context("encrypt not supported for InoLblk32Dir")
46 }
47
48 fn decrypt(
49 &self,
50 _ino: u64,
51 _device_offset: u64,
52 _file_offset: u64,
53 _buffer: &mut [u8],
54 ) -> Result<(), Error> {
55 Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk32Dir")
56 }
57
58 fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
59 self.encrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
60 }
61
62 fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
63 self.decrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
64 }
65
66 fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
67 self.encrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
68 }
69
70 fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
71 self.decrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
72 }
73
74 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
75 None
76 }
77
78 fn hash_code_casefold(&self, filename: &str) -> u32 {
79 let casefolded_filename: String = fxfs_unicode::casefold(filename.chars()).collect();
80 fscrypt::direntry::casefold_encrypt_hash_filename(
81 casefolded_filename.as_bytes(),
82 &self.dir_hash_key,
83 )
84 }
85
86 fn supports_inline_encryption(&self) -> bool {
87 false
88 }
89
90 fn crypt_ctx(&self, _ino: u64, _file_offset: u64) -> Option<(u32, u8)> {
91 None
92 }
93}
94
95impl FscryptInoLblk32DirCipher {
96 fn encrypt_filename_with_max_len(
97 &self,
98 object_id: u64,
99 buffer: &mut Vec<u8>,
100 max_len: usize,
101 ) -> Result<(), Error> {
102 ensure!(buffer.len() <= max_len, "Filename too long");
103
104 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
105 hasher.write(object_id.as_bytes());
106 let iv = [hasher.finish() as u32, 0, 0, 0];
107
108 buffer.resize(buffer.len().next_multiple_of(NAME_PADDING), 0);
109
110 let mut cbc =
111 cbc::Encryptor::<aes::Aes256>::new((&self.cts_key).into(), iv.as_bytes().into());
112 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
113 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
114 let mut chunks = blocks.get_out();
115 cbc.encrypt_blocks_mut(&mut chunks);
116 if chunks.len() >= 2 {
117 chunks.swap(chunks.len() - 1, chunks.len() - 2);
122 buffer.truncate(max_len);
123 }
124 Ok(())
125 }
126
127 fn decrypt_filename_with_max_len(
128 &self,
129 object_id: u64,
130 buffer: &mut Vec<u8>,
131 max_len: usize,
132 ) -> Result<(), Error> {
133 let alignment = buffer.len() % NAME_PADDING;
134 if alignment != 0 {
135 ensure!(buffer.len() == max_len, "Unexpected filename length");
139
140 let mut cipher = aes::Aes256::new(&self.cts_key.into());
142 let mut out = GenericArray::<u8, U16>::default();
143 cipher.decrypt_block_inout_mut(
144 (
145 GenericArray::from_slice(
146 &buffer[max_len - alignment - NAME_PADDING..max_len - alignment],
147 ),
148 &mut out,
149 )
150 .into(),
151 );
152
153 buffer.extend_from_slice(&out[alignment..]);
155 }
156
157 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
158 hasher.write(object_id.as_bytes());
159 let iv = [hasher.finish() as u32, 0, 0, 0];
160
161 let mut cbc =
162 cbc::Decryptor::<aes::Aes256>::new((&self.cts_key).into(), iv.as_bytes().into());
163 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
164 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
165 let mut chunks = blocks.get_out();
166 if chunks.len() >= 2 {
167 chunks.swap(chunks.len() - 1, chunks.len() - 2);
168 }
169 cbc.decrypt_blocks_mut(&mut chunks);
170
171 while let Some(0) = buffer.last() {
173 buffer.pop();
174 }
175 Ok(())
176 }
177}
178
179#[derive(Debug)]
180pub(super) struct FscryptInoLblk32FileCipher {
181 slot: u8,
182 ino_hash_key: [u8; 16],
183}
184
185impl FscryptInoLblk32FileCipher {
186 pub fn new(key: &UnwrappedKey) -> Self {
187 Self { slot: key[0], ino_hash_key: key[1..17].try_into().unwrap() }
188 }
189
190 #[inline(always)]
191 fn tweak(&self, ino: u64, block_num: u64) -> u32 {
192 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
193 hasher.write(ino.as_bytes());
194 (hasher.finish().wrapping_add(block_num)) as u32
195 }
196}
197
198impl Cipher for FscryptInoLblk32FileCipher {
201 fn encrypt(
202 &self,
203 _ino: u64,
204 _device_offset: u64,
205 _file_offset: u64,
206 _buffer: &mut [u8],
207 ) -> Result<(), Error> {
208 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
209 Err(e.context("encrypt not supported for InoLblk32File"))
210 }
211
212 fn decrypt(
213 &self,
214 _ino: u64,
215 _device_offset: u64,
216 _file_offset: u64,
217 _buffer: &mut [u8],
218 ) -> Result<(), Error> {
219 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
220 Err(e.context("decrypt not supported for InoLblk32File"))
221 }
222
223 fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
224 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
225 Err(e.context("encrypt_filename not supported for InoLblk32File"))
226 }
227
228 fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
229 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
230 Err(e.context("decrypt_filename not supported for InoLblk32File"))
231 }
232
233 fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
234 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
235 Err(e.context("encrypt_symlink not supported for InoLblk32File"))
236 }
237
238 fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
239 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
240 Err(e.context("decrypt_symlink not supported for InoLblk32File"))
241 }
242
243 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
244 debug_assert!(false, "hash_code called on file cipher");
245 None
246 }
247
248 fn hash_code_casefold(&self, _filename: &str) -> u32 {
249 debug_assert!(false, "hash_code_casefold called on file cipher");
250 0
251 }
252
253 fn supports_inline_encryption(&self) -> bool {
254 true
255 }
256
257 fn crypt_ctx(&self, ino: u64, file_offset: u64) -> Option<(u32, u8)> {
258 assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
259 let block_num = file_offset / BLOCK_SIZE as u64;
260 let tweak = self.tweak(ino, block_num);
261 Some((tweak, self.slot))
262 }
263}
264
265#[derive(Debug)]
267pub struct FscryptSoftwareInoLblk32FileCipher {
268 xts_key1: Aes256,
269 xts_key2: Aes256,
270}
271
272impl FscryptSoftwareInoLblk32FileCipher {
273 pub fn new(key: &UnwrappedKey) -> Self {
274 Self {
275 xts_key1: Aes256::new(GenericArray::from_slice(&key[..32])),
276 xts_key2: Aes256::new(GenericArray::from_slice(&key[32..64])),
277 }
278 }
279
280 pub fn encrypt(&self, buffer: &mut [u8], tweak: u128) -> Result<(), Error> {
281 fxfs_trace::duration!(c"encrypt", "len" => buffer.len());
282 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
283 let mut tweak = tweak;
284
285 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
286 self.xts_key2.encrypt_block(GenericArray::from_mut_slice(tweak.as_mut_bytes()));
287 self.xts_key1.encrypt_with_backend(XtsProcessor::new(Tweak(tweak), block));
288 tweak += 1;
289 }
290 Ok(())
291 }
292
293 pub fn decrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
294 fxfs_trace::duration!(c"decrypt", "len" => buffer.len());
295 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
296 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
297 self.xts_key2.encrypt_block(GenericArray::from_mut_slice(tweak.as_mut_bytes()));
298 self.xts_key1.decrypt_with_backend(XtsProcessor::new(Tweak(tweak), block));
299 tweak += 1;
300 }
301 Ok(())
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::{FscryptInoLblk32DirCipher, UnwrappedKey};
308 use crate::Cipher;
309 use crate::cipher::fscrypt_test_data;
310 use fscrypt::proxy_filename::ProxyFilename;
311 use std::sync::Arc;
312
313 #[test]
314 fn test_encrypt_filename() {
315 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
316 unwrapped_key.0[0] = 0x10;
317 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
318 let object_id = 2;
319
320 let mut text = b"filename".to_vec();
327 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
328 assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
329
330 let mut text = b"0123456789abcdef_filename".to_vec();
340 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
341 assert_eq!(
342 text,
343 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
344 .expect("decode failed")
345 );
346
347 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
372 let mut text = vec![];
373 for _ in 0..3 {
374 text.extend_from_slice(long_name_64);
375 }
376
377 let raw = hex::decode("f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf890bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1aef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd1358708a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf").expect("decode failed");
378 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
379 assert_eq!(text, raw);
380 }
381
382 #[test]
383 fn test_decrypt_filename() {
384 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
390 unwrapped_key.0[0] = 0x10;
391 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
392 let object_id = 2;
393
394 let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
396 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
397 assert_eq!(text, b"filename".to_vec());
398
399 let mut text =
401 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
402 .expect("decode failed");
403 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
404 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
405 }
406
407 #[test]
408 fn test_generated_filenames() {
409 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey(
410 fscrypt::to_directory_keys(
411 fscrypt_test_data::KEY,
412 fscrypt_test_data::UUID,
413 fscrypt_test_data::DIR_NONCE,
414 )
415 .to_unwrapped_key(),
416 )));
417
418 for file in fscrypt_test_data::FILES {
419 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
420 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
421 let proxy_name = ProxyFilename::new(&buffer);
422 let proxy_name_str: String = proxy_name.into();
423 assert_eq!(
424 proxy_name_str,
425 file.proxy_name,
426 "Proxy name mismatch for (len {}) {}",
427 file.unencrypted_name.len(),
428 file.unencrypted_name
429 );
430 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
431 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
432 }
433 }
434
435 #[test]
436 fn test_generated_casefold_filenames() {
437 let unwrapped = UnwrappedKey(
438 fscrypt::to_directory_keys(
439 fscrypt_test_data::KEY,
440 fscrypt_test_data::UUID,
441 fscrypt_test_data::CASEFOLD_DIR_NONCE,
442 )
443 .to_unwrapped_key(),
444 );
445 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
446 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
447
448 for file in fscrypt_test_data::CASEFOLD_FILES {
449 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
450 cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
451
452 let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
453 let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
454 if file.unencrypted_name.len() == 255 {
455 hash_code = expected_proxy.hash_code as u32;
459 }
460 let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
461
462 assert_eq!(
463 actual_proxy,
464 expected_proxy,
465 "Proxy name mismatch for (len {}) {}",
466 file.unencrypted_name.len(),
467 file.unencrypted_name
468 );
469 cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
470 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
471 }
472 }
473
474 #[test]
475 fn test_generated_casefold_symlinks() {
476 let unwrapped = UnwrappedKey(
477 fscrypt::to_directory_keys(
478 fscrypt_test_data::KEY,
479 fscrypt_test_data::UUID,
480 fscrypt_test_data::CASEFOLD_DIR_NONCE,
481 )
482 .to_unwrapped_key(),
483 );
484 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
485 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
486
487 for file in fscrypt_test_data::SYMLINKS {
488 let mut target_buffer = file.target.as_bytes().to_vec();
492 cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
493
494 let expected_proxy: ProxyFilename =
495 file.encrypted_target_proxy_name.try_into().unwrap();
496 let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
498
499 assert_eq!(
500 actual_proxy,
501 expected_proxy,
502 "Proxy name mismatch for symlink length {}",
503 file.target.len()
504 );
505
506 cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
507 assert_eq!(
508 String::from_utf8(target_buffer).unwrap(),
509 file.target,
510 "Decrypted target mismatch for symlink {}",
511 file.target
512 );
513 }
514 }
515}