Skip to main content

fxfs_crypto/cipher/
fxfs.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 super::{Cipher, SECTOR_SIZE, Tweak, UnwrappedKey, XtsProcessor};
5use aes::Aes256;
6use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
7use anyhow::Error;
8use log::warn;
9pub use storage_ptr_slice::MutPtrByteSlice;
10use zerocopy::IntoBytes;
11
12#[derive(Debug)]
13pub struct FxfsCipher {
14    key: Aes256,
15    legacy: bool,
16}
17impl FxfsCipher {
18    pub fn new(key: &UnwrappedKey) -> Self {
19        Self { key: Aes256::new(key.as_slice().try_into().unwrap()), legacy: false }
20    }
21
22    pub fn new_legacy(key: &UnwrappedKey) -> Self {
23        Self { key: Aes256::new(key.as_slice().try_into().unwrap()), legacy: true }
24    }
25}
26impl Cipher for FxfsCipher {
27    fn encrypt(
28        &self,
29        _ino: u64,
30        attribute_id: u64,
31        _device_offset: u64,
32        file_offset: u64,
33        mut buffer: MutPtrByteSlice<'_>,
34    ) -> Result<(), Error> {
35        fxfs_trace::duration!("encrypt", "len" => buffer.len());
36        assert_eq!(file_offset % SECTOR_SIZE, 0);
37        let mut sector_offset = file_offset / SECTOR_SIZE;
38        assert_eq!(buffer.len() % (SECTOR_SIZE as usize), 0);
39        let upper_tweak = if self.legacy { 0 } else { (attribute_id as u128) << 64 };
40        let mut offset = 0;
41        while offset < buffer.len() {
42            let sector = buffer.reborrow().subslice_mut(offset..offset + SECTOR_SIZE as usize);
43            let mut tweak = Tweak(upper_tweak | (sector_offset as u128));
44            // The same key is used for encrypting the data and computing the tweak.
45            self.key.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
46            self.key.encrypt_with_backend(XtsProcessor::new_in_place(tweak, sector));
47            sector_offset += 1;
48            offset += SECTOR_SIZE as usize;
49        }
50        Ok(())
51    }
52
53    fn decrypt(
54        &self,
55        _ino: u64,
56        attribute_id: u64,
57        _device_offset: u64,
58        file_offset: u64,
59        mut buffer: MutPtrByteSlice<'_>,
60    ) -> Result<(), Error> {
61        fxfs_trace::duration!("decrypt", "len" => buffer.len());
62        assert_eq!(file_offset % SECTOR_SIZE, 0);
63        let mut sector_offset = file_offset / SECTOR_SIZE;
64        assert_eq!(buffer.len() % (SECTOR_SIZE as usize), 0);
65        let upper_tweak = if self.legacy { 0 } else { (attribute_id as u128) << 64 };
66        let mut offset = 0;
67        while offset < buffer.len() {
68            let sector = buffer.reborrow().subslice_mut(offset..offset + SECTOR_SIZE as usize);
69            let mut tweak = Tweak(upper_tweak | (sector_offset as u128));
70            // The same key is used for encrypting the data and computing the tweak.
71            self.key.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
72            self.key.decrypt_with_backend(XtsProcessor::new_in_place(tweak, sector));
73            sector_offset += 1;
74            offset += SECTOR_SIZE as usize;
75        }
76        Ok(())
77    }
78
79    fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
80        debug_assert!(false, "encrypt_filename called on fxfs cipher");
81        Err(zx_status::Status::NOT_SUPPORTED.into())
82    }
83
84    fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
85        // NOTE: This isn't a debug assertion because it would trip on the golden image tests.
86        warn!("decrypt_filename called on fxfs cipher");
87        Err(zx_status::Status::NOT_SUPPORTED.into())
88    }
89
90    fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
91        debug_assert!(false, "hash_code called on fxfs cipher");
92        None
93    }
94
95    fn hash_code_casefold(&self, _filename: &str) -> u32 {
96        debug_assert!(false, "hash_code_casefold called on fxfs cipher");
97        0
98    }
99
100    fn supports_inline_encryption(&self) -> bool {
101        false
102    }
103
104    fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u32, u8)> {
105        None
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::{Cipher, FxfsCipher, SECTOR_SIZE};
112    use crate::UnwrappedKey;
113    use storage_ptr_slice::MutPtrByteSlice;
114
115    #[test]
116    fn test_legacy_fxfs_cipher_ignores_attribute_id() {
117        let key = UnwrappedKey::new(vec![0x42; 32]);
118        let cipher = FxfsCipher::new_legacy(&key);
119        let mut buf0 = vec![0x12; SECTOR_SIZE as usize];
120        let mut buf1 = vec![0x12; SECTOR_SIZE as usize];
121
122        cipher.encrypt(1, 0, 0, 0, MutPtrByteSlice::from(&mut buf0[..])).expect("encrypt attr 0");
123        cipher.encrypt(1, 4, 0, 0, MutPtrByteSlice::from(&mut buf1[..])).expect("encrypt attr 4");
124        assert_eq!(
125            buf0, buf1,
126            "LegacyFxfsCipher should produce identical ciphertext for same file_offset regardless \
127             of attribute_id"
128        );
129    }
130
131    #[test]
132    fn test_fxfs_cipher_domain_separates_attribute_id() {
133        let key = UnwrappedKey::new(vec![0x42; 32]);
134        let cipher = FxfsCipher::new(&key);
135        let mut buf0 = vec![0x12; SECTOR_SIZE as usize];
136        let mut buf1 = vec![0x12; SECTOR_SIZE as usize];
137
138        cipher.encrypt(1, 0, 0, 0, MutPtrByteSlice::from(&mut buf0[..])).expect("encrypt attr 0");
139        cipher.encrypt(1, 4, 0, 0, MutPtrByteSlice::from(&mut buf1[..])).expect("encrypt attr 4");
140        assert_ne!(buf0, buf1, "FxfsCipher should domain-separate tweaks across attribute_id");
141
142        // Verify decryption works correctly for each attribute_id
143        cipher.decrypt(1, 0, 0, 0, MutPtrByteSlice::from(&mut buf0[..])).expect("decrypt attr 0");
144        assert_eq!(buf0, vec![0x12; SECTOR_SIZE as usize]);
145
146        cipher.decrypt(1, 4, 0, 0, MutPtrByteSlice::from(&mut buf1[..])).expect("decrypt attr 4");
147        assert_eq!(buf1, vec![0x12; SECTOR_SIZE as usize]);
148    }
149}