Skip to main content

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