1use super::{
6 BLOCK_SIZE, Cipher, MAX_FILENAME_LEN, MAX_SYMLINK_LEN, UnwrappedKey, decrypt_filename_cts,
7 encrypt_filename_cts,
8};
9use anyhow::{Context, Error};
10use storage_ptr_slice::MutPtrByteSlice;
11
12#[derive(Debug)]
19pub struct FscryptInoLblk64DirCipher {
20 cts_key: [u8; 32],
21 dir_hash_key: [u8; 16],
22}
23
24impl FscryptInoLblk64DirCipher {
25 pub fn new(key: &UnwrappedKey) -> Self {
26 assert_eq!(key.len(), 48, "Expected 48-byte unwrapped key for FscryptInoLblk64DirCipher");
27 Self {
28 cts_key: key[..32].try_into().unwrap(),
29 dir_hash_key: key[32..48].try_into().unwrap(),
30 }
31 }
32
33 #[inline(always)]
34 fn iv(object_id: u64) -> Result<[u32; 4], Error> {
35 let ino = u32::try_from(object_id).with_context(|| {
36 format!("IV_INO_LBLK_64 requires 32-bit inode numbers, got {object_id:#x}")
37 })?;
38 Ok([0, ino.to_le(), 0, 0])
39 }
40}
41
42impl Cipher for FscryptInoLblk64DirCipher {
43 fn encrypt(
44 &self,
45 _ino: u64,
46 _attribute_id: u64,
47 _device_offset: u64,
48 _file_offset: u64,
49 _buffer: MutPtrByteSlice<'_>,
50 ) -> Result<(), Error> {
51 Err(zx_status::Status::NOT_SUPPORTED).context("encrypt not supported for InoLblk64Dir")
52 }
53
54 fn decrypt(
55 &self,
56 _ino: u64,
57 _attribute_id: u64,
58 _device_offset: u64,
59 _file_offset: u64,
60 _buffer: MutPtrByteSlice<'_>,
61 ) -> Result<(), Error> {
62 Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk64Dir")
63 }
64
65 fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
66 let iv = Self::iv(object_id)?;
67 encrypt_filename_cts(&self.cts_key, &iv, buffer, MAX_FILENAME_LEN)
68 }
69
70 fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
71 let iv = Self::iv(object_id)?;
72 decrypt_filename_cts(&self.cts_key, &iv, buffer, MAX_FILENAME_LEN)
73 }
74
75 fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
76 let iv = Self::iv(object_id)?;
77 encrypt_filename_cts(&self.cts_key, &iv, buffer, MAX_SYMLINK_LEN)
78 }
79
80 fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
81 let iv = Self::iv(object_id)?;
82 decrypt_filename_cts(&self.cts_key, &iv, buffer, MAX_SYMLINK_LEN)
83 }
84
85 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
86 None
87 }
88
89 fn hash_code_casefold(&self, filename: &str) -> u32 {
90 fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
91 }
92
93 fn supports_inline_encryption(&self) -> bool {
94 false
95 }
96
97 fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u64, u8)> {
98 None
99 }
100}
101
102#[derive(Debug)]
107pub struct FscryptInoLblk64FileCipher {
108 slot: u8,
109}
110
111impl FscryptInoLblk64FileCipher {
112 pub fn new(key: &UnwrappedKey) -> Self {
113 Self { slot: key.slot().unwrap() }
114 }
115
116 pub fn new_with_slot(slot: u8) -> Self {
117 Self { slot }
118 }
119
120 #[inline(always)]
121 fn tweak(&self, ino: u64, block_num: u64) -> u64 {
122 let ino = u32::try_from(ino).unwrap_or_else(|_| {
123 panic!("IV_INO_LBLK_64 requires 32-bit inode numbers, got {ino:#x}")
124 }) as u64;
125 let block_num = u32::try_from(block_num).unwrap_or_else(|_| {
126 panic!("IV_INO_LBLK_64 requires 32-bit logical block numbers, got {block_num:#x}")
127 }) as u64;
128 (ino << 32) | block_num
129 }
130}
131
132impl Cipher for FscryptInoLblk64FileCipher {
133 fn encrypt(
134 &self,
135 _ino: u64,
136 _attribute_id: u64,
137 _device_offset: u64,
138 _file_offset: u64,
139 _buffer: MutPtrByteSlice<'_>,
140 ) -> Result<(), Error> {
141 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
142 Err(e.context("encrypt not supported for InoLblk64File"))
143 }
144
145 fn decrypt(
146 &self,
147 _ino: u64,
148 _attribute_id: u64,
149 _device_offset: u64,
150 _file_offset: u64,
151 _buffer: MutPtrByteSlice<'_>,
152 ) -> Result<(), Error> {
153 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
154 Err(e.context("decrypt not supported for InoLblk64File"))
155 }
156
157 fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
158 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
159 Err(e.context("encrypt_filename not supported for InoLblk64File"))
160 }
161
162 fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
163 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
164 Err(e.context("decrypt_filename not supported for InoLblk64File"))
165 }
166
167 fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
168 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
169 Err(e.context("encrypt_symlink not supported for InoLblk64File"))
170 }
171
172 fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
173 let e: Error = zx_status::Status::NOT_SUPPORTED.into();
174 Err(e.context("decrypt_symlink not supported for InoLblk64File"))
175 }
176
177 fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
178 debug_assert!(false, "hash_code called on file cipher");
179 None
180 }
181
182 fn hash_code_casefold(&self, _filename: &str) -> u32 {
183 debug_assert!(false, "hash_code_casefold called on file cipher");
184 0
185 }
186
187 fn supports_inline_encryption(&self) -> bool {
188 true
189 }
190
191 fn crypt_ctx(&self, ino: u64, _attribute_id: u64, file_offset: u64) -> Option<(u64, u8)> {
192 assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
193 let block_num = file_offset / BLOCK_SIZE as u64;
194 let tweak = self.tweak(ino, block_num);
195 Some((tweak, self.slot))
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::{FscryptInoLblk64DirCipher, FscryptInoLblk64FileCipher};
202 use crate::cipher::fscrypt_test_data;
203 use crate::{Cipher, UnwrappedKey};
204
205 #[test]
206 fn test_fscrypt_ino_lblk64_file_cipher_dun() {
207 let ino = 0x1234_5678u64;
208 let file_offset = 10 * 4096;
209
210 let cipher = FscryptInoLblk64FileCipher::new(&UnwrappedKey::new_with_slot(vec![], Some(4)));
211 assert_eq!(cipher.crypt_ctx(ino, 0, file_offset), Some(((ino << 32) | 10, 4)));
212 }
213
214 #[test]
215 fn test_fscrypt_ino_lblk64_dir_cipher_roundtrip() {
216 let mut unwrapped = vec![0x11u8; 48];
217 unwrapped[0] = 0x42;
218 let cipher = FscryptInoLblk64DirCipher::new(&UnwrappedKey::new(unwrapped));
219
220 let original = b"example_encrypted_filename.txt".to_vec();
221 let mut buf1 = original.clone();
222 let mut buf2 = original.clone();
223
224 cipher.encrypt_filename(100, &mut buf1).expect("encrypt_filename failed");
225 cipher.encrypt_filename(200, &mut buf2).expect("encrypt_filename failed");
226 assert_ne!(buf1, buf2);
228
229 cipher.decrypt_filename(100, &mut buf1).expect("decrypt_filename failed");
230 assert_eq!(buf1, original);
231 }
232
233 #[test]
234 fn test_encrypt_and_decrypt_filename_golden() {
235 let mut unwrapped_key = UnwrappedKey::new([0; 48].to_vec());
240 unwrapped_key[0] = 0x10;
241 let cipher = FscryptInoLblk64DirCipher::new(&unwrapped_key);
242 let object_id = 2;
243
244 let mut text = b"filename".to_vec();
253 let expected_1block =
254 hex::decode("23cf0ebf11b011f471c3e6be7b2d7d4a").expect("decode failed");
255 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
256 assert_eq!(text, expected_1block);
257 cipher.decrypt_filename(object_id, &mut text).expect("decrypt filename failed");
258 assert_eq!(text, b"filename".to_vec());
259
260 let mut text = b"0123456789abcdef_filename".to_vec();
272 let expected_2block =
273 hex::decode("1029e8d0cf2440b023f24e080d2077044869c980dcb984074c3b977100cc9334")
274 .expect("decode failed");
275 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
276 assert_eq!(text, expected_2block);
277 cipher.decrypt_filename(object_id, &mut text).expect("decrypt filename failed");
278 assert_eq!(text, b"0123456789abcdef_filename".to_vec());
279
280 let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
293 let mut text = vec![];
294 for _ in 0..3 {
295 text.extend_from_slice(long_name_64);
296 }
297 let original_192 = text.clone();
298 let expected_192 = hex::decode(concat!(
299 "7b379f5e2cdb7f89bd3ee46fb7bac8302c0c9c1008a7080d3bd606aa16192d7c",
300 "373dad8f8a9ae6229472be9cce1e3326e338afbec4b55d1ea789d42da2375ecf",
301 "2711452440dc6264f0093c5554a44d85c45c2cb23dfa1638882c63684f858c7a",
302 "4f0f77bc5ccf62b5eb2f63e7d7b713667fc546cbb83e81b97009f611af738e52",
303 "1fdd34a3fc92954875dfa2837e904177e9f013fe9374ec0d963e7711473cc271",
304 "9d2b35196eb4af074ae22db4334813fecceb130b0d3108d5c6f96bb73fdcd419"
305 ))
306 .expect("decode failed");
307 cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
308 assert_eq!(text, expected_192);
309 cipher.decrypt_filename(object_id, &mut text).expect("decrypt filename failed");
310 assert_eq!(text, original_192);
311 }
312
313 #[test]
314 fn test_hkdf_and_filename_encryption_golden() {
315 let unwrapped = UnwrappedKey::new(
327 fscrypt::to_directory_keys_lblk64(
328 fscrypt_test_data::KEY,
329 fscrypt_test_data::UUID,
330 fscrypt_test_data::DIR_NONCE,
331 )
332 .to_unwrapped_key(),
333 );
334 assert_eq!(
335 &unwrapped[..32],
336 &hex::decode("9f5d59cdaac1960b57edcbd102ab97f58bf921975a0280a20bc41f649152d196")
337 .unwrap()[..]
338 );
339
340 let cipher = FscryptInoLblk64DirCipher::new(&unwrapped);
341
342 let mut short_name = b"A".to_vec();
343 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut short_name).unwrap();
344 assert_eq!(short_name, hex::decode("882c820a8063b71194dafebb0e8c9858").unwrap());
345 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut short_name).unwrap();
346 assert_eq!(short_name, b"A");
347
348 let mut two_block_name = b"AAAAAAAAAAAAAAAAA".to_vec();
349 cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut two_block_name).unwrap();
350 assert_eq!(
351 two_block_name,
352 hex::decode("b3bac148afddc13adeb7fd8a324d2aeafbe599ec70a6be84e0769c00689ca7f9")
353 .unwrap()
354 );
355 cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut two_block_name).unwrap();
356 assert_eq!(two_block_name, b"AAAAAAAAAAAAAAAAA");
357 }
358}