1use super::{Cipher, Tweak, UnwrappedKey, XtsProcessor};
5use aes::Aes256;
6use aes::cipher::inout::InOutBuf;
7use aes::cipher::{
8 Block, BlockCipherDecrypt, BlockCipherEncrypt, BlockModeDecrypt, BlockModeEncrypt, KeyInit,
9 KeyIvInit,
10};
11use anyhow::{Context, Error, ensure};
12use siphasher::sip::SipHasher;
13use std::hash::Hasher;
14use zerocopy::IntoBytes;
15
16const BLOCK_SIZE: usize = 4096;
17const MAX_FILENAME_LEN: usize = 255;
18const MAX_SYMLINK_LEN: usize = 4093;
19const NAME_PADDING: usize = 16;
20
21#[derive(Debug)]
22pub(crate) struct FscryptInoLblk32DirCipher {
23 cts_key: [u8; 32],
24 ino_hash_key: [u8; 16],
25 dir_hash_key: [u8; 16],
26}
27impl FscryptInoLblk32DirCipher {
28 pub fn new(key: &UnwrappedKey) -> Self {
29 Self {
30 cts_key: key[..32].try_into().unwrap(),
31 ino_hash_key: key[32..48].try_into().unwrap(),
32 dir_hash_key: key[48..64].try_into().unwrap(),
33 }
34 }
35}
36impl Cipher for FscryptInoLblk32DirCipher {
37 fn encrypt(
38 &self,
39 _ino: u64,
40 _attribute_id: 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 _attribute_id: u64,
52 _device_offset: u64,
53 _file_offset: u64,
54 _buffer: &mut [u8],
55 ) -> Result<(), Error> {
56 Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk32Dir")
57 }
58
59 fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
60 self.encrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
61 }
62
63 fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
64 self.decrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
65 }
66
67 fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
68 self.encrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
69 }
70
71 fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
72 self.decrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
73 }
74
75 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
76 None
77 }
78
79 fn hash_code_casefold(&self, filename: &str) -> u32 {
80 fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
81 }
82
83 fn supports_inline_encryption(&self) -> bool {
84 false
85 }
86
87 fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u32, u8)> {
88 None
89 }
90}
91
92impl FscryptInoLblk32DirCipher {
93 fn encrypt_filename_with_max_len(
94 &self,
95 object_id: u64,
96 buffer: &mut Vec<u8>,
97 max_len: usize,
98 ) -> Result<(), Error> {
99 ensure!(buffer.len() <= max_len, "Filename too long");
100
101 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
102 hasher.write(object_id.as_bytes());
103 let iv = [hasher.finish() as u32, 0, 0, 0];
104
105 buffer.resize(buffer.len().next_multiple_of(NAME_PADDING), 0);
106
107 let mut cbc = cbc::Encryptor::<aes::Aes256>::new(
108 (&self.cts_key).try_into().unwrap(),
109 iv.as_bytes().try_into().unwrap(),
110 );
111 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
112 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
113 let mut chunks = blocks.get_out();
114 cbc.encrypt_blocks(&mut chunks);
115 if chunks.len() >= 2 {
116 chunks.swap(chunks.len() - 1, chunks.len() - 2);
121 buffer.truncate(max_len);
122 }
123 Ok(())
124 }
125
126 fn decrypt_filename_with_max_len(
127 &self,
128 object_id: u64,
129 buffer: &mut Vec<u8>,
130 max_len: usize,
131 ) -> Result<(), Error> {
132 let alignment = buffer.len() % NAME_PADDING;
133 if alignment != 0 {
134 ensure!(buffer.len() == max_len, "Unexpected filename length");
138
139 let cipher = aes::Aes256::new((&self.cts_key).try_into().unwrap());
141 let mut out: Block<aes::Aes256> =
142 buffer[max_len - alignment - NAME_PADDING..max_len - alignment].try_into().unwrap();
143 cipher.decrypt_block(&mut out);
144
145 buffer.extend_from_slice(&out[alignment..]);
147 }
148
149 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
150 hasher.write(object_id.as_bytes());
151 let iv = [hasher.finish() as u32, 0, 0, 0];
152
153 let mut cbc = cbc::Decryptor::<aes::Aes256>::new(
154 (&self.cts_key).try_into().unwrap(),
155 iv.as_bytes().try_into().unwrap(),
156 );
157 let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
158 let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
159 let mut chunks = blocks.get_out();
160 if chunks.len() >= 2 {
161 chunks.swap(chunks.len() - 1, chunks.len() - 2);
162 }
163 cbc.decrypt_blocks(&mut chunks);
164
165 while let Some(0) = buffer.last() {
167 buffer.pop();
168 }
169 Ok(())
170 }
171}
172
173#[derive(Debug)]
174pub(super) struct FscryptInoLblk32FileCipher {
175 slot: u8,
176 ino_hash_key: [u8; 16],
177}
178
179impl FscryptInoLblk32FileCipher {
180 pub fn new(key: &UnwrappedKey) -> Self {
181 Self { slot: key[0], ino_hash_key: key[1..17].try_into().unwrap() }
182 }
183
184 #[inline(always)]
185 fn tweak(&self, ino: u64, block_num: u64) -> u32 {
186 let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
187 hasher.write(ino.as_bytes());
188 (hasher.finish().wrapping_add(block_num)) as u32
189 }
190}
191
192impl Cipher for FscryptInoLblk32FileCipher {
195 fn encrypt(
196 &self,
197 _ino: u64,
198 _attribute_id: u64,
199 _device_offset: u64,
200 _file_offset: u64,
201 _buffer: &mut [u8],
202 ) -> Result<(), Error> {
203 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
204 Err(e.context("encrypt not supported for InoLblk32File"))
205 }
206
207 fn decrypt(
208 &self,
209 _ino: u64,
210 _attribute_id: 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, _attribute_id: 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((&key[..32]).try_into().unwrap()),
272 xts_key2: Aes256::new((&key[32..64]).try_into().unwrap()),
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(tweak.as_mut_bytes().try_into().unwrap());
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(tweak.as_mut_bytes().try_into().unwrap());
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}