Skip to main content

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