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