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::{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};
14use zx_status as zx;
15
16mod fscrypt_ino_lblk32;
17pub(crate) mod fxfs;
18
19// TODO(https://fxbug.dev/375700939): Support different padding sizes based on SET_ENCRYPTION_POLICY
20// flags.
21// Note: This constant is used in platform code. It would be nice to move all fscrypt
22// internals into fxfs_lib and keep platform as simple as possible.
23pub const FSCRYPT_PADDING: usize = 16;
24// Fxfs will always use a block size >= 512 bytes, so we just assume a sector size of 512 bytes,
25// which will work fine even if a different block size is used by Fxfs or the underlying device.
26const SECTOR_SIZE: u64 = 512;
27
28/// Trait defining common methods shared across all ciphers.
29pub trait Cipher: std::fmt::Debug + Send + Sync {
30    /// Encrypts data in the `buffer`.
31    ///
32    /// * `offset` is the byte offset within the file.
33    /// * `buffer` is mutated in place.
34    ///
35    /// `buffer` *must* be 16 byte aligned.
36    fn encrypt(
37        &self,
38        ino: u64,
39        device_offset: u64,
40        file_offset: u64,
41        buffer: &mut [u8],
42    ) -> Result<(), Error>;
43
44    /// Decrypt the data in `buffer`.
45    ///
46    /// * `offset` is the byte offset within the file.
47    /// * `buffer` is mutated in place.
48    ///
49    /// `buffer` *must* be 16 byte aligned.
50    fn decrypt(
51        &self,
52        ino: u64,
53        device_offset: u64,
54        file_offset: u64,
55        buffer: &mut [u8],
56    ) -> Result<(), Error>;
57
58    /// Encrypts the filename contained in `buffer`.
59    fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
60
61    /// Decrypts the filename contained in `buffer`.
62    fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
63
64    /// Returns a hash_code to use.
65    /// Note in the case of encrypted filenames, takes the raw encrypted bytes.
66    fn hash_code(&self, _raw_filename: &[u8], filename: &str) -> u32;
67
68    /// Returns a case-folded hash_code to use for 'filename'.
69    fn hash_code_casefold(&self, _filename: &str) -> u32;
70
71    /// True if supports inline encryption
72    fn supports_inline_encryption(&self) -> bool;
73
74    /// If this cipher type supports inline encryption, returns the (dun, slot) value.
75    /// Else returns None.
76    fn crypt_ctx(&self, ino: u64, file_offset: u64) -> Option<(u32, u8)>;
77}
78
79/// Helper function to obtain a Cipher for a key.
80/// Uses key to interpret the meaning of the UnwrappedKey blob and then creates a
81/// cipher instance from the blob, returning it.
82#[inline]
83pub(crate) fn key_to_cipher(
84    key: &WrappedKey,
85    unwrapped_key: &UnwrappedKey,
86) -> Result<Option<Arc<dyn Cipher>>, zx::Status> {
87    match key {
88        WrappedKey::Fxfs(_) => Ok(Some(Arc::new(fxfs::FxfsCipher::new(&unwrapped_key)))),
89        WrappedKey::FscryptInoLblk32Dir { .. } => {
90            Ok(Some(Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32DirCipher::new(&unwrapped_key))))
91        }
92        WrappedKey::FscryptInoLblk32File { .. } => {
93            Ok(Some(Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32FileCipher::new(&unwrapped_key))))
94        }
95        _ => Err(zx::Status::NOT_SUPPORTED),
96    }
97}
98
99/// A container that holds ciphers related to a specific object.
100#[derive(Clone, Debug, Default)]
101pub struct CipherSet(BTreeMap<u64, Option<Arc<dyn Cipher>>>);
102impl CipherSet {
103    pub fn find_key(self: &Arc<Self>, id: u64) -> FindKeyResult {
104        if let Some(cipher) = self.0.get(&id) {
105            if let Some(cipher) = cipher {
106                FindKeyResult::Key(Arc::clone(cipher))
107            } else {
108                FindKeyResult::Unavailable
109            }
110        } else {
111            FindKeyResult::NotFound
112        }
113    }
114
115    pub fn add_key(&mut self, id: u64, cipher: Option<Arc<dyn Cipher>>) {
116        self.0.insert(id, cipher);
117    }
118}
119impl From<Vec<(u64, Option<Arc<dyn Cipher>>)>> for CipherSet {
120    fn from(keys: Vec<(u64, Option<Arc<dyn Cipher>>)>) -> Self {
121        Self(keys.into_iter().collect())
122    }
123}
124impl From<BTreeMap<u64, Option<Arc<dyn Cipher>>>> for CipherSet {
125    fn from(keys: BTreeMap<u64, Option<Arc<dyn Cipher>>>) -> Self {
126        Self(keys)
127    }
128}
129
130pub enum FindKeyResult {
131    /// No key registered with that key_id.
132    NotFound,
133    /// The key is known, but not available for use (cannot be unwrapped).
134    Unavailable,
135    Key(Arc<dyn Cipher>),
136}
137
138// This assumes little-endianness which is likely to always be the case.
139assert_cfg!(target_endian = "little");
140#[derive(IntoBytes, KnownLayout, FromBytes, Immutable)]
141#[repr(C)]
142struct Tweak(u128);
143
144pub fn xor_in_place(a: &mut [u8], b: &[u8]) {
145    for (b1, b2) in a.iter_mut().zip(b.iter()) {
146        *b1 ^= *b2;
147    }
148}
149
150// To be used with encrypt_with_backend.
151struct CbcEncryptProcessor<'a> {
152    tweak: Tweak,
153    data: &'a mut [u8],
154}
155
156impl<'a> CbcEncryptProcessor<'a> {
157    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
158        Self { tweak, data }
159    }
160}
161
162impl BlockSizeUser for CbcEncryptProcessor<'_> {
163    type BlockSize = U16;
164}
165
166impl BlockClosure for CbcEncryptProcessor<'_> {
167    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
168        let Self { mut tweak, data } = self;
169        for block in data.chunks_exact_mut(16) {
170            xor_in_place(block, &tweak.0.to_le_bytes());
171            let chunk: &mut GenericArray<u8, _> = GenericArray::from_mut_slice(block);
172            backend.proc_block(InOut::from(chunk));
173            tweak.0 = u128::from_le_bytes(block.try_into().unwrap())
174        }
175    }
176}
177
178// To be used with decrypt_with_backend.
179struct CbcDecryptProcessor<'a> {
180    tweak: Tweak,
181    data: &'a mut [u8],
182}
183
184impl<'a> CbcDecryptProcessor<'a> {
185    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
186        Self { tweak, data }
187    }
188}
189
190impl BlockSizeUser for CbcDecryptProcessor<'_> {
191    type BlockSize = U16;
192}
193
194impl BlockClosure for CbcDecryptProcessor<'_> {
195    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
196        let Self { mut tweak, data } = self;
197        for block in data.chunks_exact_mut(16) {
198            let ciphertext = block.to_vec();
199            let chunk = GenericArray::from_mut_slice(block);
200            backend.proc_block(InOut::from(chunk));
201            xor_in_place(block, &tweak.0.to_le_bytes());
202            tweak.0 = u128::from_le_bytes(ciphertext.try_into().unwrap());
203        }
204    }
205}
206
207// To be used with encrypt|decrypt_with_backend.
208struct XtsProcessor<'a> {
209    tweak: Tweak,
210    data: &'a mut [u8],
211}
212
213impl<'a> XtsProcessor<'a> {
214    // `tweak` should be encrypted.  `data` should be a single sector and *must* be 16 byte aligned.
215    fn new(tweak: Tweak, data: &'a mut [u8]) -> Self {
216        assert_eq!(data.as_ptr() as usize & 15, 0, "data must be 16 byte aligned");
217        Self { tweak, data }
218    }
219}
220
221impl BlockSizeUser for XtsProcessor<'_> {
222    type BlockSize = U16;
223}
224
225impl BlockClosure for XtsProcessor<'_> {
226    fn call<B: BlockBackend<BlockSize = Self::BlockSize>>(self, backend: &mut B) {
227        let Self { mut tweak, data } = self;
228        for chunk in data.chunks_exact_mut(16) {
229            let ptr = chunk.as_mut_ptr() as *mut u128;
230            // SAFETY: We know each chunk is exactly 16 bytes and it should be safe to transmute to
231            // u128 and GenericArray<u8, U16>.  There are safe ways of doing the following, but this
232            // is extremely performance sensitive, and even seemingly innocuous changes here can
233            // have an order-of-magnitude impact on what the compiler produces and that can be seen
234            // in our benchmarks.  This assumes little-endianness which is likely to always be the
235            // case.
236            unsafe {
237                *ptr ^= tweak.0;
238                let chunk = ptr as *mut GenericArray<u8, U16>;
239                backend.proc_block(InOut::from_raw(chunk, chunk));
240                *ptr ^= tweak.0;
241            }
242            tweak.0 = (tweak.0 << 1) ^ ((tweak.0 as i128 >> 127) as u128 & 0x87);
243        }
244    }
245}