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::InOutBuf;
6use aes::cipher::{
7    Block, BlockCipherDecrypt, BlockModeDecrypt, BlockModeEncrypt, KeyInit, KeyIvInit,
8};
9use anyhow::{Error, ensure};
10use std::collections::BTreeMap;
11use std::sync::Arc;
12pub use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
13pub use storage_xts::{Tweak, XtsInPlaceProcessor, XtsProcessor};
14use zerocopy::IntoBytes;
15use zx_status as zx;
16
17pub mod fscrypt_ino_lblk32;
18pub mod fscrypt_ino_lblk64;
19#[cfg(test)]
20mod fscrypt_test_data;
21pub(crate) mod fxfs;
22
23// TODO(https://fxbug.dev/375700939): Support different padding sizes based on
24// SET_ENCRYPTION_POLICY 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;
31const BLOCK_SIZE: usize = 4096;
32const MAX_FILENAME_LEN: usize = 255;
33const MAX_SYMLINK_LEN: usize = 4093;
34
35fn encrypt_filename_cts(
36    cts_key: &[u8; 32],
37    iv: &[u32; 4],
38    buffer: &mut Vec<u8>,
39    max_len: usize,
40) -> Result<(), Error> {
41    ensure!(buffer.len() <= max_len, "Filename too long");
42    buffer.resize(buffer.len().next_multiple_of(FSCRYPT_PADDING), 0);
43
44    let mut cbc = cbc::Encryptor::<aes::Aes256>::new(
45        cts_key.try_into().unwrap(),
46        iv.as_bytes().try_into().unwrap(),
47    );
48    let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
49    let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
50    let mut chunks = blocks.get_out();
51    cbc.encrypt_blocks(&mut chunks);
52    if chunks.len() >= 2 {
53        // We are encrypting with CTS.  In most cases, the padding will mean it's a multiple of
54        // FSCRYPT_PADDING bytes, so all we need to do is swap the last two chunks.  There is one
55        // exception: when the filename ends up being longer than max_len after padding.  In
56        // that case, all we have to do is trim the end after swapping the last two chunks.
57        chunks.swap(chunks.len() - 1, chunks.len() - 2);
58        buffer.truncate(max_len);
59    }
60    Ok(())
61}
62
63fn decrypt_filename_cts(
64    cts_key: &[u8; 32],
65    iv: &[u32; 4],
66    buffer: &mut Vec<u8>,
67    max_len: usize,
68) -> Result<(), Error> {
69    let alignment = buffer.len() % FSCRYPT_PADDING;
70    if alignment != 0 {
71        // For CTS, the only case we need to care about is when the encrypted filename is
72        // max_len bytes. In all other cases, the filename should be a multiple of FSCRYPT_PADDING
73        // bytes.
74        ensure!(buffer.len() == max_len, "Unexpected filename length");
75
76        // Decrypt the second to last block.
77        let cipher = aes::Aes256::new(cts_key.try_into().unwrap());
78        let mut out: Block<aes::Aes256> =
79            buffer[max_len - alignment - FSCRYPT_PADDING..max_len - alignment].try_into().unwrap();
80        cipher.decrypt_block(&mut out);
81
82        // Copy the extra bytes we need.
83        buffer.extend_from_slice(&out[alignment..]);
84    }
85
86    let mut cbc = cbc::Decryptor::<aes::Aes256>::new(
87        cts_key.try_into().unwrap(),
88        iv.as_bytes().try_into().unwrap(),
89    );
90    let inout = InOutBuf::<'_, '_, u8>::from(&mut buffer[..]);
91    let (mut blocks, _): (InOutBuf<'_, '_, Block<aes::Aes256>>, _) = inout.into_chunks();
92    let mut chunks = blocks.get_out();
93    if chunks.len() >= 2 {
94        chunks.swap(chunks.len() - 1, chunks.len() - 2);
95    }
96    cbc.decrypt_blocks(&mut chunks);
97
98    // Strip padding
99    while let Some(0) = buffer.last() {
100        buffer.pop();
101    }
102    Ok(())
103}
104
105/// Trait defining common methods shared across all ciphers.
106pub trait Cipher: std::fmt::Debug + Send + Sync {
107    /// Encrypts data in the `buffer`.
108    ///
109    /// * `offset` is the byte offset within the file.
110    /// * `buffer` is mutated in place.
111    ///
112    /// `buffer` *must* be 16 byte aligned.
113    fn encrypt(
114        &self,
115        ino: u64,
116        attribute_id: u64,
117        device_offset: u64,
118        file_offset: u64,
119        buffer: MutPtrByteSlice<'_>,
120    ) -> Result<(), Error>;
121
122    /// Decrypt the data in `buffer`.
123    ///
124    /// * `offset` is the byte offset within the file.
125    /// * `buffer` is mutated in place.
126    ///
127    /// `buffer` *must* be 16 byte aligned.
128    fn decrypt(
129        &self,
130        ino: u64,
131        attribute_id: u64,
132        device_offset: u64,
133        file_offset: u64,
134        buffer: MutPtrByteSlice<'_>,
135    ) -> Result<(), Error>;
136
137    /// Decrypts data from `src` into `dst`.
138    ///
139    /// * `file_offset` is the byte offset within the file.
140    /// * `src` and `dst` must have the same length.
141    /// * `src` and `dst` must both be aligned to 64 bytes.
142    fn decrypt_to(
143        &self,
144        ino: u64,
145        attribute_id: u64,
146        device_offset: u64,
147        file_offset: u64,
148        src: PtrByteSlice<'_>,
149        mut dst: MutPtrByteSlice<'_>,
150    ) -> Result<(), Error> {
151        dst.copy_from_ptr_slice(src);
152        self.decrypt(ino, attribute_id, device_offset, file_offset, dst)
153    }
154
155    /// Encrypts the filename contained in `buffer`.
156    fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
157
158    /// Decrypts the filename contained in `buffer`.
159    fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error>;
160
161    /// Encrypts the symlink target contained in `buffer`.
162    fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
163        self.encrypt_filename(object_id, buffer)
164    }
165
166    /// Decrypts the symlink target contained in `buffer`.
167    fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
168        self.decrypt_filename(object_id, buffer)
169    }
170
171    /// Returns a hash_code to use.
172    /// Note in the case of encrypted filenames, takes the raw encrypted bytes.
173    fn hash_code(&self, _raw_filename: &[u8], filename: &str) -> Option<u32>;
174
175    /// Returns a case-folded hash_code to use for 'filename'.
176    fn hash_code_casefold(&self, _filename: &str) -> u32;
177
178    /// True if supports inline encryption
179    fn supports_inline_encryption(&self) -> bool;
180
181    /// If this cipher type supports inline encryption, returns the (dun, slot) value.
182    /// Else returns None.
183    fn crypt_ctx(&self, ino: u64, attribute_id: u64, file_offset: u64) -> Option<(u64, u8)>;
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub enum KeyType {
188    Fxfs,
189    FscryptInoLblk32Dir,
190    FscryptInoLblk32File,
191    FscryptInoLblk64Dir,
192    FscryptInoLblk64File,
193}
194
195pub trait ToKeyType {
196    fn to_key_type(&self) -> Option<KeyType>;
197}
198
199impl ToKeyType for WrappedKey {
200    fn to_key_type(&self) -> Option<KeyType> {
201        match self {
202            WrappedKey::Fxfs(_) => Some(KeyType::Fxfs),
203            WrappedKey::FscryptInoLblk32Dir { .. } => Some(KeyType::FscryptInoLblk32Dir),
204            WrappedKey::FscryptInoLblk32File { .. } => Some(KeyType::FscryptInoLblk32File),
205            WrappedKey::FscryptInoLblk64Dir { .. } => Some(KeyType::FscryptInoLblk64Dir),
206            WrappedKey::FscryptInoLblk64File { .. } => Some(KeyType::FscryptInoLblk64File),
207            _ => None,
208        }
209    }
210}
211
212impl ToKeyType for EncryptionKey {
213    fn to_key_type(&self) -> Option<KeyType> {
214        match self {
215            EncryptionKey::LegacyFxfs(_) => unreachable!(),
216            EncryptionKey::Fxfs(_) => Some(KeyType::Fxfs),
217            EncryptionKey::FscryptInoLblk32Dir { .. } => Some(KeyType::FscryptInoLblk32Dir),
218            EncryptionKey::FscryptInoLblk32File { .. } => Some(KeyType::FscryptInoLblk32File),
219            EncryptionKey::FscryptInoLblk64Dir { .. } => Some(KeyType::FscryptInoLblk64Dir),
220            EncryptionKey::FscryptInoLblk64File { .. } => Some(KeyType::FscryptInoLblk64File),
221        }
222    }
223}
224
225impl ToKeyType for KeyType {
226    fn to_key_type(&self) -> Option<KeyType> {
227        Some(*self)
228    }
229}
230
231/// Helper function to obtain a Cipher for a key.
232/// Uses key to interpret the meaning of the UnwrappedKey blob and then creates a
233/// cipher instance from the blob, returning it.
234#[inline]
235pub fn key_to_cipher(
236    key_type: &impl ToKeyType,
237    unwrapped_key: &UnwrappedKey,
238) -> Result<Arc<dyn Cipher>, zx::Status> {
239    key_type
240        .to_key_type()
241        .map(|key_type| match key_type {
242            KeyType::Fxfs => Arc::new(fxfs::FxfsCipher::new(unwrapped_key)) as Arc<dyn Cipher>,
243            KeyType::FscryptInoLblk32Dir => {
244                Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32DirCipher::new(unwrapped_key))
245            }
246            KeyType::FscryptInoLblk32File => {
247                Arc::new(fscrypt_ino_lblk32::FscryptInoLblk32FileCipher::new(unwrapped_key))
248            }
249            KeyType::FscryptInoLblk64Dir => {
250                Arc::new(fscrypt_ino_lblk64::FscryptInoLblk64DirCipher::new(unwrapped_key))
251            }
252            KeyType::FscryptInoLblk64File => {
253                Arc::new(fscrypt_ino_lblk64::FscryptInoLblk64FileCipher::new(unwrapped_key))
254            }
255        })
256        .ok_or(zx::Status::NOT_SUPPORTED)
257}
258
259#[derive(Clone, Debug)]
260pub enum CipherHolder {
261    Cipher(Arc<dyn Cipher>),
262    Unavailable,
263}
264
265impl CipherHolder {
266    pub fn into_cipher(self) -> Option<Arc<dyn Cipher>> {
267        match self {
268            CipherHolder::Cipher(c) => Some(c),
269            _ => None,
270        }
271    }
272}
273
274/// A container that holds ciphers related to a specific object.
275#[derive(Clone, Debug, Default)]
276pub struct CipherSet(BTreeMap<u64, CipherHolder>);
277impl CipherSet {
278    pub fn find_key(self: &Arc<Self>, id: u64) -> FindKeyResult {
279        match self.0.get(&id) {
280            Some(CipherHolder::Cipher(cipher)) => FindKeyResult::Key(Arc::clone(cipher)),
281            Some(CipherHolder::Unavailable) => FindKeyResult::Unavailable,
282            None => FindKeyResult::NotFound,
283        }
284    }
285
286    pub fn add_key(&mut self, id: u64, cipher: CipherHolder) {
287        self.0.insert(id, cipher);
288    }
289}
290impl From<Vec<(u64, CipherHolder)>> for CipherSet {
291    fn from(keys: Vec<(u64, CipherHolder)>) -> Self {
292        Self(keys.into_iter().collect())
293    }
294}
295impl From<BTreeMap<u64, CipherHolder>> for CipherSet {
296    fn from(keys: BTreeMap<u64, CipherHolder>) -> Self {
297        Self(keys)
298    }
299}
300
301pub enum FindKeyResult {
302    /// No key registered with that key_id.
303    NotFound,
304    /// The key is known, but not available for use (cannot be unwrapped).
305    Unavailable,
306    Key(Arc<dyn Cipher>),
307}