Skip to main content

starnix_crypt/
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 anyhow::{Error, bail};
8use async_trait::async_trait;
9use fidl_fuchsia_fxfs::{
10    self as ffxfs, CryptRequest, CryptRequestStream, FscryptKeyIdentifier,
11    FscryptKeyIdentifierAndNonce, ObjectType, WrappedKey,
12};
13use fidl_fuchsia_hardware_inlineencryption::DeviceSynchronousProxy;
14use fscrypt::hkdf::{HKDF_CONTEXT_INODE_HASH_KEY, HKDF_CONTEXT_KEY_IDENTIFIER, fscrypt_hkdf};
15use fuchsia_sync::Mutex;
16use futures::stream::StreamExt;
17use fxfs_crypto::{Crypt, EncryptionKey, FxfsKey, KeyPurpose, UnwrappedKey, WrappingKeyId};
18use hkdf::Hkdf;
19use linux_uapi::FSCRYPT_KEY_IDENTIFIER_SIZE;
20use starnix_uapi::errors::{Errno, errno, error, from_status_like_fdio};
21use std::collections::hash_map::{Entry, HashMap};
22use std::sync::OnceLock;
23
24// In this implementation of fscrypt, we use a HKDF (Hmac Key Derivation Function) to derive a
25// a wrapping key and wrapping key id from the raw key bytes passed in by a user on
26// FS_IOC_ADD_ENCRYPTION_KEY. HKDFs requires an input "info" string. We define constants for the
27// respective "info" strings here.
28const FXFS_FSCRYPT_WRAPPING_KEY_INFO: &str = "fscrypt1";
29const FSCRYPT_HKDF_NONCE_PREFIX: &[u8] = b"fscrypt\0";
30
31const DATA_UNIT_SIZE: u32 = 4096;
32const AES256_KEY_SIZE: usize = 32;
33
34/// An fscrypt wrapping key id.
35pub type EncryptionKeyId = [u8; FSCRYPT_KEY_IDENTIFIER_SIZE as usize];
36
37/// Policy mode derived from `fscrypt_policy_v2` flags on `FS_IOC_SET_ENCRYPTION_POLICY`.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub enum FscryptMode {
40    /// Default v2 policy (`flags` has neither `IV_INO_LBLK_64` nor `IV_INO_LBLK_32`):
41    /// uses per-file wrapped keys (`WrappedKey::Fxfs` / `FxfsCipher`) for files.
42    #[default]
43    Standard,
44    /// `FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64`: shared hardware keyslot with 64-bit
45    /// `(ino << 32) | lblk_num` DUNs and `HKDF_CONTEXT_IV_INO_LBLK_64_KEY` (4).
46    InoLblk64,
47    /// `FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32`: shared hardware keyslot with 32-bit
48    /// `(SipHash(ino) + lblk_num) as u32` DUNs and `HKDF_CONTEXT_IV_INO_LBLK_32_KEY` (6).
49    InoLblk32,
50}
51
52impl FscryptMode {
53    pub fn from_flags(flags: fidl_fuchsia_io::FscryptPolicyFlags) -> Self {
54        if flags.contains(fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_64) {
55            Self::InoLblk64
56        } else if flags.contains(fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32) {
57            Self::InoLblk32
58        } else {
59            Self::Standard
60        }
61    }
62}
63
64struct KeyInfo {
65    users: Vec<u32>,
66    key: Box<[u8]>,
67    key_token: Option<zx::EventPair>,
68}
69
70struct CryptServiceInner {
71    keys: HashMap<EncryptionKeyId, KeyInfo>,
72    metadata_key: Option<EncryptionKeyId>,
73    data_key: Option<EncryptionKeyId>,
74}
75
76pub struct CryptService {
77    inner: Mutex<CryptServiceInner>,
78    inline_crypto_proxy: Option<DeviceSynchronousProxy>,
79    uuid: OnceLock<[u8; 16]>,
80}
81
82impl CryptService {
83    /// Creates a new crypt service that supports Starnix user volumes.
84    pub fn new(
85        raw_metadata_key: &[u8],
86        raw_data_key: &[u8],
87        inline_crypto_proxy: Option<DeviceSynchronousProxy>,
88    ) -> Self {
89        let metadata_wrapping_key_id = derive_lblk32_wrapping_key_id(raw_metadata_key);
90        let data_wrapping_key_id = derive_lblk32_wrapping_key_id(raw_data_key);
91
92        fn to_key_info(key: &[u8]) -> KeyInfo {
93            KeyInfo { users: Vec::new(), key: key.into(), key_token: None }
94        }
95
96        Self {
97            inner: Mutex::new(CryptServiceInner {
98                keys: HashMap::from_iter([
99                    (metadata_wrapping_key_id, to_key_info(raw_metadata_key)),
100                    (data_wrapping_key_id, to_key_info(raw_data_key)),
101                ]),
102                metadata_key: Some(metadata_wrapping_key_id),
103                data_key: Some(data_wrapping_key_id),
104            }),
105            inline_crypto_proxy,
106            uuid: OnceLock::new(),
107        }
108    }
109
110    /// Returns true if `key` is registered with the service.
111    pub fn contains_key(&self, key: EncryptionKeyId) -> bool {
112        let inner = self.inner.lock();
113        inner.keys.contains_key(&key)
114    }
115
116    /// Returns the users registered for `key`.
117    pub fn get_users_for_key(&self, key: EncryptionKeyId) -> Option<Vec<u32>> {
118        let inner = self.inner.lock();
119        inner.keys.get(&key).map(|x| x.users.clone())
120    }
121
122    /// Adds the specified wrapping key for user `uid`.
123    pub fn add_wrapping_key(&self, raw_key: &[u8], uid: u32) -> Result<EncryptionKeyId, Errno> {
124        let (key, key_token) = if let Some(proxy) = self.inline_crypto_proxy.as_ref() {
125            let key = Box::from(
126                proxy
127                    .derive_raw_secret(raw_key, zx::MonotonicInstant::INFINITE)
128                    .map_err(|error| {
129                        log::error!(error:?; "derive_raw_secret FIDL error");
130                        errno!(EPIPE)
131                    })?
132                    .map_err(|status| {
133                        let status = zx::Status::err_from_raw(status);
134                        log::error!(status:?; "derive_raw_secret failed");
135                        from_status_like_fdio!(status)
136                    })?,
137            );
138            let key_token = proxy
139                .program_key(raw_key, DATA_UNIT_SIZE, zx::MonotonicInstant::INFINITE)
140                .map_err(|error| {
141                    log::error!(error:?; "program_key FIDL error");
142                    errno!(EPIPE)
143                })?
144                .map_err(|status| {
145                    let status = zx::Status::err_from_raw(status);
146                    log::error!(status:?; "program_key failed");
147                    from_status_like_fdio!(status)
148                })?;
149            (key, Some(key_token))
150        } else {
151            (Box::from(raw_key), None)
152        };
153        let key_identifier = derive_lblk32_wrapping_key_id(&key);
154        let mut inner = self.inner.lock();
155        match inner.keys.entry(key_identifier) {
156            Entry::Occupied(mut e) => {
157                let users = &mut e.get_mut().users;
158                if !users.contains(&uid) {
159                    users.push(uid);
160                }
161                Ok(key_identifier)
162            }
163            Entry::Vacant(vacant) => {
164                vacant.insert(KeyInfo { users: vec![uid], key, key_token });
165                Ok(key_identifier)
166            }
167        }
168    }
169
170    /// Serves crypt requests.
171    pub async fn handle_connection(&self, mut stream: CryptRequestStream) -> Result<(), Error> {
172        while let Some(request) = stream.next().await {
173            match request {
174                Ok(CryptRequest::CreateKey { owner, purpose, responder }) => {
175                    match self.create_key(owner, purpose) {
176                        Ok((key, unwrapped_key)) => responder.send(Ok((
177                            &key.wrapping_key_id,
178                            &key.key[..],
179                            &unwrapped_key[..],
180                        ))),
181                        Err(e) => responder.send(Err(e.into_raw())),
182                    }
183                    .unwrap_or_else(
184                        |error| log::error!(error:?; "Failed to send CreateKey response"),
185                    );
186                }
187                Ok(CryptRequest::CreateKeyWithId {
188                    owner,
189                    wrapping_key_id,
190                    object_type,
191                    flags,
192                    responder,
193                }) => {
194                    match self.create_key_with_id(owner, wrapping_key_id, object_type, flags) {
195                        Ok((key, unwrapped_key, key_token)) => {
196                            responder.send(Ok((&key.into(), &unwrapped_key[..], key_token)))
197                        }
198                        Err(e) => responder.send(Err(e.into_raw())),
199                    }
200                    .unwrap_or_else(
201                        |error| log::error!(error:?; "Failed to send CreateKeyWithId response"),
202                    );
203                }
204                Ok(CryptRequest::UnwrapKey { owner, wrapped_key, responder }) => {
205                    match self.unwrap_key(&wrapped_key, owner) {
206                        Ok((unwrapped_key, key_token)) => {
207                            responder.send(Ok((&unwrapped_key[..], key_token)))
208                        }
209                        Err(e) => responder.send(Err(e.into_raw())),
210                    }
211                    .unwrap_or_else(
212                        |error| log::error!(error:?; "Failed to send UnwrapKey response"),
213                    );
214                }
215                Err(error) => {
216                    log::error!(error:?; "Error in CryptRequestStream");
217                    bail!(error);
218                }
219            }
220        }
221        Ok(())
222    }
223
224    /// Removes `wrapping_key_id` for user `uid`.
225    pub fn forget_wrapping_key(
226        &self,
227        wrapping_key_id: EncryptionKeyId,
228        uid: u32,
229    ) -> Result<(), Errno> {
230        let mut inner = self.inner.lock();
231        match inner.keys.entry(EncryptionKeyId::from(wrapping_key_id)) {
232            Entry::Occupied(mut e) => {
233                let user_ids = &mut e.get_mut().users;
234                if !user_ids.contains(&uid) {
235                    return error!(ENOKEY);
236                } else {
237                    let index = user_ids.iter().position(|x: &u32| *x == uid).unwrap();
238                    user_ids.remove(index);
239                    if user_ids.is_empty() {
240                        e.remove();
241                    }
242                }
243            }
244            Entry::Vacant(_) => {
245                return error!(ENOKEY);
246            }
247        }
248        Ok(())
249    }
250
251    pub fn set_uuid(&self, uuid: [u8; 16]) {
252        self.uuid.set(uuid).unwrap();
253    }
254
255    fn derive_directory_key(
256        &self,
257        key: &[u8],
258        nonce: &[u8],
259        mode: FscryptMode,
260    ) -> Result<Vec<u8>, zx::Status> {
261        let uuid = self.uuid.get().ok_or(zx::Status::BAD_STATE)?;
262        Ok(match mode {
263            FscryptMode::InoLblk64 => {
264                fscrypt::to_directory_keys_lblk64(key, uuid, nonce).to_unwrapped_key()
265            }
266            FscryptMode::InoLblk32 | FscryptMode::Standard => {
267                fscrypt::to_directory_keys(key, uuid, nonce).to_unwrapped_key()
268            }
269        })
270    }
271
272    fn create_key(
273        &self,
274        owner: u64,
275        purpose: ffxfs::KeyPurpose,
276    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status> {
277        let inner = self.inner.lock();
278        let wrapping_key_id = match purpose {
279            ffxfs::KeyPurpose::Data => inner.data_key.as_ref().ok_or_else(|| {
280                log::error!(
281                    "tried to create key with KeyPurpose::Data but no active data wrapping key"
282                );
283                zx::Status::BAD_STATE
284            })?,
285            ffxfs::KeyPurpose::Metadata => inner.metadata_key.as_ref().ok_or_else(|| {
286                log::error!(
287                    "tried to create key with KeyPurpose::Metadata but no active data wrapping key"
288                );
289                zx::Status::BAD_STATE
290            })?,
291            _ => return Err(zx::Status::INVALID_ARGS),
292        };
293        let key = &inner.keys.get(wrapping_key_id).ok_or(zx::Status::BAD_STATE)?.key;
294        let cipher = get_fxfs_cipher(key);
295        let nonce = zero_extended_nonce(owner);
296
297        let mut key = [0u8; 32];
298        rand::fill(&mut key[..]);
299
300        let wrapped = cipher.encrypt(&nonce, &key[..]).map_err(|error| {
301            log::error!(error:?; "Failed to wrap key");
302            zx::Status::INTERNAL
303        })?;
304        debug_assert!(
305            wrapped.len() == fxfs_crypto::FXFS_WRAPPED_KEY_SIZE,
306            "wrapped key is the wrong length"
307        );
308
309        Ok((
310            FxfsKey {
311                wrapping_key_id: *wrapping_key_id,
312                // Unwrap safety: we debug asserted the size is correct above.
313                key: wrapped.try_into().unwrap(),
314            },
315            UnwrappedKey::new(key.into()),
316        ))
317    }
318
319    fn create_key_with_id(
320        &self,
321        owner: u64,
322        wrapping_key_id: WrappingKeyId,
323        object_type: ObjectType,
324        flags: fidl_fuchsia_io::FscryptPolicyFlags,
325    ) -> Result<(EncryptionKey, UnwrappedKey, Option<zx::EventPair>), zx::Status> {
326        let mut inner = self.inner.lock();
327        let key_info = inner.keys.get_mut(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
328        let mode = FscryptMode::from_flags(flags);
329        match object_type {
330            ObjectType::Directory | ObjectType::Symlink => {
331                let mut nonce = [0; 16];
332                zx::cprng_draw(&mut nonce);
333                let unwrapped_key = self.derive_directory_key(&key_info.key, &nonce, mode)?;
334                let encryption_key = match mode {
335                    FscryptMode::InoLblk64 => EncryptionKey::FscryptInoLblk64Dir {
336                        key_identifier: wrapping_key_id,
337                        nonce,
338                    },
339                    FscryptMode::InoLblk32 | FscryptMode::Standard => {
340                        EncryptionKey::FscryptInoLblk32Dir {
341                            key_identifier: wrapping_key_id,
342                            nonce,
343                        }
344                    }
345                };
346                Ok((encryption_key, UnwrappedKey::new(unwrapped_key), None))
347            }
348            ObjectType::File => {
349                // Only use shared-key inline encryption when an `IV_INO_LBLK_*` policy mode is
350                // active AND a hardware keyslot is programmed. Default (`FscryptMode::Standard`)
351                // policies use per-file wrapped keys (`WrappedKey::Fxfs`), avoiding cross-file
352                // key+DUN reuse.
353                if let (Some(key_token), FscryptMode::InoLblk32 | FscryptMode::InoLblk64) =
354                    (&key_info.key_token, mode)
355                {
356                    let dup_token = key_token.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
357                    let unwrapped_key = derive_file_key(&key_info.key, mode);
358                    let encryption_key = match mode {
359                        FscryptMode::InoLblk64 => {
360                            EncryptionKey::FscryptInoLblk64File { key_identifier: wrapping_key_id }
361                        }
362                        FscryptMode::InoLblk32 => {
363                            EncryptionKey::FscryptInoLblk32File { key_identifier: wrapping_key_id }
364                        }
365                        FscryptMode::Standard => unreachable!(),
366                    };
367                    Ok((encryption_key, UnwrappedKey::new(unwrapped_key), Some(dup_token)))
368                } else {
369                    // Use a per-file wrapped key (`FxfsCipher`).
370                    let cipher = get_fxfs_cipher(&key_info.key);
371                    let nonce = zero_extended_nonce(owner);
372
373                    let mut key = [0u8; 32];
374                    rand::fill(&mut key[..]);
375
376                    let wrapped = cipher.encrypt(&nonce, &key[..]).map_err(|error| {
377                        log::error!(error:?; "Failed to wrap key");
378                        zx::Status::INTERNAL
379                    })?;
380                    debug_assert!(
381                        wrapped.len() == fxfs_crypto::FXFS_WRAPPED_KEY_SIZE,
382                        "wrapped key is the wrong length"
383                    );
384
385                    Ok((
386                        EncryptionKey::Fxfs(FxfsKey {
387                            wrapping_key_id,
388                            // Unwrap safety: we debug asserted the size is correct above.
389                            key: wrapped.try_into().unwrap(),
390                        }),
391                        UnwrappedKey::new(key.into()),
392                        None,
393                    ))
394                }
395            }
396            _ => Err(zx::Status::NOT_SUPPORTED),
397        }
398    }
399
400    fn unwrap_key(
401        &self,
402        wrapped_key: &WrappedKey,
403        owner: u64,
404    ) -> Result<(UnwrappedKey, Option<zx::EventPair>), zx::Status> {
405        let mut inner = self.inner.lock();
406        match wrapped_key {
407            WrappedKey::Fxfs(ffxfs::FxfsKey { wrapping_key_id, wrapped_key }) => {
408                let key_info = inner.keys.get(wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
409                let cipher = get_fxfs_cipher(&key_info.key);
410                let nonce = zero_extended_nonce(owner);
411
412                Ok((
413                    UnwrappedKey::new(
414                        cipher
415                            .decrypt(&nonce, &wrapped_key[..])
416                            .map_err(|_| zx::Status::IO_DATA_INTEGRITY)?,
417                    ),
418                    None,
419                ))
420            }
421            WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier }) => {
422                let key_info = inner.keys.get_mut(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
423                let Some(key_token) = &key_info.key_token else {
424                    // The caller is trying to use a hardware wrapped key, but we
425                    // don't have access to the wrapping hardware.
426                    return Err(zx::Status::UNAVAILABLE);
427                };
428                let dup_token = key_token.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
429                Ok((
430                    UnwrappedKey::new(derive_file_key(&key_info.key, FscryptMode::InoLblk32)),
431                    Some(dup_token),
432                ))
433            }
434            WrappedKey::FscryptInoLblk64File(FscryptKeyIdentifier { key_identifier }) => {
435                let key_info = inner.keys.get_mut(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
436                let Some(key_token) = &key_info.key_token else {
437                    return Err(zx::Status::UNAVAILABLE);
438                };
439                let dup_token = key_token.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
440                Ok((
441                    UnwrappedKey::new(derive_file_key(&key_info.key, FscryptMode::InoLblk64)),
442                    Some(dup_token),
443                ))
444            }
445            WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
446                key_identifier,
447                nonce,
448            }) => {
449                let key_info = inner.keys.get(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
450
451                Ok((
452                    UnwrappedKey::new(self.derive_directory_key(
453                        &key_info.key,
454                        nonce,
455                        FscryptMode::InoLblk32,
456                    )?),
457                    None,
458                ))
459            }
460            WrappedKey::FscryptInoLblk64Dir(FscryptKeyIdentifierAndNonce {
461                key_identifier,
462                nonce,
463            }) => {
464                let key_info = inner.keys.get(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
465
466                Ok((
467                    UnwrappedKey::new(self.derive_directory_key(
468                        &key_info.key,
469                        nonce,
470                        FscryptMode::InoLblk64,
471                    )?),
472                    None,
473                ))
474            }
475            _ => Err(zx::Status::NOT_SUPPORTED),
476        }
477    }
478}
479
480#[async_trait]
481impl Crypt for CryptService {
482    async fn create_key(
483        &self,
484        owner: u64,
485        purpose: KeyPurpose,
486    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status> {
487        self.create_key(owner, purpose.into())
488    }
489
490    async fn create_key_with_id(
491        &self,
492        owner: u64,
493        wrapping_key_id: WrappingKeyId,
494        object_type: ObjectType,
495        flags: fidl_fuchsia_io::FscryptPolicyFlags,
496    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status> {
497        let (key, unwrapped_key, _key_token) =
498            self.create_key_with_id(owner, wrapping_key_id, object_type, flags)?;
499        Ok((key, unwrapped_key))
500    }
501
502    async fn unwrap_key(
503        &self,
504        wrapped_key: &WrappedKey,
505        owner: u64,
506    ) -> Result<UnwrappedKey, zx::Status> {
507        let (unwrapped_key, _key_token) = self.unwrap_key(wrapped_key, owner)?;
508        Ok(unwrapped_key)
509    }
510}
511
512fn zero_extended_nonce(val: u64) -> Nonce {
513    let mut nonce = Nonce::default();
514    nonce.as_mut_slice()[..8].copy_from_slice(&val.to_le_bytes());
515    nonce
516}
517
518fn derive_file_key(key: &[u8], mode: FscryptMode) -> Vec<u8> {
519    match mode {
520        FscryptMode::InoLblk32 | FscryptMode::Standard => {
521            let ino_hash_key: [u8; 16] = fscrypt_hkdf(key, &[], HKDF_CONTEXT_INODE_HASH_KEY);
522            ino_hash_key.to_vec()
523        }
524        FscryptMode::InoLblk64 => Vec::new(),
525    }
526}
527
528fn get_fxfs_cipher(raw_key: &[u8]) -> Aes256GcmSiv {
529    let hk = Hkdf::<sha2::Sha256>::new(None, raw_key);
530    let mut wrapping_key = [0u8; AES256_KEY_SIZE];
531    hk.expand(FXFS_FSCRYPT_WRAPPING_KEY_INFO.as_bytes(), &mut wrapping_key).unwrap();
532    Aes256GcmSiv::new((&wrapping_key).into())
533}
534
535fn derive_lblk32_wrapping_key_id(raw_key: &[u8]) -> [u8; FSCRYPT_KEY_IDENTIFIER_SIZE as usize] {
536    let hk = Hkdf::<sha2::Sha512>::new(None, raw_key);
537    let mut key_identifier = [0u8; FSCRYPT_KEY_IDENTIFIER_SIZE as usize];
538    let mut hkdf_info = FSCRYPT_HKDF_NONCE_PREFIX.to_vec();
539    hkdf_info.push(HKDF_CONTEXT_KEY_IDENTIFIER);
540    hk.expand(&hkdf_info, &mut key_identifier).unwrap();
541    key_identifier
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use assert_matches::assert_matches;
548    use block_client::RemoteBlockClient;
549    use fidl_fuchsia_fxfs::WrappedKey;
550    use fidl_fuchsia_hardware_inlineencryption::{DeviceMarker, DeviceRequest};
551    use fidl_fuchsia_storage_block::BlockProxy;
552    use fxfs_crypto::FindKeyResult;
553
554    use fuchsia_async::LocalExecutor;
555    use starnix_uapi::errno;
556    use std::sync::Arc;
557    use storage_device::block_device::BlockDevice;
558    use storage_device::{Device, InlineCryptoOptions, ReadOptions, WriteOptions};
559    use test_vmo_backed_block_server::VmoBackedServer;
560
561    const BLOCK_SIZE: u32 = 4096;
562    const BLOCK_COUNT: u64 = 393216;
563    const TEST_UUID: [u8; 16] =
564        [75, 146, 230, 48, 132, 165, 68, 97, 141, 247, 22, 242, 153, 171, 153, 38];
565
566    #[test]
567    fn add_and_forget_wrapping_keys() {
568        let service = CryptService::new(&[0; 32], &[1; 32], None);
569
570        // Add the wrapping key for users 0 and 1
571        let wrapping_key_id =
572            service.add_wrapping_key(&u128::to_le_bytes(1), 0).expect("add wrapping key failed");
573
574        let wrapping_key_id2 =
575            service.add_wrapping_key(&u128::to_le_bytes(1), 1).expect("add wrapping key failed");
576
577        assert_eq!(wrapping_key_id2, wrapping_key_id);
578
579        // A user should be able to add the same key multiple times.
580        let wrapping_key_id3 =
581            service.add_wrapping_key(&u128::to_le_bytes(1), 1).expect("add wrapping key failed");
582
583        assert_eq!(wrapping_key_id3, wrapping_key_id);
584
585        {
586            let inner = service.inner.lock();
587            assert_eq!(inner.keys.get(&wrapping_key_id).unwrap().users, [0, 1]);
588        }
589
590        // User 1 forgets the wrapping key. Since user 0 still has the key added,
591        // create_key_with_id should still succeed.
592        service.forget_wrapping_key(wrapping_key_id, 1).expect("forget wrapping key failed");
593        service
594            .create_key_with_id(
595                0,
596                wrapping_key_id,
597                ObjectType::File,
598                fidl_fuchsia_io::FscryptPolicyFlags::empty(),
599            )
600            .expect("create key with id failed");
601
602        // User 1 cannot forget the same key a second time.
603        assert_eq!(
604            service.forget_wrapping_key(wrapping_key_id, 1).expect_err(
605                "forget wrapping key should fail if the key was already removed by this user"
606            ),
607            errno!(ENOKEY)
608        );
609        // Once both users remove the key, create_key_with_id should fail.
610        service.forget_wrapping_key(wrapping_key_id, 0).expect("forget wrapping key failed");
611        assert_eq!(
612            service
613                .create_key_with_id(
614                    0,
615                    EncryptionKeyId::from(u128::to_le_bytes(1)),
616                    ObjectType::File,
617                    fidl_fuchsia_io::FscryptPolicyFlags::empty(),
618                )
619                .expect_err(
620                    "create_key_with_id should fail if the key hasn't been added by the caller"
621                ),
622            zx::Status::UNAVAILABLE
623        );
624        service.add_wrapping_key(&u128::to_le_bytes(1), 0).expect("add wrapping key failed");
625    }
626
627    #[fuchsia::test]
628    async fn test_derive_wrapping_key_id_and_lblk32_derived_keys() {
629        const EXPECTED_WRAPPING_KEY_ID: [u8; 16] =
630            [40, 205, 90, 253, 77, 129, 133, 220, 222, 25, 208, 200, 136, 101, 239, 101];
631        const EXPECTED_CTS_KEY: [u8; 32] = [
632            223, 72, 191, 189, 133, 62, 81, 175, 91, 93, 132, 0, 9, 246, 22, 32, 76, 91, 28, 2, 96,
633            27, 182, 66, 131, 84, 218, 118, 230, 226, 142, 115,
634        ];
635        const EXPECTED_INO_HASH_KEY: [u8; 16] =
636            [241, 22, 180, 110, 76, 135, 84, 48, 206, 33, 210, 253, 11, 10, 230, 122];
637
638        let block_server = Arc::new(
639            VmoBackedServer::new(BLOCK_COUNT, BLOCK_SIZE, &[])
640                .expect("Failed to create VmoBackedServer"),
641        );
642
643        let (insecure_inilne_crypto_proxy, server) =
644            fidl::endpoints::create_sync_proxy::<DeviceMarker>();
645        std::thread::spawn(|| {
646            LocalExecutor::default().run_singlethreaded(async move {
647                block_server.connect_insecure_inline_encryption_server(server, TEST_UUID).await;
648            })
649        });
650
651        let service = CryptService::new(&[0; 32], &[1; 32], Some(insecure_inilne_crypto_proxy));
652        service.set_uuid(TEST_UUID);
653        let wrapping_key_id = service.add_wrapping_key(&[0xdc; 32], 0).unwrap();
654        assert_eq!(wrapping_key_id, EXPECTED_WRAPPING_KEY_ID);
655
656        let (_, unwrapped_key, key_token) = service
657            .create_key_with_id(
658                0,
659                wrapping_key_id,
660                ObjectType::Directory,
661                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
662            )
663            .unwrap();
664        assert!(key_token.is_none());
665        let (cts_key, remainder) = unwrapped_key.split_at(EXPECTED_CTS_KEY.len());
666        let (ino_hash_key, _dir_hash_key) = remainder.split_at(EXPECTED_INO_HASH_KEY.len());
667
668        assert_eq!(cts_key, &EXPECTED_CTS_KEY);
669        assert_eq!(ino_hash_key, &EXPECTED_INO_HASH_KEY);
670    }
671
672    #[fuchsia::test]
673    async fn test_create_key_with_id_with_lblk32_key() {
674        let block_server = Arc::new(
675            VmoBackedServer::new(BLOCK_COUNT, BLOCK_SIZE, &[])
676                .expect("Failed to create VmoBackedServer"),
677        );
678
679        let block_server_clone = block_server.clone();
680        let (insecure_inilne_crypto_proxy, server) =
681            fidl::endpoints::create_sync_proxy::<DeviceMarker>();
682        std::thread::spawn(|| {
683            LocalExecutor::default().run_singlethreaded(async move {
684                block_server_clone
685                    .connect_insecure_inline_encryption_server(server, TEST_UUID)
686                    .await;
687            })
688        });
689
690        let service = CryptService::new(&[0; 32], &[1; 32], Some(insecure_inilne_crypto_proxy));
691        let wrapping_key_id = service.add_wrapping_key(&[0xcd; 32], 0).unwrap();
692
693        let (wrapped_key, unwrapped_key, key_token) = service
694            .create_key_with_id(
695                0,
696                wrapping_key_id,
697                ObjectType::File,
698                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
699            )
700            .expect("create_key failed");
701        assert_matches!(wrapped_key, EncryptionKey::FscryptInoLblk32File { .. });
702        let key_token = key_token.expect("expected key_token");
703
704        let mut key = [0xcd; 32];
705        for b in &mut key {
706            *b = *b >> 4 | *b << 4;
707        }
708        let expected_ino_hash_key: [u8; 16] = fscrypt_hkdf(&key, &[], HKDF_CONTEXT_INODE_HASH_KEY);
709        assert_eq!(unwrapped_key[..16], expected_ino_hash_key);
710        // Validate encrypted reads/writes with the key we just programmed.
711        let device = BlockDevice::new(
712            RemoteBlockClient::new(block_server.clone().connect::<BlockProxy>())
713                .await
714                .expect("Unable to create block client"),
715            false,
716        )
717        .await
718        .unwrap();
719
720        let expected_slot = device.register_key(key_token).await.expect("register_key failed");
721
722        let plaintext: &[u8] = b"This is aligned sensitive data!!";
723        let mut buf = device.allocate_buffer(4096).await;
724        buf.subslice_mut(..plaintext.len()).copy_from_slice(plaintext);
725        device
726            .write_with_opts(
727                0,
728                buf.as_ref(),
729                WriteOptions {
730                    inline_crypto: InlineCryptoOptions::enabled(expected_slot, 0),
731                    ..Default::default()
732                },
733            )
734            .await
735            .expect("failed to write data");
736
737        let mut read_buf = device.allocate_buffer(4096).await;
738
739        // Reading without inline crypto should return garbage.
740        device
741            .read_with_opts(0, read_buf.as_mut(), ReadOptions::default())
742            .await
743            .expect("Read failed");
744        assert_ne!(&read_buf.to_vec()[..plaintext.len()], plaintext);
745
746        // Reading using a different key than the one used for writing should also return garbage.
747        let wrapping_key_id_2 = service.add_wrapping_key(&[0xab; 32], 0).unwrap();
748        let (_, _, key_token_2) = service
749            .create_key_with_id(
750                0,
751                wrapping_key_id_2,
752                ObjectType::File,
753                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
754            )
755            .unwrap();
756        // Before registering key_token_2 with the session, using expected_slot + 1 must fail.
757        device
758            .read_with_opts(
759                0,
760                read_buf.as_mut(),
761                ReadOptions { inline_crypto: InlineCryptoOptions::enabled(expected_slot + 1, 0) },
762            )
763            .await
764            .expect_err("Read passed unexpectedly with unregistered key slot");
765
766        let slot_2 =
767            device.register_key(key_token_2.unwrap()).await.expect("register_key 2 failed");
768        device
769            .read_with_opts(
770                0,
771                read_buf.as_mut(),
772                ReadOptions { inline_crypto: InlineCryptoOptions::enabled(slot_2, 0) },
773            )
774            .await
775            .expect("Read failed");
776        assert_ne!(&read_buf.to_vec()[..plaintext.len()], plaintext);
777
778        // Should not be able to read from an unused key slot.
779        device
780            .read_with_opts(
781                0,
782                read_buf.as_mut(),
783                ReadOptions { inline_crypto: InlineCryptoOptions::enabled(expected_slot + 2, 0) },
784            )
785            .await
786            .expect_err("Read passed unexpectedly with unused key slot");
787
788        // Reading with the correct key should work.
789        device
790            .read_with_opts(
791                0,
792                read_buf.as_mut(),
793                ReadOptions { inline_crypto: InlineCryptoOptions::enabled(expected_slot, 0) },
794            )
795            .await
796            .expect("Read failed");
797        assert_eq!(&read_buf.to_vec()[..plaintext.len()], plaintext);
798    }
799
800    #[fuchsia::test]
801    fn unwrap_fxfs_wrapped_key_with_lblk32_key() {
802        let block_server = Arc::new(
803            VmoBackedServer::new(BLOCK_COUNT, BLOCK_SIZE, &[])
804                .expect("Failed to create VmoBackedServer"),
805        );
806        let (insecure_inilne_crypto_proxy, server) =
807            fidl::endpoints::create_sync_proxy::<DeviceMarker>();
808        std::thread::spawn(|| {
809            LocalExecutor::default().run_singlethreaded(async move {
810                block_server.connect_insecure_inline_encryption_server(server, TEST_UUID).await;
811            })
812        });
813
814        let service = CryptService::new(&[0; 32], &[1; 32], Some(insecure_inilne_crypto_proxy));
815        service.set_uuid(TEST_UUID);
816        let wrapping_key_id =
817            service.add_wrapping_key(&[0xcd; 32], 0).expect("add wrapping key failed");
818        let (wrapped_key, expected_unwrapped_key, _) = service
819            .create_key_with_id(
820                0,
821                wrapping_key_id,
822                ObjectType::Directory,
823                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
824            )
825            .expect("create_key failed");
826        assert_matches!(
827            wrapped_key,
828            EncryptionKey::FscryptInoLblk32Dir {
829                key_identifier,
830                ..
831            } if key_identifier == wrapping_key_id
832        );
833        let (unwrapped_key, _) =
834            service.unwrap_key(&wrapped_key.into(), 0).expect("create_key failed");
835        assert_eq!(*unwrapped_key, *expected_unwrapped_key);
836    }
837
838    #[test]
839    fn wrap_unwrap_key() {
840        let service = CryptService::new(&[0; 32], &[0xcd; 32], None);
841
842        let (fxfs_key, unwrapped_key) =
843            service.create_key(0, ffxfs::KeyPurpose::Data).expect("create_key failed");
844        let (unwrap_result, _) = service
845            .unwrap_key(&WrappedKey::Fxfs(fxfs_key.clone().into()), 0)
846            .expect("unwrap_key failed");
847        assert_eq!(*unwrap_result, *unwrapped_key);
848
849        // Do it twice to make sure the service can use the same key repeatedly.
850        let (fxfs_key, unwrapped_key) =
851            service.create_key(1, ffxfs::KeyPurpose::Data).expect("create_key failed");
852        let (unwrap_result, _) =
853            service.unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1).expect("unwrap_key failed");
854        assert_eq!(*unwrap_result, *unwrapped_key);
855    }
856
857    #[test]
858    fn wrap_unwrap_key_with_arbitrary_wrapping_key() {
859        let service = CryptService::new(&[0; 32], &[1; 32], None);
860
861        let wrapping_key_id =
862            service.add_wrapping_key(&[2; 32], 0).expect("add wrapping key failed");
863
864        let (wrapped_key, unwrapped_key, _) = service
865            .create_key_with_id(
866                0,
867                wrapping_key_id,
868                ObjectType::File,
869                fidl_fuchsia_io::FscryptPolicyFlags::empty(),
870            )
871            .expect("create_key_with_id failed");
872        // TODO(https://fxbug.dev/436902004): Switch to lkb32 wrapped key type.
873        match wrapped_key {
874            EncryptionKey::Fxfs(fxfs_key) => {
875                assert_eq!(fxfs_key.wrapping_key_id, wrapping_key_id);
876                let (unwrap_result, _) = service
877                    .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 0)
878                    .expect("unwrap_key failed");
879                assert_eq!(*unwrap_result, *unwrapped_key);
880            }
881            _ => panic!("Found a non-FxfsKey wrapped key"),
882        }
883
884        // Do it twice to make sure the service can use the same key repeatedly.
885        let (wrapped_key, unwrapped_key, _) = service
886            .create_key_with_id(
887                1,
888                wrapping_key_id,
889                ObjectType::File,
890                fidl_fuchsia_io::FscryptPolicyFlags::empty(),
891            )
892            .expect("create_key_with_id failed");
893        // TODO(https://fxbug.dev/436902004): Switch to lkb32 wrapped key type.
894        match wrapped_key {
895            EncryptionKey::Fxfs(fxfs_key) => {
896                assert_eq!(fxfs_key.wrapping_key_id, wrapping_key_id);
897                let (unwrap_result, _) = service
898                    .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1)
899                    .expect("unwrap_key failed");
900                assert_eq!(*unwrap_result, *unwrapped_key);
901            }
902            _ => panic!("Found a non-FxfsKey wrapped key"),
903        }
904    }
905
906    #[test]
907    fn create_key_with_wrapping_key_that_does_not_exist() {
908        let service = CryptService::new(&[0; 32], &[1; 32], None);
909
910        let wrapping_key_id =
911            service.add_wrapping_key(&[2; 32], 0).expect("add wrapping key failed");
912
913        let (wrapped_key, unwrapped_key, _) = service
914            .create_key_with_id(
915                0,
916                wrapping_key_id,
917                ObjectType::File,
918                fidl_fuchsia_io::FscryptPolicyFlags::empty(),
919            )
920            .expect("create_key_with_id failed");
921
922        // TODO(https://fxbug.dev/436902004): Switch to lkb32 wrapped key type.
923        match wrapped_key {
924            EncryptionKey::Fxfs(fxfs_key) => {
925                assert_eq!(fxfs_key.wrapping_key_id, wrapping_key_id);
926                let (unwrap_result, _) = service
927                    .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 0)
928                    .expect("unwrap_key failed");
929                assert_eq!(*unwrap_result, *unwrapped_key);
930            }
931            _ => panic!("Found a non-FxfsKey wrapped key"),
932        }
933
934        service.forget_wrapping_key(wrapping_key_id, 0).unwrap();
935
936        service
937            .create_key_with_id(
938                0,
939                wrapping_key_id,
940                ObjectType::File,
941                fidl_fuchsia_io::FscryptPolicyFlags::empty(),
942            )
943            .expect_err("create_key_with_id should fail if the wrapping key does not exist");
944    }
945
946    #[test]
947    fn unwrap_key_wrong_key() {
948        let service = CryptService::new(&[0; 32], &[0xcd; 32], None);
949        let (mut fxfs_key, _) =
950            service.create_key(0, ffxfs::KeyPurpose::Data).expect("create_key failed");
951        for byte in &mut fxfs_key.key.0 {
952            *byte ^= 0xff;
953        }
954        service
955            .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 0)
956            .expect_err("unwrap_key should fail");
957    }
958
959    #[test]
960    fn unwrap_key_wrong_owner() {
961        let service = CryptService::new(&[0; 32], &[0xcd; 32], None);
962
963        let (fxfs_key, _) =
964            service.create_key(0, ffxfs::KeyPurpose::Data).expect("create_key failed");
965        service
966            .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1)
967            .expect_err("unwrap_key should fail");
968    }
969
970    #[fuchsia::test]
971    async fn test_add_wrapping_key_uses_raw_key() {
972        let (client, server) = fidl::endpoints::create_sync_proxy::<DeviceMarker>();
973        let raw_key_bytes = [0xAB; 32];
974        let expected_key = raw_key_bytes.clone();
975
976        std::thread::spawn(move || {
977            LocalExecutor::default().run_singlethreaded(async move {
978                let mut stream = server.into_stream();
979                while let Some(Ok(request)) = stream.next().await {
980                    match request {
981                        DeviceRequest::DeriveRawSecret { wrapped_key, responder } => {
982                            let mut derived = wrapped_key.clone();
983                            derived[0] ^= 0xFF;
984                            responder.send(Ok(&derived)).unwrap();
985                        }
986                        DeviceRequest::ProgramKey { wrapped_key, responder, .. } => {
987                            if wrapped_key == expected_key {
988                                let (_server_ep, client_ep) = zx::EventPair::create();
989                                responder.send(Ok(client_ep)).unwrap();
990                            } else {
991                                responder.send(Err(zx::Status::INVALID_ARGS.into_raw())).unwrap();
992                            }
993                        }
994                    }
995                }
996            })
997        });
998
999        let service = CryptService::new(&[0; 32], &[1; 32], Some(client));
1000        service.set_uuid([0x55; 16]);
1001        let key_id = service.add_wrapping_key(&raw_key_bytes, 0).expect("add_wrapping_key failed");
1002
1003        // 1. IV_INO_LBLK_32 returns 16-byte unwrapped key (ino_hash_key) + key_token for files
1004        //    and 64-byte unwrapped key for directories.
1005        let (wrapped_lblk32, unwrapped_lblk32, token_lblk32) = service
1006            .create_key_with_id(
1007                0,
1008                key_id,
1009                ObjectType::File,
1010                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
1011            )
1012            .expect("create_key failed");
1013        assert_eq!(unwrapped_lblk32.len(), 16);
1014        assert!(token_lblk32.is_some());
1015        let (_, unwrapped_dir32, token_dir32) = service
1016            .create_key_with_id(
1017                0,
1018                key_id,
1019                ObjectType::Directory,
1020                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
1021            )
1022            .expect("create_key dir failed");
1023        assert_eq!(unwrapped_dir32.len(), 64);
1024        assert!(token_dir32.is_none());
1025
1026        // 2. IV_INO_LBLK_64 returns empty unwrapped key + key_token for files and 48-byte unwrapped
1027        //    key for directories.
1028        assert!(service.contains_key(key_id));
1029        let (wrapped_lblk64, unwrapped_lblk64, token_lblk64) = service
1030            .create_key_with_id(
1031                0,
1032                key_id,
1033                ObjectType::File,
1034                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_64,
1035            )
1036            .expect("create_key failed");
1037        assert!(unwrapped_lblk64.is_empty());
1038        assert!(token_lblk64.is_some());
1039        let (wrapped_dir64, unwrapped_dir64, token_dir64) = service
1040            .create_key_with_id(
1041                0,
1042                key_id,
1043                ObjectType::Directory,
1044                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_64,
1045            )
1046            .expect("create_key dir failed");
1047        assert_eq!(unwrapped_dir64.len(), 48);
1048        assert!(token_dir64.is_none());
1049
1050        // 3. Standard policy (PAD_16 only, no IV_INO_LBLK_*) uses per-file WrappedKey::Fxfs (32B).
1051        assert!(service.contains_key(key_id));
1052        let (wrapped_std, unwrapped_std, token_std) = service
1053            .create_key_with_id(
1054                42,
1055                key_id,
1056                ObjectType::File,
1057                fidl_fuchsia_io::FscryptPolicyFlags::PAD_16,
1058            )
1059            .expect("create_key failed");
1060        assert!(matches!(wrapped_std, EncryptionKey::Fxfs(_)));
1061        assert_eq!(unwrapped_std.len(), 32);
1062        assert!(token_std.is_none());
1063
1064        // 4. Verify all three modes coexist under the same main key and unwrap deterministically.
1065        let (unwrapped, token) =
1066            service.unwrap_key(&wrapped_lblk32.into(), 0).expect("unwrap failed");
1067        assert_eq!(*unwrapped, *unwrapped_lblk32);
1068        assert!(token.is_some());
1069        let (unwrapped, token) =
1070            service.unwrap_key(&wrapped_lblk64.into(), 0).expect("unwrap failed");
1071        assert!(unwrapped.is_empty());
1072        assert!(token.is_some());
1073        let (unwrapped, token) =
1074            service.unwrap_key(&wrapped_dir64.into(), 0).expect("unwrap failed");
1075        assert_eq!(*unwrapped, *unwrapped_dir64);
1076        assert!(token.is_none());
1077        let (unwrapped, token) =
1078            service.unwrap_key(&wrapped_std.into(), 42).expect("unwrap failed");
1079        assert_eq!(*unwrapped, *unwrapped_std);
1080        assert!(token.is_none());
1081    }
1082
1083    #[fuchsia::test]
1084    async fn test_crypt_trait_create_and_unwrap_key() {
1085        let service = CryptService::new(&[0; 32], &[1; 32], None);
1086        service.set_uuid(TEST_UUID);
1087        let wrapping_key_id =
1088            service.add_wrapping_key(&[2; 32], 0).expect("add wrapping key failed");
1089
1090        let crypt: &dyn Crypt = &service;
1091
1092        // 1. Fxfs data and metadata keys created via Crypt trait.
1093        for purpose in [KeyPurpose::Data, KeyPurpose::Metadata] {
1094            let (fxfs_key, unwrapped_key) =
1095                crypt.create_key(0, purpose).await.expect("create_key failed");
1096            let unwrapped = crypt
1097                .unwrap_key(&WrappedKey::Fxfs(fxfs_key.clone().into()), 0)
1098                .await
1099                .expect("unwrap_key failed");
1100            assert_eq!(*unwrapped, *unwrapped_key);
1101
1102            // Security invariant: Mismatched owner fails unwrap with IO_DATA_INTEGRITY.
1103            assert_eq!(
1104                crypt
1105                    .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1)
1106                    .await
1107                    .expect_err("unwrap with wrong owner should fail"),
1108                zx::Status::IO_DATA_INTEGRITY
1109            );
1110        }
1111
1112        // 2. Fscrypt directory key created via Crypt trait.
1113        let (dir_key, unwrapped_dir) = crypt
1114            .create_key_with_id(
1115                0,
1116                wrapping_key_id,
1117                ObjectType::Directory,
1118                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
1119            )
1120            .await
1121            .expect("create_key_with_id dir failed");
1122        let unwrapped =
1123            crypt.unwrap_key(&WrappedKey::from(dir_key), 0).await.expect("unwrap_key dir failed");
1124        assert_eq!(*unwrapped, *unwrapped_dir);
1125
1126        // 3. Fscrypt file key created via Crypt trait.
1127        let (file_key, unwrapped_file) = crypt
1128            .create_key_with_id(
1129                0,
1130                wrapping_key_id,
1131                ObjectType::File,
1132                fidl_fuchsia_io::FscryptPolicyFlags::PAD_16,
1133            )
1134            .await
1135            .expect("create_key_with_id file failed");
1136        let unwrapped =
1137            crypt.unwrap_key(&WrappedKey::from(file_key), 0).await.expect("unwrap_key file failed");
1138        assert_eq!(*unwrapped, *unwrapped_file);
1139    }
1140
1141    #[fuchsia::test]
1142    async fn test_crypt_trait_batch_unwrap_keys() {
1143        let service = CryptService::new(&[0; 32], &[1; 32], None);
1144        service.set_uuid(TEST_UUID);
1145        let wrapping_key_id =
1146            service.add_wrapping_key(&[2; 32], 0).expect("add wrapping key failed");
1147
1148        let crypt: &dyn Crypt = &service;
1149
1150        let (fxfs_data_key, _) =
1151            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key data failed");
1152        let (fxfs_meta_key, _) =
1153            crypt.create_key(0, KeyPurpose::Metadata).await.expect("create_key metadata failed");
1154        let (dir_key, _) = crypt
1155            .create_key_with_id(
1156                0,
1157                wrapping_key_id,
1158                ObjectType::Directory,
1159                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
1160            )
1161            .await
1162            .expect("create_key_with_id failed");
1163
1164        // Key with a non-existent wrapping key ID.
1165        let missing_key =
1166            EncryptionKey::FscryptInoLblk32Dir { key_identifier: [0xff; 16], nonce: [0; 16] };
1167
1168        let keys = vec![
1169            (1, EncryptionKey::Fxfs(fxfs_data_key)),
1170            (2, EncryptionKey::Fxfs(fxfs_meta_key)),
1171            (3, dir_key),
1172            (4, missing_key),
1173        ];
1174
1175        let cipher_set = Arc::new(crypt.unwrap_keys(&keys, 0).await.expect("unwrap_keys failed"));
1176
1177        // Valid keys resolve to functional ciphers.
1178        assert!(matches!(cipher_set.find_key(1), FindKeyResult::Key(_)));
1179        assert!(matches!(cipher_set.find_key(2), FindKeyResult::Key(_)));
1180        let dir_cipher = match cipher_set.find_key(3) {
1181            FindKeyResult::Key(c) => c,
1182            _ => panic!("Expected key 3 to be found"),
1183        };
1184        // Invariant: Missing wrapping key resolves to Unavailable rather than failing the batch.
1185        assert!(matches!(cipher_set.find_key(4), FindKeyResult::Unavailable));
1186        // Invariant: Unqueried key resolves to NotFound.
1187        assert!(matches!(cipher_set.find_key(5), FindKeyResult::NotFound));
1188
1189        // Verify the directory cipher is functional.
1190        let mut filename = b"test_filename".to_vec();
1191        dir_cipher.encrypt_filename(100, &mut filename).expect("encrypt filename failed");
1192        assert_ne!(&filename, b"test_filename");
1193        dir_cipher.decrypt_filename(100, &mut filename).expect("decrypt filename failed");
1194        assert_eq!(&filename, b"test_filename");
1195    }
1196
1197    #[fuchsia::test]
1198    async fn test_crypt_trait_forgotten_key_lifecycle() {
1199        let service = CryptService::new(&[0; 32], &[1; 32], None);
1200        service.set_uuid(TEST_UUID);
1201        let wrapping_key_id =
1202            service.add_wrapping_key(&[2; 32], 0).expect("add wrapping key failed");
1203
1204        let crypt: &dyn Crypt = &service;
1205
1206        let (dir_key, unwrapped_dir) = crypt
1207            .create_key_with_id(
1208                0,
1209                wrapping_key_id,
1210                ObjectType::Directory,
1211                fidl_fuchsia_io::FscryptPolicyFlags::IV_INO_LBLK_32,
1212            )
1213            .await
1214            .expect("create_key_with_id failed");
1215
1216        let unwrapped = crypt
1217            .unwrap_key(&WrappedKey::from(dir_key.clone()), 0)
1218            .await
1219            .expect("unwrap_key before forget should succeed");
1220        assert_eq!(*unwrapped, *unwrapped_dir);
1221
1222        // Invariant: Forgetting the wrapping key locks it. Subsequent unwrapping through the trait
1223        // must fail with UNAVAILABLE.
1224        service.forget_wrapping_key(wrapping_key_id, 0).expect("forget wrapping key failed");
1225
1226        assert_eq!(
1227            crypt
1228                .unwrap_key(&WrappedKey::from(dir_key), 0)
1229                .await
1230                .expect_err("unwrap_key after forget should fail"),
1231            zx::Status::UNAVAILABLE
1232        );
1233    }
1234}