fxfs_crypto/
cipher.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use crate::{EncryptionKey, UnwrappedKey, WrappedKey};
5use aes::cipher::generic_array::GenericArray;
6use aes::cipher::inout::InOut;
7use aes::cipher::typenum::consts::U16;
8use aes::cipher::{BlockBackend, BlockClosure, BlockSizeUser};
9use anyhow::Error;
10use static_assertions::assert_cfg;
11use std::collections::BTreeMap;
12use std::sync::Arc;
13use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, transmute_mut};
14use zx_status as zx;
15
16pub mod fscrypt_ino_lblk32;
17#[cfg(test)]
18mod fscrypt_test_data;
19pub(crate) mod fxfs;
20
21// TODO(https://fxbug.dev/375700939): Support different padding sizes based on SET_ENCRYPTION_POLICY
22// flags.
23// Note: This constant is used in platform code. It would be nice to move all fscrypt
24// internals into fxfs_lib and keep platform as simple as possible.
25pub const FSCRYPT_PADDING: usize = 16;
26// Fxfs will always use a block size >= 512 bytes, so we just assume a sector size of 512 bytes,
27// which will work fine even if a different block size is used by Fxfs or the underlying device.
28const SECTOR_SIZE: u64 = 512;
29
30/// Trait defining common methods shared across all ciphers.
31pub trait Cipher: std::fmt::Debug + Send + Sync {
32    /// Encrypts data in the `buffer`.
33    ///
34    /// * `offset` is the byte offset within the file.
35    /// * `buffer` is mutated in place.
36    ///
37    /// `buffer` *must* be 16 byte aligned.
38    fn encrypt(
39        &self,
40        ino: u64,
41        device_offset: u64,
42        file_offset: u64,
43        buffer: &mut [u8],
44    ) -> Result<(), Error>;
45
46    /// Decrypt the data in `buffer`.
47    ///
48    /// * `offset` is the byte offset within the file.
49    /// * `buffer` is mutated in place.
50    ///
51    /// `buffer` *must* be 16 byte aligned.
52    fn decrypt(
53        &self,
54        ino: u64,
55        device_offset: u64,
56        file_offset: u64,
57        buffer: &mut [u8],
58    ) -> Result<(), Error>;
59
60    /// Encrypts the filename contained in `buffer`.
61    fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
62
63    /// Decrypts the filename contained in `buffer`.
64    fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
65
66    /// Encrypts the symlink target contained in `buffer`.
67    fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
68        self.encrypt_filename(object_id, buffer)
69    }
70
71    /// Decrypts the symlink target contained in `buffer`.
72    fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
73        self.decrypt_filename(object_id, buffer)
74    }
75
76    /// Returns a hash_code to use.
77    /// Note in the case of encrypted filenames, takes the raw encrypted bytes.
78    fn hash_code(&self, _raw_filename: &[u8], filename: &str) -> Option<u32>;
79
80    /// Returns a case-folded hash_code to use for 'filename'.
81    fn hash_code_casefold(&self, _filename: &str) -> u32;
82
83    /// True if supports inline encryption
84    fn supports_inline_encryption(&self) -> bool;
85
86    /// If this cipher type supports inline encryption, returns the (dun, slot) value.
87    /// Else returns None.
88    fn crypt_ctx(&self, ino: u64, file_offset: u64) -> Option<(u32, u8)>;
89}
90
91#[derive(Clone, Copy, Debug, PartialEq)]
92pub enum KeyType {
93    Fxfs,
94    FscryptInoLblk32Dir,
95    FscryptInoLblk32File,
96}
97
98pub trait ToKeyType {
99    fn to_key_type(&self) -> Option<KeyType>;
100}
101
102impl ToKeyType for WrappedKey {
103    fn to_key_type(&self) -> Option<KeyType> {
104        match self {
105            WrappedKey::Fxfs(_) => Some(KeyType::Fxfs),
106            WrappedKey::FscryptInoLblk32Dir { .. } => Some(KeyType::FscryptInoLblk32Dir),
107            WrappedKey::FscryptInoLblk32File { .. } => Some(KeyType::FscryptInoLblk32File),
108            _ => None,
109        }
110    }
111}
112
113impl ToKeyType for EncryptionKey {
114    fn to_key_type(&self) -> Option<KeyType> {
115        match self {
116            EncryptionKey::Fxfs(_) => Some(KeyType::Fxfs),
117            EncryptionKey::FscryptInoLblk32Dir { .. } => Some(KeyType::FscryptInoLblk32Dir),
118            EncryptionKey::FscryptInoLblk32File { .. } => Some(KeyType::FscryptInoLblk32File),
119        }
120    }
121}
122
123impl ToKeyType for KeyType {
124    fn to_key_type(&self) -> Option<KeyType> {
125        Some(*self)
126    }
127}
128
129/// Helper function to obtain a Cipher for a key.
130/// Uses key to interpret the meaning of the UnwrappedKey blob and then creates a
131/// cipher instance from the blob, returning it.
132#[inline]
133pub fn key_to_cipher(
134    key_type: &impl ToKeyType,
135    unwrapped_key: &UnwrappedKey,
136) -> Result<Arc<dyn Cipher>, zx::Status> {
137    key_type
138        .to_key_type()
139        .map(|key_type| match key_type {
140            KeyType::Fxfs => Arc::new(fxfs::FxfsCipher::new(&unwrapped_key)) as Arc<dyn Cipher>,
141            KeyType::FscryptInoLblk32Dir => {
142                Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32DirCipher::new(&unwrapped_key))
143            }
144            KeyType::FscryptInoLblk32File => {
145                Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32FileCipher::new(&unwrapped_key))
146            }
147        })
148        .ok_or(zx::Status::NOT_SUPPORTED)
149}
150
151#[derive(Clone, Debug)]
152pub enum CipherHolder {
153    Cipher(Arc<dyn Cipher>),
154    Unavailable(KeyType),
155}
156
157impl CipherHolder {
158    pub fn into_cipher(self) -> Option<Arc<dyn Cipher>> {
159        match self {
160            CipherHolder::Cipher(c) => Some(c),
161            _ => None,
162        }
163    }
164}
165
166/// A container that holds ciphers related to a specific object.
167#[derive(Clone, Debug, Default)]
168pub struct CipherSet(BTreeMap<u64, CipherHolder>);
169impl CipherSet {
170    pub fn find_key(self: &Arc<Self>, id: u64) -> FindKeyResult {
171        match self.0.get(&id) {
172            Some(CipherHolder::Cipher(cipher)) => FindKeyResult::Key(Arc::clone(cipher)),
173            Some(CipherHolder::Unavailable(key_type)) => FindKeyResult::Unavailable(*key_type),
174            None => FindKeyResult::NotFound,
175        }
176    }
177
178    pub fn add_key(&mut self, id: u64, cipher: CipherHolder) {
179        self.0.insert(id, cipher);
180    }
181}
182impl From<Vec<(u64, CipherHolder)>> for CipherSet {
183    fn from(keys: Vec<(u64, CipherHolder)>) -> Self {
184        Self(keys.into_iter().collect())
185    }
186}
187impl From<BTreeMap<u64, CipherHolder>> for CipherSet {
188    fn from(keys: BTreeMap<u64, CipherHolder>) -> Self {
189        Self(keys)
190    }
191}
192
193pub enum FindKeyResult {
194    /// No key registered with that key_id.
195    NotFound,
196    /// The key is known, but not available for use (cannot be unwrapped).
197    Unavailable(KeyType),
198    Key(Arc<dyn Cipher>),
199}
200
201// This assumes little-endianness which is likely to always be the case.
202assert_cfg!(target_endian = "little");
203#[derive(IntoBytes, KnownLayout, FromBytes, Immutable)]
204#[repr(C)]
205struct Tweak(u128);
206
207pub fn xor_in_place(a: &mut [u8], b: &[u8]) {
208    for (b1, b2) in a.iter_mut().zip(b.iter()) {
209        *b1 ^= *b2;
210    }
211}
212
213// To be used with encrypt_with_backend.
214struct CbcEncryptProcessor<'a> {
215    tweak: Tweak,
216    data: &'a mut [u8],
217}
218
219impl<'a> CbcEncryptProcessor<'a> {
220    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
221        Self { tweak, data }
222    }
223}
224
225impl BlockSizeUser for CbcEncryptProcessor<'_> {
226    type BlockSize = U16;
227}
228
229impl BlockClosure for CbcEncryptProcessor<'_> {
230    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
231        let Self { mut tweak, data } = self;
232        for block in data.chunks_exact_mut(16) {
233            xor_in_place(block, &tweak.0.to_le_bytes());
234            let chunk: &mut GenericArray<u8, _> = GenericArray::from_mut_slice(block);
235            backend.proc_block(InOut::from(chunk));
236            tweak.0 = u128::from_le_bytes(block.try_into().unwrap())
237        }
238    }
239}
240
241// To be used with decrypt_with_backend.
242struct CbcDecryptProcessor<'a> {
243    tweak: Tweak,
244    data: &'a mut [u8],
245}
246
247impl<'a> CbcDecryptProcessor<'a> {
248    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
249        Self { tweak, data }
250    }
251}
252
253impl BlockSizeUser for CbcDecryptProcessor<'_> {
254    type BlockSize = U16;
255}
256
257impl BlockClosure for CbcDecryptProcessor<'_> {
258    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
259        let Self { mut tweak, data } = self;
260        for block in data.chunks_exact_mut(16) {
261            let ciphertext = block.to_vec();
262            let chunk = GenericArray::from_mut_slice(block);
263            backend.proc_block(InOut::from(chunk));
264            xor_in_place(block, &tweak.0.to_le_bytes());
265            tweak.0 = u128::from_le_bytes(ciphertext.try_into().unwrap());
266        }
267    }
268}
269
270// To be used with encrypt|decrypt_with_backend.
271struct XtsProcessor<'a> {
272    tweak: Tweak,
273    data: &'a mut [u8],
274}
275
276impl<'a> XtsProcessor<'a> {
277    // `tweak` should be encrypted.  `data` should be a single sector and *must* be 16 byte aligned.
278    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
279        assert_eq!(data.as_ptr() as usize & 15, 0, "data must be 16 byte aligned");
280        Self { tweak, data }
281    }
282}
283
284impl BlockSizeUser for XtsProcessor<'_> {
285    type BlockSize = U16;
286}
287
288impl BlockClosure for XtsProcessor<'_> {
289    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
290        let Self { mut tweak, data } = self;
291        let (chunks, _remainder) = data.as_chunks_mut::<16>();
292        for chunk in chunks {
293            let val: &mut zerocopy::Unalign<u128> = transmute_mut!(chunk);
294            val.set(val.get() ^ tweak.0);
295
296            let chunk_ga: &mut GenericArray<u8, U16> = chunk.into();
297            backend.proc_block(InOut::from(chunk_ga));
298
299            let val: &mut zerocopy::Unalign<u128> = transmute_mut!(chunk);
300            val.set(val.get() ^ tweak.0);
301            tweak.0 = (tweak.0 << 1) ^ ((tweak.0 as i128 >> 127) as u128 & 0x87);
302        }
303    }
304}