Skip to main content

fxfs_crypto/
lib.rs

1// Copyright 2023 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::Aes256;
6use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
7use anyhow::anyhow;
8use async_trait::async_trait;
9use chacha20::cipher::{KeyIvInit, StreamCipher as _, StreamCipherSeek};
10use chacha20::{self, ChaCha20};
11use fidl_fuchsia_fxfs_common as fidl_fuchsia_fxfs;
12use fidl_fuchsia_io_common as fidl_fuchsia_io;
13use fprint::TypeFingerprint;
14use futures::TryStreamExt as _;
15use futures::stream::FuturesUnordered;
16use serde::de::{Error as SerdeError, Visitor};
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use std::collections::BTreeMap;
19use storage_xts::{Tweak, XtsCtsProcessor};
20use zerocopy::IntoBytes;
21use zx_status as zx;
22
23mod cipher;
24pub mod ff1;
25
26pub use cipher::fscrypt_ino_lblk32::{
27    FscryptInoLblk32FileCipher, FscryptSoftwareInoLblk32FileCipher,
28};
29pub use cipher::fscrypt_ino_lblk64::{FscryptInoLblk64DirCipher, FscryptInoLblk64FileCipher};
30pub use cipher::fxfs::FxfsCipher;
31pub use cipher::{
32    Cipher, CipherHolder, CipherSet, FindKeyResult, KeyType, MutPtrByteSlice, PtrByteSlice,
33    key_to_cipher,
34};
35pub use fidl_fuchsia_fxfs::{
36    EmptyStruct, FscryptKeyIdentifier, FscryptKeyIdentifierAndNonce, ObjectType, WrappedKey,
37};
38
39pub use cipher::{FSCRYPT_PADDING, MAX_SYMLINK_LEN};
40pub const FXFS_KEY_SIZE: usize = 256 / 8;
41pub const FXFS_WRAPPED_KEY_SIZE: usize = FXFS_KEY_SIZE + 16;
42
43/// Essentially just a vector by another name to indicate that it holds unwrapped key material.
44/// The length of an unwrapped key depends on the type of key that is wrapped.
45#[derive(Debug)]
46pub struct UnwrappedKey {
47    key: Vec<u8>,
48    /// Optional inline encryption key slot registered with the block device.
49    slot: Option<u8>,
50}
51impl UnwrappedKey {
52    pub fn new(key: Vec<u8>) -> Self {
53        Self { key, slot: None }
54    }
55
56    pub fn new_with_slot(key: Vec<u8>, slot: Option<u8>) -> Self {
57        Self { key, slot }
58    }
59
60    pub fn slot(&self) -> Option<u8> {
61        self.slot
62    }
63}
64impl std::ops::Deref for UnwrappedKey {
65    type Target = Vec<u8>;
66    fn deref(&self) -> &Self::Target {
67        &self.key
68    }
69}
70impl std::ops::DerefMut for UnwrappedKey {
71    fn deref_mut(&mut self) -> &mut Self::Target {
72        &mut self.key
73    }
74}
75
76/// A fixed length array of 48 bytes that holds an AES-256-GCM-SIV wrapped key.
77#[repr(transparent)]
78#[derive(Clone, Debug, PartialEq)]
79pub struct WrappedKeyBytes(pub [u8; FXFS_WRAPPED_KEY_SIZE]);
80impl Default for WrappedKeyBytes {
81    fn default() -> Self {
82        Self([0u8; FXFS_WRAPPED_KEY_SIZE])
83    }
84}
85impl TryFrom<Vec<u8>> for WrappedKeyBytes {
86    type Error = anyhow::Error;
87
88    fn try_from(buf: Vec<u8>) -> Result<Self, Self::Error> {
89        Ok(Self(buf.try_into().map_err(|_| anyhow!("wrapped key wrong length"))?))
90    }
91}
92impl From<[u8; FXFS_WRAPPED_KEY_SIZE]> for WrappedKeyBytes {
93    fn from(buf: [u8; FXFS_WRAPPED_KEY_SIZE]) -> Self {
94        Self(buf)
95    }
96}
97impl TypeFingerprint for WrappedKeyBytes {
98    fn fingerprint() -> String {
99        "WrappedKeyBytes".to_owned()
100    }
101}
102
103impl std::ops::Deref for WrappedKeyBytes {
104    type Target = [u8; FXFS_WRAPPED_KEY_SIZE];
105    fn deref(&self) -> &Self::Target {
106        &self.0
107    }
108}
109
110impl std::ops::DerefMut for WrappedKeyBytes {
111    fn deref_mut(&mut self) -> &mut Self::Target {
112        &mut self.0
113    }
114}
115
116// Because default impls of Serialize/Deserialize for [T; N] are only defined for N in 0..=32, we
117// have to define them ourselves.
118impl Serialize for WrappedKeyBytes {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: Serializer,
122    {
123        serializer.serialize_bytes(&self[..])
124    }
125}
126
127impl<'de> Deserialize<'de> for WrappedKeyBytes {
128    fn deserialize<D>(deserializer: D) -> Result<WrappedKeyBytes, D::Error>
129    where
130        D: Deserializer<'de>,
131    {
132        struct WrappedKeyVisitor;
133
134        impl<'d> Visitor<'d> for WrappedKeyVisitor {
135            type Value = WrappedKeyBytes;
136
137            fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
138                formatter.write_str("Expected wrapped keys to be 48 bytes")
139            }
140
141            fn visit_bytes<E>(self, bytes: &[u8]) -> Result<WrappedKeyBytes, E>
142            where
143                E: SerdeError,
144            {
145                self.visit_byte_buf(bytes.to_vec())
146            }
147
148            fn visit_byte_buf<E>(self, bytes: Vec<u8>) -> Result<WrappedKeyBytes, E>
149            where
150                E: SerdeError,
151            {
152                let orig_len = bytes.len();
153                let bytes: [u8; FXFS_WRAPPED_KEY_SIZE] =
154                    bytes.try_into().map_err(|_| SerdeError::invalid_length(orig_len, &self))?;
155                Ok(WrappedKeyBytes::from(bytes))
156            }
157        }
158        deserializer.deserialize_byte_buf(WrappedKeyVisitor)
159    }
160}
161
162fn reject_legacy_key<'de, D: Deserializer<'de>>(_: D) -> Result<FxfsKey, D::Error> {
163    Err(SerdeError::custom("LegacyFxfs keys are no longer supported"))
164}
165
166/// This specifies a single key to be used to encrypt/decrypt.
167#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypeFingerprint)]
168pub enum EncryptionKey {
169    /// Legacy Fxfs key that derives XTS tweaks using only the sector offset.
170    LegacyFxfs(#[serde(deserialize_with = "reject_legacy_key")] FxfsKey),
171    // NOTE: `key_identifier` can be thought of as the "name" of the key to use; it is not a
172    // per-file or per-directory key. It is similar to Fxfs's wrapping key ID, although it
173    // doesn't wrap anything. Files using the same `key_identifier` are encrypted using the
174    // same underlying key, with just differences in the tweak used. Directories also use the
175    // same underlying key, but some structures are further salted using the provided nonce.
176    FscryptInoLblk32File {
177        key_identifier: [u8; 16],
178    },
179    FscryptInoLblk32Dir {
180        key_identifier: [u8; 16],
181        nonce: [u8; 16],
182    },
183    /// Fxfs key that domain-separates XTS tweaks using `(attribute_id << 64) | sector_offset`.
184    Fxfs(FxfsKey),
185    FscryptInoLblk64File {
186        key_identifier: [u8; 16],
187    },
188    FscryptInoLblk64Dir {
189        key_identifier: [u8; 16],
190        nonce: [u8; 16],
191    },
192}
193
194impl EncryptionKey {
195    pub fn wrapping_key_id(&self) -> Option<WrappingKeyId> {
196        match self {
197            EncryptionKey::LegacyFxfs(_) => unreachable!(),
198            EncryptionKey::Fxfs(key) => Some(key.wrapping_key_id),
199            EncryptionKey::FscryptInoLblk32File { key_identifier }
200            | EncryptionKey::FscryptInoLblk32Dir { key_identifier, .. }
201            | EncryptionKey::FscryptInoLblk64File { key_identifier }
202            | EncryptionKey::FscryptInoLblk64Dir { key_identifier, .. } => Some(*key_identifier),
203        }
204    }
205}
206
207impl<'a> arbitrary::Arbitrary<'a> for EncryptionKey {
208    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
209        Ok(match u.int_in_range(0..=4)? {
210            0 => EncryptionKey::FscryptInoLblk32File { key_identifier: u.arbitrary()? },
211            1 => EncryptionKey::FscryptInoLblk32Dir {
212                key_identifier: u.arbitrary()?,
213                nonce: u.arbitrary()?,
214            },
215            2 => EncryptionKey::Fxfs(u.arbitrary()?),
216            3 => EncryptionKey::FscryptInoLblk64File { key_identifier: u.arbitrary()? },
217            4 => EncryptionKey::FscryptInoLblk64Dir {
218                key_identifier: u.arbitrary()?,
219                nonce: u.arbitrary()?,
220            },
221            _ => unreachable!(),
222        })
223    }
224}
225
226impl From<EncryptionKey> for WrappedKey {
227    fn from(value: EncryptionKey) -> Self {
228        match value {
229            EncryptionKey::LegacyFxfs(_) => unreachable!(),
230            EncryptionKey::Fxfs(key) => WrappedKey::Fxfs(key.into()),
231            EncryptionKey::FscryptInoLblk32File { key_identifier } => {
232                WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier })
233            }
234            EncryptionKey::FscryptInoLblk32Dir { key_identifier, nonce } => {
235                WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
236                    key_identifier,
237                    nonce,
238                })
239            }
240            EncryptionKey::FscryptInoLblk64File { key_identifier } => {
241                WrappedKey::FscryptInoLblk64File(FscryptKeyIdentifier { key_identifier })
242            }
243            EncryptionKey::FscryptInoLblk64Dir { key_identifier, nonce } => {
244                WrappedKey::FscryptInoLblk64Dir(FscryptKeyIdentifierAndNonce {
245                    key_identifier,
246                    nonce,
247                })
248            }
249        }
250    }
251}
252
253impl From<&EncryptionKey> for KeyType {
254    fn from(value: &EncryptionKey) -> Self {
255        match value {
256            EncryptionKey::LegacyFxfs(_) => unreachable!(),
257            EncryptionKey::Fxfs(_) => KeyType::Fxfs,
258            EncryptionKey::FscryptInoLblk32File { .. } => KeyType::FscryptInoLblk32File,
259            EncryptionKey::FscryptInoLblk32Dir { .. } => KeyType::FscryptInoLblk32Dir,
260            EncryptionKey::FscryptInoLblk64File { .. } => KeyType::FscryptInoLblk64File,
261            EncryptionKey::FscryptInoLblk64Dir { .. } => KeyType::FscryptInoLblk64Dir,
262        }
263    }
264}
265
266impl TryFrom<WrappedKey> for EncryptionKey {
267    type Error = zx::Status;
268
269    fn try_from(value: WrappedKey) -> Result<Self, Self::Error> {
270        Ok(match value {
271            WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey { wrapping_key_id, wrapped_key }) => {
272                EncryptionKey::Fxfs(FxfsKey { wrapping_key_id, key: WrappedKeyBytes(wrapped_key) })
273            }
274            WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier }) => {
275                EncryptionKey::FscryptInoLblk32File { key_identifier }
276            }
277            WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
278                key_identifier,
279                nonce,
280            }) => EncryptionKey::FscryptInoLblk32Dir { key_identifier, nonce },
281            WrappedKey::FscryptInoLblk64File(FscryptKeyIdentifier { key_identifier }) => {
282                EncryptionKey::FscryptInoLblk64File { key_identifier }
283            }
284            WrappedKey::FscryptInoLblk64Dir(FscryptKeyIdentifierAndNonce {
285                key_identifier,
286                nonce,
287            }) => EncryptionKey::FscryptInoLblk64Dir { key_identifier, nonce },
288            _ => return Err(zx::Status::NOT_SUPPORTED),
289        })
290    }
291}
292
293/// An Fxfs encryption key wrapped in AES-256-GCM-SIV and the associated wrapping key ID.
294/// This can be provided to Crypt::unwrap_key to obtain the unwrapped key.
295#[derive(Clone, Default, Debug, Serialize, Deserialize, TypeFingerprint, PartialEq)]
296pub struct FxfsKey {
297    /// The identifier of the wrapping key.  The identifier has meaning to whatever is doing the
298    /// unwrapping.
299    pub wrapping_key_id: WrappingKeyId,
300    /// AES 256 requires a 512 bit key, which is made of two 256 bit keys, one for the data and one
301    /// for the tweak.  It is safe to use the same 256 bit key for both (see
302    /// https://csrc.nist.gov/CSRC/media/Projects/Block-Cipher-Techniques/documents/BCM/Comments/XTS/follow-up_XTS_comments-Ball.pdf)
303    /// which is what we do here.  Since the key is wrapped with AES-GCM-SIV, there are an
304    /// additional 16 bytes paid per key (so the actual key material is 32 bytes once unwrapped).
305    pub key: WrappedKeyBytes,
306}
307
308pub type WrappingKeyId = [u8; 16];
309
310impl From<FxfsKey> for fidl_fuchsia_fxfs::FxfsKey {
311    fn from(value: FxfsKey) -> Self {
312        fidl_fuchsia_fxfs::FxfsKey {
313            wrapping_key_id: value.wrapping_key_id,
314            wrapped_key: value.key.0,
315        }
316    }
317}
318
319impl<'a> arbitrary::Arbitrary<'a> for FxfsKey {
320    fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
321        // There doesn't seem to be much point to randomly generate crypto keys.
322        return Ok(FxfsKey::default());
323    }
324}
325
326/// A thin wrapper around a ChaCha20 stream cipher.  This will use a zero nonce. **NOTE**: Great
327/// care must be taken not to encrypt different plaintext with the same key and offset (even across
328/// multiple boots), so consider if this suits your purpose before using it.
329pub struct StreamCipher(ChaCha20);
330
331impl StreamCipher {
332    pub fn new(key: &UnwrappedKey, offset: u64) -> Self {
333        let mut cipher = Self(ChaCha20::new(
334            &chacha20::Key::try_from(&key[..]).expect("Invalid StreamCipher key length"),
335            /* nonce: */ &[0; 12].into(),
336        ));
337        cipher.0.seek(offset);
338        cipher
339    }
340
341    pub fn encrypt(&mut self, buffer: &mut [u8]) {
342        fxfs_trace::duration!("StreamCipher::encrypt", "len" => buffer.len());
343        self.0.apply_keystream(buffer);
344    }
345
346    pub fn decrypt(&mut self, buffer: &mut [u8]) {
347        fxfs_trace::duration!("StreamCipher::decrypt", "len" => buffer.len());
348        self.0.apply_keystream(buffer);
349    }
350
351    pub fn offset(&self) -> u64 {
352        self.0.current_pos()
353    }
354
355    pub fn key_is_new(&self) -> bool {
356        0 == self.0.current_pos()
357    }
358}
359
360/// Cipher used for encrypting and decrypting journal mutations with AES-256-XTS and CTS.
361pub struct JournalXtsCipher {
362    key: Aes256,
363    // Used to prevent reallocations when we need to move into an aligned pointer.
364    aligned_buf: Vec<u128>,
365    tweak: u64,
366    key_start_tweak: u64,
367}
368
369impl JournalXtsCipher {
370    pub fn new(key: &UnwrappedKey, tweak: u64) -> Self {
371        Self {
372            key: Aes256::new(key.as_slice().try_into().expect("Invalid key length")),
373            aligned_buf: Vec::new(),
374            tweak,
375            key_start_tweak: tweak,
376        }
377    }
378
379    /// Encrypts `buffer` using AES-256-XTS with ciphertext stealing into a newly allocated vector.
380    /// If `buffer.len() < 16`, it will be zero-padded to 16 bytes first.
381    pub fn encrypt(&mut self, buffer: &[u8]) -> Vec<u8> {
382        let out_len = std::cmp::max(buffer.len(), 16);
383        let mut out_buf = vec![0u8; out_len];
384        let mut tweak = Tweak::new(self.tweak as u128);
385        self.key.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
386        if buffer.as_ptr().cast::<u128>().is_aligned()
387            && buffer.len() >= 16
388            && out_buf.as_ptr().cast::<u128>().is_aligned()
389        {
390            let slice = PtrByteSlice::from(buffer);
391            let out_slice = MutPtrByteSlice::from(&mut out_buf[..]);
392            self.key.encrypt_with_backend(XtsCtsProcessor::new(tweak, slice, out_slice));
393        } else {
394            self.aligned_buf.resize(out_len.div_ceil(16), 0u128);
395            let aligned_bytes = &mut self.aligned_buf.as_mut_bytes()[..out_len];
396            if buffer.len() < 16 {
397                aligned_bytes.fill(0);
398            }
399            aligned_bytes[..buffer.len()].copy_from_slice(buffer);
400            let slice = MutPtrByteSlice::from(&mut *aligned_bytes);
401            self.key.encrypt_with_backend(XtsCtsProcessor::new_in_place(tweak, slice));
402            out_buf.copy_from_slice(&aligned_bytes[..out_len]);
403        }
404        self.tweak += 1;
405        out_buf
406    }
407
408    /// Decrypts `buffer` in place using AES-256-XTS with ciphertext stealing.
409    /// `buffer.len()` must be >= 16.
410    pub fn decrypt(&mut self, buffer: &mut [u8]) {
411        assert!(buffer.len() >= 16);
412        let len = buffer.len();
413        let mut tweak = Tweak::new(self.tweak as u128);
414        self.key.encrypt_block(tweak.as_mut_bytes().try_into().unwrap());
415
416        if buffer.as_ptr().cast::<u128>().is_aligned() {
417            let slice = MutPtrByteSlice::from(buffer);
418            self.key.decrypt_with_backend(XtsCtsProcessor::new_in_place(tweak, slice));
419        } else {
420            self.aligned_buf.resize(buffer.len().div_ceil(16), 0u128);
421            let aligned_bytes = &mut self.aligned_buf.as_mut_bytes()[..len];
422            aligned_bytes.copy_from_slice(buffer);
423            let slice = MutPtrByteSlice::from(aligned_bytes);
424            self.key.decrypt_with_backend(XtsCtsProcessor::new_in_place(tweak, slice));
425            buffer.copy_from_slice(&self.aligned_buf.as_bytes()[..len]);
426        }
427        self.tweak += 1;
428    }
429
430    pub fn current_tweak(&self) -> u64 {
431        self.tweak
432    }
433
434    pub fn key_is_new(&self) -> bool {
435        self.tweak - self.key_start_tweak == 0
436    }
437}
438
439pub enum JournalCipher {
440    ChaCha20(StreamCipher),
441    Aes256Xts(Box<JournalXtsCipher>),
442}
443
444impl JournalCipher {
445    pub fn new_chacha20(key: &UnwrappedKey, offset: u64) -> Self {
446        JournalCipher::ChaCha20(StreamCipher::new(key, offset))
447    }
448
449    pub fn new_aes256_xts(key: &UnwrappedKey, current_tweak: u64) -> Self {
450        JournalCipher::Aes256Xts(Box::new(JournalXtsCipher::new(key, current_tweak)))
451    }
452
453    pub fn encrypt(&mut self, buffer: &[u8]) -> Vec<u8> {
454        match self {
455            JournalCipher::ChaCha20(_) => unreachable!(),
456            JournalCipher::Aes256Xts(c) => c.encrypt(buffer),
457        }
458    }
459
460    pub fn decrypt(&mut self, buffer: &mut [u8]) {
461        match self {
462            JournalCipher::ChaCha20(c) => c.decrypt(buffer),
463            JournalCipher::Aes256Xts(c) => c.decrypt(buffer),
464        }
465    }
466
467    pub fn sequence_number(&self) -> u64 {
468        match self {
469            JournalCipher::ChaCha20(c) => c.offset(),
470            JournalCipher::Aes256Xts(c) => c.current_tweak(),
471        }
472    }
473
474    /// If a key has not been used for encrypting for decrypting yet.
475    pub fn key_is_new(&self) -> bool {
476        match self {
477            JournalCipher::ChaCha20(c) => c.key_is_new(),
478            JournalCipher::Aes256Xts(c) => c.key_is_new(),
479        }
480    }
481}
482
483/// Different keys are used for metadata and data in order to make certain operations requiring a
484/// metadata key rotation (e.g. secure erase) more efficient.
485pub enum KeyPurpose {
486    /// The key will be used to wrap user data.
487    Data,
488    /// The key will be used to wrap internal metadata.
489    Metadata,
490}
491
492impl TryFrom<fidl_fuchsia_fxfs::KeyPurpose> for KeyPurpose {
493    type Error = zx::Status;
494
495    fn try_from(purpose: fidl_fuchsia_fxfs::KeyPurpose) -> Result<Self, Self::Error> {
496        match purpose {
497            fidl_fuchsia_fxfs::KeyPurpose::Data => Ok(KeyPurpose::Data),
498            fidl_fuchsia_fxfs::KeyPurpose::Metadata => Ok(KeyPurpose::Metadata),
499            _ => Err(zx::Status::INVALID_ARGS),
500        }
501    }
502}
503
504impl From<KeyPurpose> for fidl_fuchsia_fxfs::KeyPurpose {
505    fn from(purpose: KeyPurpose) -> Self {
506        match purpose {
507            KeyPurpose::Data => fidl_fuchsia_fxfs::KeyPurpose::Data,
508            KeyPurpose::Metadata => fidl_fuchsia_fxfs::KeyPurpose::Metadata,
509        }
510    }
511}
512
513/// The `Crypt` trait below provides a mechanism to unwrap a key or set of keys.
514/// The wrapping keys can be one of these types.
515pub enum WrappingKey {
516    /// This is used for keys of the type WrappedKey::Fxfs.
517    Aes256GcmSiv([u8; 32]),
518    /// This is used for legacy fscrypt keys that use a 64-byte main key.
519    Fscrypt([u8; 64]),
520}
521impl From<[u8; 32]> for WrappingKey {
522    fn from(value: [u8; 32]) -> Self {
523        WrappingKey::Aes256GcmSiv(value)
524    }
525}
526impl From<[u8; 64]> for WrappingKey {
527    fn from(value: [u8; 64]) -> Self {
528        WrappingKey::Fscrypt(value)
529    }
530}
531
532/// The keys it unwraps can be wrapped with either Aes256GcmSiv (ideally) or using via
533/// legacy fscrypt master key + HKDF.
534
535/// An interface trait with the ability to wrap and unwrap encryption keys.
536///
537/// Note that existence of this trait does not imply that an object will **securely**
538/// wrap and unwrap keys; rather just that it presents an interface for wrapping operations.
539#[async_trait]
540pub trait Crypt: Send + Sync {
541    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
542    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
543    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
544    /// by looking at the wrapped keys.
545    async fn create_key(
546        &self,
547        owner: u64,
548        purpose: KeyPurpose,
549    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status>;
550
551    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
552    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
553    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
554    /// by looking at the wrapped keys. `flags` specifies the fscrypt policy flags for the object.
555    async fn create_key_with_id(
556        &self,
557        owner: u64,
558        wrapping_key_id: WrappingKeyId,
559        object_type: ObjectType,
560        flags: fidl_fuchsia_io::FscryptPolicyFlags,
561    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status>;
562
563    /// Unwraps a single key, returning a raw unwrapped key.
564    /// This method is generally only used with StreamCipher and FF1.
565    /// Returns `zx::Status::UNAVAILABLE` if the key is known but cannot be unwrapped (e.g. it is
566    /// locked).
567    /// Returns `zx::Status::NOT_FOUND` if the wrapping key is not known.
568    async fn unwrap_key(
569        &self,
570        wrapped_key: &WrappedKey,
571        owner: u64,
572    ) -> Result<UnwrappedKey, zx::Status>;
573
574    /// Unwraps object keys and stores the result as a CipherSet mapping key_id to:
575    ///   - Some(cipher) if unwrapping key was found or
576    ///   - None if unwrapping key was missing.
577    /// The cipher can be used directly to encrypt/decrypt data.
578    async fn unwrap_keys(
579        &self,
580        keys: &[(u64, EncryptionKey)],
581        owner: u64,
582    ) -> Result<CipherSet, zx::Status> {
583        let futures: FuturesUnordered<_> = keys
584            .iter()
585            .map(|(key_id, key)| {
586                let key_id = *key_id;
587                let wrapped_key = WrappedKey::from(key.clone());
588                let owner = owner;
589                async move {
590                    match self.unwrap_key(&wrapped_key, owner).await {
591                        Ok(unwrapped_key) => cipher::key_to_cipher(key, &unwrapped_key)
592                            .map(|c| (key_id, cipher::CipherHolder::Cipher(c))),
593                        Err(zx::Status::UNAVAILABLE) => {
594                            Ok((key_id, cipher::CipherHolder::Unavailable))
595                        }
596                        Err(e) => Err(e),
597                    }
598                }
599            })
600            .collect();
601        let result = futures.try_collect::<BTreeMap<u64, _>>().await?;
602        Ok(result.into())
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use super::{JournalCipher, JournalXtsCipher, StreamCipher, UnwrappedKey};
609    use zerocopy::IntoBytes;
610
611    #[test]
612    fn test_journal_xts_cipher_roundtrip() {
613        let key = UnwrappedKey::new(vec![0x42; 32]);
614        let mut enc = JournalXtsCipher::new(&key, 0);
615        let mut dec = JournalXtsCipher::new(&key, 0);
616
617        // Test < 16 bytes (padded to 16)
618        let buf_short = vec![1, 2, 3, 4, 5];
619        let mut enc_short = enc.encrypt(&buf_short);
620        assert_eq!(enc_short.len(), 16);
621        dec.decrypt(&mut enc_short);
622        assert_eq!(&enc_short[..5], &[1, 2, 3, 4, 5]);
623        assert_eq!(&enc_short[5..], &[0; 11]);
624        assert_eq!(enc.current_tweak(), 1);
625        assert_eq!(dec.current_tweak(), 1);
626
627        // Test exact 16 bytes
628        let buf_16 = vec![7u8; 16];
629        let mut enc_16 = enc.encrypt(&buf_16);
630        assert_eq!(enc_16.len(), 16);
631        dec.decrypt(&mut enc_16);
632        assert_eq!(enc_16, vec![7u8; 16]);
633        assert_eq!(enc.current_tweak(), 2);
634        assert_eq!(dec.current_tweak(), 2);
635
636        // Test 25 bytes (CTS)
637        let buf_25: Vec<u8> = (0..25).collect();
638        let mut enc_25 = enc.encrypt(&buf_25);
639        assert_eq!(enc_25.len(), 25);
640        dec.decrypt(&mut enc_25);
641        assert_eq!(enc_25, (0..25).collect::<Vec<u8>>());
642        assert_eq!(enc.current_tweak(), 3);
643        assert_eq!(dec.current_tweak(), 3);
644    }
645
646    #[test]
647    fn test_journal_cipher_enum() {
648        let key = UnwrappedKey::new(vec![0x33; 32]);
649        let mut enc = JournalCipher::new_aes256_xts(&key, 0);
650        let mut dec = JournalCipher::new_aes256_xts(&key, 0);
651
652        let buf = vec![9u8; 20];
653        let mut enc_buf = enc.encrypt(&buf);
654        assert_eq!(enc_buf.len(), 20);
655        dec.decrypt(&mut enc_buf);
656        assert_eq!(enc_buf, vec![9u8; 20]);
657        assert_eq!(enc.sequence_number(), 1);
658    }
659
660    #[test]
661    fn test_stream_cipher_offset() {
662        let key = UnwrappedKey::new(vec![
663            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
664            25, 26, 27, 28, 29, 30, 31, 32,
665        ]);
666        let mut cipher1 = StreamCipher::new(&key, 0);
667        let mut p1 = [1, 2, 3, 4];
668        let mut c1 = p1.clone();
669        cipher1.encrypt(&mut c1);
670
671        let mut cipher2 = StreamCipher::new(&key, 1);
672        let p2 = [5, 6, 7, 8];
673        let mut c2 = p2.clone();
674        cipher2.encrypt(&mut c2);
675
676        let xor_fn = |buf1: &mut [u8], buf2| {
677            for (b1, b2) in buf1.iter_mut().zip(buf2) {
678                *b1 ^= b2;
679            }
680        };
681
682        // Check that c1 ^ c2 != p1 ^ p2 (which would be the case if the same offset was used for
683        // both ciphers).
684        xor_fn(&mut c1, &c2);
685        xor_fn(&mut p1, &p2);
686        assert_ne!(c1, p1);
687    }
688
689    #[test]
690    fn test_journal_xts_cipher_aligned_and_unaligned_allocations() {
691        let key = UnwrappedKey::new(vec![0x5a; 32]);
692        let test_lengths = [1, 5, 16, 25];
693        // Enough initialized storage for every test length plus the unaligned offset.
694        let backing_size: usize = test_lengths.iter().max().unwrap() + 1;
695        // Packed into u128 to ensure alignment.
696        let mut backing_vector = vec![0u128; backing_size.div_ceil(16)];
697
698        for &len in &test_lengths {
699            let plaintext = vec![42u8; len];
700            let mut expected_plaintext = plaintext.clone();
701            if expected_plaintext.len() < 16 {
702                expected_plaintext.resize(16, 0);
703            }
704
705            // Aligned case.
706            {
707                let buf = &mut backing_vector.as_mut_bytes()[..len];
708                assert!(buf.as_ptr().cast::<u128>().is_aligned());
709                buf.copy_from_slice(plaintext.as_slice());
710                let mut encrypted = {
711                    let mut enc = JournalXtsCipher::new(&key, 13);
712                    enc.encrypt(buf)
713                };
714                {
715                    let mut dec = JournalXtsCipher::new(&key, 13);
716                    dec.decrypt(&mut encrypted);
717                }
718                assert_eq!(encrypted, expected_plaintext);
719            }
720
721            // Unaligned case.
722            {
723                let buf = &mut backing_vector.as_mut_bytes()[1..1 + len];
724                assert!(!buf.as_ptr().cast::<u128>().is_aligned());
725                buf.copy_from_slice(plaintext.as_slice());
726                let mut encrypted = {
727                    let mut enc = JournalXtsCipher::new(&key, 13);
728                    enc.encrypt(buf)
729                };
730                {
731                    let mut dec = JournalXtsCipher::new(&key, 13);
732                    dec.decrypt(&mut encrypted);
733                }
734                assert_eq!(encrypted, expected_plaintext);
735            }
736        }
737    }
738}