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