fxfs_crypt_common/
lib.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.
4
5use aes_gcm_siv::aead::Aead;
6use aes_gcm_siv::{Aes256GcmSiv, Key, KeyInit as _, Nonce};
7use async_trait::async_trait;
8use fuchsia_sync::Mutex;
9use fxfs_crypto::{
10    Crypt, EncryptionKey, FscryptKeyIdentifierAndNonce, KeyPurpose, ObjectType, UnwrappedKey,
11    WrappedKey, WrappingKeyId,
12};
13use rand::rngs::StdRng;
14use rand::{RngCore, SeedableRng};
15use std::collections::hash_map::{Entry, HashMap};
16use std::sync::atomic::{AtomicBool, Ordering};
17use zx_status as zx;
18
19fn zero_extended_nonce(val: u64) -> Nonce {
20    let mut nonce = Nonce::default();
21    nonce.as_mut_slice()[..8].copy_from_slice(&val.to_le_bytes());
22    nonce
23}
24
25struct Cipher {
26    // Used to create or unwrap `EncryptionKey::Fxfs`.
27    aes_gcm_siv: Aes256GcmSiv,
28    // Used to create or unwrap `EncryptionKey::FscryptInoLblk32Dir`.
29    wrapping_key: [u8; 32],
30}
31
32impl Cipher {
33    fn new(wrapping_key: [u8; 32]) -> Self {
34        Self {
35            aes_gcm_siv: Aes256GcmSiv::new(Key::<Aes256GcmSiv>::from_slice(&wrapping_key)),
36            wrapping_key,
37        }
38    }
39
40    fn encrypt(&self, nonce: &Nonce, plaintext: &[u8]) -> Result<Vec<u8>, zx::Status> {
41        self.aes_gcm_siv.encrypt(nonce, plaintext).map_err(|_e| zx::Status::INTERNAL)
42    }
43
44    fn decrypt(&self, nonce: &Nonce, ciphertext: &[u8]) -> Result<Vec<u8>, zx::Status> {
45        self.aes_gcm_siv.decrypt(nonce, ciphertext).map_err(|_e| zx::Status::INTERNAL)
46    }
47}
48
49struct CryptBaseInner {
50    ciphers: HashMap<WrappingKeyId, Cipher>,
51    active_data_key: Option<WrappingKeyId>,
52    active_metadata_key: Option<WrappingKeyId>,
53}
54
55/// `CryptBase` is a helper for managing wrapping keys and performing cryptographic operations.
56pub struct CryptBase {
57    inner: Mutex<CryptBaseInner>,
58    shutdown: AtomicBool,
59    /// Legacy fscrypt uses the filesystem UUID to salt encryption keys in some variants.
60    /// We don't have direct access to the filesystem so we store the UUID here.
61    filesystem_uuid: [u8; 16],
62}
63
64impl CryptBase {
65    pub fn new() -> Self {
66        Self {
67            inner: Mutex::new(CryptBaseInner {
68                ciphers: HashMap::new(),
69                active_data_key: None,
70                active_metadata_key: None,
71            }),
72            shutdown: AtomicBool::new(false),
73            filesystem_uuid: [0; 16],
74        }
75    }
76
77    pub fn add_wrapping_key(&self, id: WrappingKeyId, key: [u8; 32]) -> Result<(), zx::Status> {
78        let mut inner = self.inner.lock();
79        match inner.ciphers.entry(id) {
80            Entry::Occupied(_) => Err(zx::Status::ALREADY_EXISTS),
81            Entry::Vacant(v) => {
82                v.insert(Cipher::new(key));
83                Ok(())
84            }
85        }
86    }
87
88    pub fn set_active_key(&self, purpose: KeyPurpose, id: WrappingKeyId) -> Result<(), zx::Status> {
89        let mut inner = self.inner.lock();
90        if !inner.ciphers.contains_key(&id) {
91            return Err(zx::Status::NOT_FOUND);
92        }
93        match purpose {
94            KeyPurpose::Data => inner.active_data_key = Some(id),
95            KeyPurpose::Metadata => inner.active_metadata_key = Some(id),
96        }
97        Ok(())
98    }
99
100    pub fn forget_wrapping_key(&self, id: &WrappingKeyId) -> Result<(), zx::Status> {
101        let mut inner = self.inner.lock();
102        if let Some(active_id) = inner.active_data_key {
103            if active_id == *id {
104                return Err(zx::Status::INVALID_ARGS);
105            }
106        }
107        if let Some(active_id) = inner.active_metadata_key {
108            if active_id == *id {
109                return Err(zx::Status::INVALID_ARGS);
110            }
111        }
112        inner.ciphers.remove(id);
113        Ok(())
114    }
115
116    pub fn shutdown(&self) {
117        self.shutdown.store(true, Ordering::Relaxed);
118    }
119
120    /// Fscrypt in INO_LBLK32 and INO_LBLK64 modes mix the filesystem_uuid into key derivation
121    /// functions. Crypt should be told the uuid ahead of time to support decryption of migrated
122    /// data. (Note that we make an assumption that there is only one filesystem.)
123    pub fn set_filesystem_uuid(&mut self, uuid: &[u8; 16]) {
124        self.filesystem_uuid = *uuid;
125    }
126}
127
128#[async_trait]
129impl Crypt for CryptBase {
130    async fn create_key(
131        &self,
132        owner: u64,
133        purpose: KeyPurpose,
134    ) -> Result<(fxfs_crypto::FxfsKey, UnwrappedKey), zx::Status> {
135        if self.shutdown.load(Ordering::Relaxed) {
136            return Err(zx::Status::INTERNAL);
137        }
138        let inner = self.inner.lock();
139        let wrapping_key_id = match purpose {
140            KeyPurpose::Data => inner.active_data_key,
141            KeyPurpose::Metadata => inner.active_metadata_key,
142        }
143        .ok_or(zx::Status::INVALID_ARGS)?;
144
145        let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
146
147        let nonce = zero_extended_nonce(owner);
148
149        let mut uwnrapped_key = [0u8; 32];
150        StdRng::from_os_rng().fill_bytes(&mut uwnrapped_key);
151
152        let wrapped_key = cipher.encrypt(&nonce, &uwnrapped_key[..])?;
153        Ok((
154            fxfs_crypto::FxfsKey {
155                wrapping_key_id,
156                key: wrapped_key.try_into().map_err(|_| zx::Status::INTERNAL)?,
157            },
158            UnwrappedKey::new(uwnrapped_key.to_vec()),
159        ))
160    }
161
162    async fn create_key_with_id(
163        &self,
164        owner: u64,
165        wrapping_key_id: WrappingKeyId,
166        object_type: ObjectType,
167    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status> {
168        if self.shutdown.load(Ordering::Relaxed) {
169            return Err(zx::Status::INTERNAL);
170        }
171
172        match object_type {
173            ObjectType::Directory | ObjectType::Symlink => {
174                let mut nonce = [0; 16];
175                StdRng::from_os_rng().fill_bytes(&mut nonce);
176                let inner = self.inner.lock();
177                let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
178                let mut unwrapped_key = [0u8; 96];
179                fscrypt::hkdf::hkdf(&cipher.wrapping_key, &nonce, &mut unwrapped_key);
180                Ok((
181                    EncryptionKey::FscryptInoLblk32Dir {
182                        key_identifier: wrapping_key_id,
183                        nonce: nonce.try_into().map_err(|_| zx::Status::INTERNAL)?,
184                    },
185                    UnwrappedKey::new(unwrapped_key.to_vec()),
186                ))
187            }
188            _ => {
189                let inner = self.inner.lock();
190                let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
191                let nonce = zero_extended_nonce(owner);
192                let mut unwrapped_key = [0u8; 32];
193                StdRng::from_os_rng().fill_bytes(&mut unwrapped_key);
194                let wrapped = cipher.encrypt(&nonce, &unwrapped_key[..])?;
195                Ok((
196                    EncryptionKey::Fxfs(fxfs_crypto::FxfsKey {
197                        wrapping_key_id,
198                        key: wrapped.try_into().map_err(|_| zx::Status::INTERNAL)?,
199                    }),
200                    UnwrappedKey::new(unwrapped_key.to_vec()),
201                ))
202            }
203        }
204    }
205
206    async fn unwrap_key(
207        &self,
208        wrapped_key: &WrappedKey,
209        owner: u64,
210    ) -> Result<UnwrappedKey, zx::Status> {
211        if self.shutdown.load(Ordering::Relaxed) {
212            return Err(zx::Status::INTERNAL);
213        }
214
215        match wrapped_key {
216            WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
217                key_identifier,
218                nonce,
219            }) => {
220                let inner = self.inner.lock();
221                let cipher = inner.ciphers.get(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
222                let mut unwrapped_key = [0u8; 96];
223                fscrypt::hkdf::hkdf(&cipher.wrapping_key, nonce, &mut unwrapped_key);
224                Ok(UnwrappedKey::new(unwrapped_key.to_vec()))
225            }
226            WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey { wrapping_key_id, wrapped_key }) => {
227                let inner = self.inner.lock();
228                let cipher = inner.ciphers.get(wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
229                let mut nonce = Nonce::default();
230                nonce.as_mut_slice()[..8].copy_from_slice(&owner.to_le_bytes());
231                Ok(UnwrappedKey::new(cipher.decrypt(&nonce, wrapped_key)?))
232            }
233            _ => Err(zx::Status::NOT_SUPPORTED),
234        }
235    }
236}
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[fuchsia::test]
242    async fn test_wrap_unwrap() {
243        let crypt = CryptBase::new();
244        let key = [0xABu8; 32];
245        let id = [1u8; 16];
246        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
247        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
248
249        let (fxfs_key, unwrapped_key) =
250            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
251        assert_eq!(fxfs_key.wrapping_key_id, id);
252        assert_eq!(unwrapped_key.len(), 32);
253
254        let unwrapped_back = crypt
255            .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 0)
256            .await
257            .expect("unwrap_key failed");
258        assert_eq!(*unwrapped_key, *unwrapped_back);
259    }
260
261    #[fuchsia::test]
262    async fn test_forget_wrapping_key() {
263        let crypt = CryptBase::new();
264        let key = [0xABu8; 32];
265        let id = [1u8; 16];
266        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
267        assert_eq!(crypt.add_wrapping_key(id, key), Err(zx::Status::ALREADY_EXISTS));
268        crypt.forget_wrapping_key(&id).unwrap();
269        assert_eq!(
270            crypt
271                .unwrap_key(
272                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
273                        wrapping_key_id: id,
274                        wrapped_key: [0u8; 48]
275                    }),
276                    0
277                )
278                .await
279                .expect_err("unwrap_key should fail when wrapping key is forgotten"),
280            zx::Status::UNAVAILABLE
281        );
282        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
283    }
284
285    #[fuchsia::test]
286    async fn test_active_key_management() {
287        let crypt = CryptBase::new();
288        let key = [0xABu8; 32];
289        let id1 = [0u8; 16];
290        let id2 = [1u8; 16];
291        crypt.add_wrapping_key(id1, key).expect("add_wrapping_key failed");
292        crypt.add_wrapping_key(id2, key).expect("add_wrapping_key failed");
293
294        crypt.set_active_key(KeyPurpose::Data, id1).expect("set_active_key failed");
295        crypt.set_active_key(KeyPurpose::Metadata, id2).expect("set_active_key failed");
296
297        assert_eq!(crypt.forget_wrapping_key(&id1), Err(zx::Status::INVALID_ARGS));
298        assert_eq!(crypt.forget_wrapping_key(&id2), Err(zx::Status::INVALID_ARGS));
299    }
300
301    #[fuchsia::test]
302    async fn test_shutdown() {
303        let crypt = CryptBase::new();
304        let key = [0xABu8; 32];
305        let id = [1u8; 16];
306        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
307        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
308
309        crypt.shutdown();
310
311        assert_eq!(
312            crypt
313                .create_key(0, KeyPurpose::Data)
314                .await
315                .expect_err("create_key should fail when crypt has shut down"),
316            zx::Status::INTERNAL
317        );
318        assert_eq!(
319            crypt
320                .create_key_with_id(0, id, ObjectType::File)
321                .await
322                .expect_err("create_key_with_id should fail when crypt has shut down"),
323            zx::Status::INTERNAL
324        );
325        assert_eq!(
326            crypt
327                .unwrap_key(
328                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
329                        wrapping_key_id: id,
330                        wrapped_key: [0u8; 48]
331                    }),
332                    0,
333                )
334                .await
335                .expect_err("unwrap_key should fail when crypt has shut down"),
336            zx::Status::INTERNAL
337        );
338    }
339
340    #[fuchsia::test]
341    async fn test_create_key_no_active_key() {
342        let crypt = CryptBase::new();
343        assert_eq!(
344            crypt
345                .create_key(0, KeyPurpose::Data)
346                .await
347                .expect_err("create_key should fail when no active key is set"),
348            zx::Status::INVALID_ARGS
349        );
350    }
351
352    #[fuchsia::test]
353    async fn test_create_key_with_id_not_found() {
354        let crypt = CryptBase::new();
355        let id = [1u8; 16];
356        assert_eq!(
357            crypt.create_key_with_id(0, id, ObjectType::File).await.expect_err(
358                "create_key_with_id should fail when no active key is set at wrapping key id"
359            ),
360            zx::Status::UNAVAILABLE
361        );
362    }
363
364    #[fuchsia::test]
365    async fn test_unwrap_key_not_found() {
366        let crypt = CryptBase::new();
367        let id = [1u8; 16];
368        assert_eq!(
369            crypt
370                .unwrap_key(
371                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
372                        wrapping_key_id: id,
373                        wrapped_key: [0u8; 48]
374                    }),
375                    0,
376                )
377                .await
378                .expect_err("unwrap_key should fail when no active key is set at wrapping key id"),
379            zx::Status::UNAVAILABLE
380        );
381    }
382
383    #[fuchsia::test]
384    async fn test_unwrap_key_wrong_owner() {
385        let crypt = CryptBase::new();
386        let key = [0xABu8; 32];
387        let id = [0u8; 16];
388        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
389        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
390
391        let (fxfs_key, _unwrapped_key) =
392            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
393        // Try to unwrap with wrong owner (1 instead of 0)
394        assert_eq!(
395            crypt
396                .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1)
397                .await
398                .expect_err("unwrap_key should fail when owner does not match"),
399            zx::Status::INTERNAL
400        );
401    }
402
403    #[fuchsia::test]
404    async fn test_wrap_unwrap_key_with_arbitrary_wrapping_key_id() {
405        let crypt = CryptBase::new();
406        let key = [0xABu8; 32];
407        let id = [2u8; 16];
408        crypt.add_wrapping_key(id, key).expect("add_key failed");
409
410        let (wrapped_key, unwrapped_key) = crypt
411            .create_key_with_id(0, id, ObjectType::File)
412            .await
413            .expect("create_key_with_id failed");
414        let unwrap_result =
415            crypt.unwrap_key(&WrappedKey::from(wrapped_key), 0).await.expect("unwrap_key failed");
416        assert_eq!(*unwrap_result, *unwrapped_key);
417
418        // Do it twice to make sure the service can use the same key repeatedly.
419        let (wrapped_key, unwrapped_key) = crypt
420            .create_key_with_id(1, id, ObjectType::File)
421            .await
422            .expect("create_key_with_id failed");
423        let unwrap_result =
424            crypt.unwrap_key(&WrappedKey::from(wrapped_key), 1).await.expect("unwrap_key failed");
425        assert_eq!(*unwrap_result, *unwrapped_key);
426    }
427
428    #[fuchsia::test]
429    async fn test_unwrap_key_wrong_key() {
430        let crypt = CryptBase::new();
431        let key = [0xABu8; 32];
432        let id = [0u8; 16];
433        crypt.add_wrapping_key(id, key).expect("add_key failed");
434        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
435
436        let (fxfs_key, _unwrapped_key) =
437            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
438        let mut modified_wrapped_key = fxfs_key.key.to_vec();
439        for byte in &mut modified_wrapped_key {
440            *byte ^= 0xff;
441        }
442        assert_eq!(
443            crypt
444                .unwrap_key(
445                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
446                        wrapping_key_id: fxfs_key.wrapping_key_id,
447                        wrapped_key: modified_wrapped_key.clone().try_into().unwrap(),
448                    }),
449                    0,
450                )
451                .await
452                .is_err(),
453            true
454        );
455    }
456}