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