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 fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
80 }
81
82 fn supports_inline_encryption(&self) -> bool {
83 false
84 }
85
86 fn crypt_ctx(&self, _ino: u64, _file_offset: u64) -> Option<(u32, u8)> {
87 None
88 }
89}
90
91impl FscryptInoLblk32DirCipher {
92 fn encrypt_filename_with_max_len(
93 &self,
94 object_id: u64,
95 buffer: &mut Vec<u8>,
96 max_len: usize,
97 ) -> Result<(), Error> {
98 ensure!(buffer.len() <= max_len, "Filename too long");
99
100 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
101 hasher.write(object_id.as_bytes());
102 let iv = [hasher.finish() as u32, 0, 0, 0];
103
104 buffer.resize(buffer.len().next_multiple_of(NAME_PADDING), 0);
105
106 let mut cbc =
107 cbc::Encryptor::<aes::Aes256>::new((&self.cts_key).into(), iv.as_bytes().into());
108 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
109 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
110 let mut chunks = blocks.get_out();
111 cbc.encrypt_blocks_mut(&mut chunks);
112 if chunks.len() >= 2 {
113 chunks.swap(chunks.len() - 1, chunks.len() - 2);
118 buffer.truncate(max_len);
119 }
120 Ok(())
121 }
122
123 fn decrypt_filename_with_max_len(
124 &self,
125 object_id: u64,
126 buffer: &mut Vec<u8>,
127 max_len: usize,
128 ) -> Result<(), Error> {
129 let alignment = buffer.len() % NAME_PADDING;
130 if alignment != 0 {
131 ensure!(buffer.len() == max_len, "Unexpected filename length");
135
136 let mut cipher = aes::Aes256::new(&self.cts_key.into());
138 let mut out = GenericArray::<u8, U16>::default();
139 cipher.decrypt_block_inout_mut(
140 (
141 GenericArray::from_slice(
142 &buffer[max_len - alignment - NAME_PADDING..max_len - alignment],
143 ),
144 &mut out,
145 )
146 .into(),
147 );
148
149 buffer.extend_from_slice(&out[alignment..]);
151 }
152
153 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
154 hasher.write(object_id.as_bytes());
155 let iv = [hasher.finish() as u32, 0, 0, 0];
156
157 let mut cbc =
158 cbc::Decryptor::<aes::Aes256>::new((&self.cts_key).into(), iv.as_bytes().into());
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(&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 _device_offset: u64,
201 _file_offset: u64,
202 _buffer: &mut [u8],
203 ) -> Result<(), Error> {
204 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
205 Err(e.context("encrypt not supported for InoLblk32File"))
206 }
207
208 fn decrypt(
209 &self,
210 _ino: u64,
211 _device_offset: u64,
212 _file_offset: u64,
213 _buffer: &mut [u8],
214 ) -> Result<(), Error> {
215 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
216 Err(e.context("decrypt not supported for InoLblk32File"))
217 }
218
219 fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
220 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
221 Err(e.context("encrypt_filename not supported for InoLblk32File"))
222 }
223
224 fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
225 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
226 Err(e.context("decrypt_filename not supported for InoLblk32File"))
227 }
228
229 fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
230 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
231 Err(e.context("encrypt_symlink not supported for InoLblk32File"))
232 }
233
234 fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
235 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
236 Err(e.context("decrypt_symlink not supported for InoLblk32File"))
237 }
238
239 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
240 debug_assert!(false, "hash_code called on file cipher");
241 None
242 }
243
244 fn hash_code_casefold(&self, _filename: &str) -> u32 {
245 debug_assert!(false, "hash_code_casefold called on file cipher");
246 0
247 }
248
249 fn supports_inline_encryption(&self) -> bool {
250 true
251 }
252
253 fn crypt_ctx(&self, ino: u64, file_offset: u64) -> Option<(u32, u8)> {
254 assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
255 let block_num = file_offset / BLOCK_SIZE as u64;
256 let tweak = self.tweak(ino, block_num);
257 Some((tweak, self.slot))
258 }
259}
260
261#[derive(Debug)]
263pub struct FscryptSoftwareInoLblk32FileCipher {
264 xts_key1: Aes256,
265 xts_key2: Aes256,
266}
267
268impl FscryptSoftwareInoLblk32FileCipher {
269 pub fn new(key: &UnwrappedKey) -> Self {
270 Self {
271 xts_key1: Aes256::new(GenericArray::from_slice(&key[..32])),
272 xts_key2: Aes256::new(GenericArray::from_slice(&key[32..64])),
273 }
274 }
275
276 pub fn encrypt(&self, buffer: &mut [u8], tweak: u128) -> Result<(), Error> {
277 fxfs_trace::duration!("encrypt", "len" => buffer.len());
278 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
279 let mut tweak = tweak;
280
281 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
282 self.xts_key2.encrypt_block(GenericArray::from_mut_slice(tweak.as_mut_bytes()));
283 self.xts_key1.encrypt_with_backend(XtsProcessor::new(Tweak(tweak), block));
284 tweak += 1;
285 }
286 Ok(())
287 }
288
289 pub fn decrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
290 fxfs_trace::duration!("decrypt", "len" => buffer.len());
291 assert_eq!(buffer.len() % BLOCK_SIZE, 0);
292 for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
293 self.xts_key2.encrypt_block(GenericArray::from_mut_slice(tweak.as_mut_bytes()));
294 self.xts_key1.decrypt_with_backend(XtsProcessor::new(Tweak(tweak), block));
295 tweak += 1;
296 }
297 Ok(())
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::{FscryptInoLblk32DirCipher, UnwrappedKey};
304 use crate::Cipher;
305 use crate::cipher::fscrypt_test_data;
306 use fscrypt::proxy_filename::ProxyFilename;
307 use std::sync::Arc;
308
309 #[test]
310 fn test_encrypt_filename() {
311 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
312 unwrapped_key.0[0] = 0x10;
313 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
314 let object_id = 2;
315
316 let mut text = b"filename".to_vec();
323 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
324 assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
325
326 let mut text = b"0123456789abcdef_filename".to_vec();
336 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
337 assert_eq!(
338 text,
339 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
340 .expect("decode failed")
341 );
342
343 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
368 let mut text = vec![];
369 for _ in 0..3 {
370 text.extend_from_slice(long_name_64);
371 }
372
373 let raw = hex::decode("f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf890bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1aef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd1358708a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf").expect("decode failed");
374 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
375 assert_eq!(text, raw);
376 }
377
378 #[test]
379 fn test_decrypt_filename() {
380 let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
386 unwrapped_key.0[0] = 0x10;
387 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
388 let object_id = 2;
389
390 let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
392 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
393 assert_eq!(text, b"filename".to_vec());
394
395 let mut text =
397 hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
398 .expect("decode failed");
399 cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
400 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
401 }
402
403 #[test]
404 fn test_generated_filenames() {
405 let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey(
406 fscrypt::to_directory_keys(
407 fscrypt_test_data::KEY,
408 fscrypt_test_data::UUID,
409 fscrypt_test_data::DIR_NONCE,
410 )
411 .to_unwrapped_key(),
412 )));
413
414 for file in fscrypt_test_data::FILES {
415 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
416 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
417 let proxy_name = ProxyFilename::new(&buffer);
418 let proxy_name_str: String = proxy_name.into();
419 assert_eq!(
420 proxy_name_str,
421 file.proxy_name,
422 "Proxy name mismatch for (len {}) {}",
423 file.unencrypted_name.len(),
424 file.unencrypted_name
425 );
426 cipher.decrypt_filename(fscrypt_test_data::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_filenames() {
433 let unwrapped = UnwrappedKey(
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::CASEFOLD_FILES {
445 let mut buffer = file.unencrypted_name.as_bytes().to_vec();
446 cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
447
448 let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
449 let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
450 if file.unencrypted_name.len() == 255 {
451 hash_code = expected_proxy.hash_code as u32;
455 }
456 let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
457
458 assert_eq!(
459 actual_proxy,
460 expected_proxy,
461 "Proxy name mismatch for (len {}) {}",
462 file.unencrypted_name.len(),
463 file.unencrypted_name
464 );
465 cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
466 assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
467 }
468 }
469
470 #[test]
471 fn test_generated_casefold_symlinks() {
472 let unwrapped = UnwrappedKey(
473 fscrypt::to_directory_keys(
474 fscrypt_test_data::KEY,
475 fscrypt_test_data::UUID,
476 fscrypt_test_data::CASEFOLD_DIR_NONCE,
477 )
478 .to_unwrapped_key(),
479 );
480 let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
481 let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
482
483 for file in fscrypt_test_data::SYMLINKS {
484 let mut target_buffer = file.target.as_bytes().to_vec();
488 cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
489
490 let expected_proxy: ProxyFilename =
491 file.encrypted_target_proxy_name.try_into().unwrap();
492 let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
494
495 assert_eq!(
496 actual_proxy,
497 expected_proxy,
498 "Proxy name mismatch for symlink length {}",
499 file.target.len()
500 );
501
502 cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
503 assert_eq!(
504 String::from_utf8(target_buffer).unwrap(),
505 file.target,
506 "Decrypted target mismatch for symlink {}",
507 file.target
508 );
509 }
510 }
511}