1use crate::xattr;
5use aes::cipher::inout::InOutBuf;
6use aes::cipher::{BlockDecrypt, BlockDecryptMut, BlockEncrypt, KeyInit, KeyIvInit};
7use aes::Block;
8use anyhow::Error;
9use fscrypt::{hkdf, Context};
10use siphasher::sip::SipHasher;
11use std::hash::Hasher;
12use zerocopy::{FromBytes, IntoBytes};
13
14const NAME_XATTR_CRYPTO_CONTEXT: &[u8] = b"c";
15
16pub fn try_read_context_from_xattr(
17 xattr: &Vec<xattr::XattrEntry>,
18) -> Result<Option<Context>, Error> {
19 let raw_context = if let Some(content) = xattr.iter().find(|entry| {
20 entry.index == xattr::Index::Encryption && *entry.name == *NAME_XATTR_CRYPTO_CONTEXT
21 }) {
22 &content.value
23 } else {
24 return Ok(None);
26 };
27 Context::try_from_bytes(raw_context.as_ref())
28}
29
30pub struct PerFileDecryptor {
31 xts_key1: aes::Aes256,
33 xts_key2: aes::Aes256,
34 cts_key: [u8; 32],
36 dirhash_key: [u8; 16],
38
39 ino_hash_key: Option<[u8; 16]>,
41}
42
43impl PerFileDecryptor {
44 pub(super) fn new(main_key: &[u8; 64], context: Context, uuid: &[u8; 16]) -> Self {
45 if context.flags & fscrypt::POLICY_FLAGS_INO_LBLK_32 != 0 {
46 let mut hdkf_info = [0; 17];
50 hdkf_info[1..17].copy_from_slice(uuid);
51 hdkf_info[0] = fscrypt::ENCRYPTION_MODE_AES_256_XTS;
52 let xts_key = hkdf::fscrypt_hkdf::<64>(
53 main_key,
54 &hdkf_info,
55 hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY,
56 );
57 hdkf_info[0] = fscrypt::ENCRYPTION_MODE_AES_256_CTS;
58 let cts_key = hkdf::fscrypt_hkdf::<32>(
59 main_key,
60 &hdkf_info,
61 hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY,
62 );
63 let dirhash_key =
64 hkdf::fscrypt_hkdf::<16>(main_key, &context.nonce, hkdf::HKDF_CONTEXT_DIRHASH_KEY);
65 let ino_hash_key =
66 Some(hkdf::fscrypt_hkdf::<16>(main_key, &[], hkdf::HKDF_CONTEXT_INODE_HASH_KEY));
67 Self {
68 xts_key1: aes::Aes256::new((&xts_key[..32]).into()),
69 xts_key2: aes::Aes256::new((&xts_key[32..]).into()),
70 cts_key,
71 dirhash_key,
72 ino_hash_key,
73 }
74 } else {
75 let key = hkdf::fscrypt_hkdf::<64>(
78 main_key,
79 &context.nonce,
80 hkdf::HKDF_CONTEXT_PER_FILE_ENC_KEY,
81 );
82 let dirhash_key =
83 hkdf::fscrypt_hkdf::<16>(main_key, &context.nonce, hkdf::HKDF_CONTEXT_DIRHASH_KEY);
84 let cts_key: [u8; 32] = key[..32].try_into().unwrap();
85 Self {
86 xts_key1: aes::Aes256::new((&key[..32]).into()),
87 xts_key2: aes::Aes256::new((&key[32..]).into()),
88 cts_key,
89 dirhash_key,
90 ino_hash_key: None,
91 }
92 }
93 }
94
95 pub fn decrypt_data(&self, ino: u32, block_num: u32, buffer: &mut [u8]) {
96 assert_eq!((buffer.as_ptr() as usize) % 16, 0, "Require 16-byte aligned buffers");
97 assert_eq!(buffer.len() % 16, 0, "Require buffters be multiple of 16-bytes");
98 let key1 = self.xts_key1.clone();
100 let key2 = self.xts_key2.clone();
101 let mut tweak: u128 = if let Some(ino_hash_key) = self.ino_hash_key {
102 let mut hasher = SipHasher::new_with_key(&ino_hash_key);
103 let ino64 = ino as u64;
104 hasher.write(ino64.as_bytes());
105 hasher.finish() as u32 + block_num
106 } else {
107 block_num
108 } as u128;
109 key2.encrypt_block(tweak.as_mut_bytes().into());
110 for chunk in buffer.chunks_exact_mut(16) {
111 *u128::mut_from_bytes(chunk).unwrap() ^= tweak;
112 key1.decrypt_block(chunk.into());
113 *u128::mut_from_bytes(chunk).unwrap() ^= tweak;
114 tweak = (tweak << 1) ^ (if tweak >> 127 != 0 { 0x87 } else { 0 });
115 }
116 }
117
118 pub fn decrypt_filename_data(&self, ino: u32, data: &mut [u8]) {
120 let mut iv = [0u8; 16];
121 if let Some(ino_hash_key) = self.ino_hash_key {
122 let mut hasher = SipHasher::new_with_key(&ino_hash_key);
123 hasher.write((ino as u64).as_bytes());
124 iv[..4].copy_from_slice(&hasher.finish().as_bytes()[..4]);
125 }
126 let mut cbc = cbc::Decryptor::<aes::Aes256>::new((&self.cts_key).into(), (&iv).into());
130 let inout: InOutBuf<'_, '_, u8> = data.into();
131 let (mut blocks, tail): (InOutBuf<'_, '_, Block>, _) = inout.into_chunks();
132 debug_assert_eq!(tail.len(), 0);
133 let mut chunks = blocks.get_out();
134 if chunks.len() >= 2 {
135 chunks.swap(chunks.len() - 1, chunks.len() - 2);
136 }
137 cbc.decrypt_blocks_mut(&mut chunks);
138 }
139
140 pub fn dirhash_key(&self) -> &[u8; 16] {
141 &self.dirhash_key
142 }
143}