Skip to main content

rustls/crypto/cipher/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::ToString;
3use core::{array, fmt};
4
5use pki_types::FipsStatus;
6use zeroize::Zeroize;
7
8use crate::enums::{ContentType, ProtocolVersion};
9use crate::error::{ApiMisuse, Error};
10use crate::msgs::{put_u16, put_u64};
11use crate::suites::ConnectionTrafficSecrets;
12
13mod messages;
14pub(crate) use messages::encode_record_header;
15pub use messages::{
16    EncodableVersion, EncryptBuffer, InboundOpaque, OutboundPlain, Payload, Record, RecordError,
17};
18
19mod record_layer;
20pub(crate) use record_layer::{Decrypted, DecryptionState, EncryptionState, PreEncryptAction};
21
22/// Factory trait for building `RecordEncrypter` and `RecordDecrypter` for a TLS1.3 cipher suite.
23pub trait Tls13AeadAlgorithm: Send + Sync {
24    /// Build a `RecordEncrypter` for the given key/iv.
25    fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn RecordEncrypter>;
26
27    /// Build a `RecordDecrypter` for the given key/iv.
28    fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn RecordDecrypter>;
29
30    /// The length of key in bytes required by `encrypter()` and `decrypter()`.
31    fn key_len(&self) -> usize;
32
33    /// The length of IV in bytes required by `encrypter()` and `decrypter()`.
34    fn iv_len(&self) -> usize {
35        NONCE_LEN
36    }
37
38    /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item.
39    ///
40    /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported
41    /// variant of `ConnectionTrafficSecrets`.
42    fn extract_keys(
43        &self,
44        key: AeadKey,
45        iv: Iv,
46    ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError>;
47
48    /// Return the FIPS validation status of this implementation.
49    fn fips(&self) -> FipsStatus {
50        FipsStatus::Unvalidated
51    }
52}
53
54/// Factory trait for building `RecordEncrypter` and `RecordDecrypter` for a TLS1.2 cipher suite.
55pub trait Tls12AeadAlgorithm: Send + Sync + 'static {
56    /// Build a `RecordEncrypter` for the given key/iv and extra key block (which can be used for
57    /// improving explicit nonce size security, if needed).
58    ///
59    /// The length of `key` is set by [`KeyBlockShape::enc_key_len`].
60    ///
61    /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`].
62    ///
63    /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`].
64    fn encrypter(&self, key: AeadKey, iv: &[u8], extra: &[u8]) -> Box<dyn RecordEncrypter>;
65
66    /// Build a `RecordDecrypter` for the given key/iv.
67    ///
68    /// The length of `key` is set by [`KeyBlockShape::enc_key_len`].
69    ///
70    /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`].
71    fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box<dyn RecordDecrypter>;
72
73    /// Return a `KeyBlockShape` that defines how large the `key_block` is and how it
74    /// is split up prior to calling `encrypter()`, `decrypter()` and/or `extract_keys()`.
75    fn key_block_shape(&self) -> KeyBlockShape;
76
77    /// Convert the key material from `key`/`iv`, into a `ConnectionTrafficSecrets` item.
78    ///
79    /// The length of `key` is set by [`KeyBlockShape::enc_key_len`].
80    ///
81    /// The length of `iv` is set by [`KeyBlockShape::fixed_iv_len`].
82    ///
83    /// The length of `extra` is set by [`KeyBlockShape::explicit_nonce_len`].
84    ///
85    /// May return [`UnsupportedOperationError`] if the AEAD algorithm is not a supported
86    /// variant of `ConnectionTrafficSecrets`.
87    fn extract_keys(
88        &self,
89        key: AeadKey,
90        iv: &[u8],
91        explicit: &[u8],
92    ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError>;
93
94    /// Return the FIPS validation status of this implementation.
95    fn fips(&self) -> FipsStatus {
96        FipsStatus::Unvalidated
97    }
98}
99
100/// An error indicating that the AEAD algorithm does not support the requested operation.
101#[expect(clippy::exhaustive_structs)]
102#[derive(Debug, Eq, PartialEq, Clone, Copy)]
103pub struct UnsupportedOperationError;
104
105impl From<UnsupportedOperationError> for Error {
106    fn from(value: UnsupportedOperationError) -> Self {
107        Self::General(value.to_string())
108    }
109}
110
111impl fmt::Display for UnsupportedOperationError {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "operation not supported")
114    }
115}
116
117impl core::error::Error for UnsupportedOperationError {}
118
119/// How a TLS1.2 `key_block` is partitioned.
120///
121/// Note: ciphersuites with non-zero `mac_key_length` are  not currently supported.
122#[expect(clippy::exhaustive_structs)]
123pub struct KeyBlockShape {
124    /// How long keys are.
125    ///
126    /// `enc_key_length` terminology is from the standard ([RFC 5246 A.6]).
127    ///
128    /// [RFC 5246 A.6]: <https://www.rfc-editor.org/rfc/rfc5246#appendix-A.6>
129    pub enc_key_len: usize,
130
131    /// How long the fixed part of the 'IV' is.
132    ///
133    /// `fixed_iv_length` terminology is from the standard ([RFC 5246 A.6]).
134    ///
135    /// This isn't usually an IV, but we continue the
136    /// terminology misuse to match the standard.
137    ///
138    /// [RFC 5246 A.6]: <https://www.rfc-editor.org/rfc/rfc5246#appendix-A.6>
139    pub fixed_iv_len: usize,
140
141    /// This is a non-standard extension which extends the
142    /// key block to provide an initial explicit nonce offset,
143    /// in a deterministic and safe way.  GCM needs this,
144    /// chacha20poly1305 works this way by design.
145    pub explicit_nonce_len: usize,
146}
147
148/// Objects with this trait can decrypt TLS records.
149pub trait RecordDecrypter: Send + Sync {
150    /// Decrypt the given TLS record, using the sequence number
151    /// `seq` which can be used to derive a unique [`Nonce`].
152    fn decrypt<'a>(
153        &mut self,
154        record: Record<InboundOpaque<'a>>,
155        seq: u64,
156    ) -> Result<Record<&'a [u8]>, Error>;
157}
158
159/// Objects with this trait can encrypt TLS records.
160pub trait RecordEncrypter: Send + Sync {
161    /// Encrypt the given TLS record into `out`, using the sequence number
162    /// `seq` which can be used to derive a unique [`Nonce`].
163    ///
164    /// The encrypted payload including all framing the ciphersuite requires, such
165    /// as any explicit nonce, padding and/or authentication tag, is written to the
166    /// front of `out`. `out` must be at least [`Self::encrypted_payload_len()`] bytes
167    /// long. See [`EncryptBuffer`] for a convenient wrapper.
168    ///
169    /// The return value describes the resulting record: its payload borrows the
170    /// written prefix of `out`, and its `typ` and `version` are what the record
171    /// header should carry on the wire. Encoding the record header is the caller's
172    /// responsibility and implementations of the `RecordEncrypter` trait must not
173    /// write it to `out` themselves.
174    fn encrypt<'a>(
175        &mut self,
176        record: Record<OutboundPlain<'_>>,
177        seq: u64,
178        out: &'a mut [u8],
179    ) -> Result<Record<&'a [u8]>, Error>;
180
181    /// Return the length of the ciphertext that results from encrypting plaintext of length `payload_len`.
182    ///
183    /// For a zero `payload_len` this should return the _minimum_ overhead for any
184    /// payload.  Then, to fragment a long payload into chunks of length `F`,
185    /// Rustls will first set `A := encrypted_payload_len(0)` and then supply the
186    /// payload to [`Self::encrypt()`] in chunks of length `F - A`.  Each `encrypt()`
187    /// is then free to pad or otherwise transform the length at its option.
188    fn encrypted_payload_len(&self, payload_len: usize) -> usize;
189}
190
191/// A write or read IV.
192#[derive(Default, Clone)]
193pub struct Iv {
194    buf: [u8; Self::MAX_LEN],
195    used: usize,
196}
197
198impl Iv {
199    /// Create a new `Iv` from a byte slice.
200    ///
201    /// Returns an error if the length of `value` exceeds [`Self::MAX_LEN`].
202    pub fn new(value: &[u8]) -> Result<Self, Error> {
203        if value.len() > Self::MAX_LEN {
204            return Err(ApiMisuse::IvLengthExceedsMaximum {
205                actual: value.len(),
206                maximum: Self::MAX_LEN,
207            }
208            .into());
209        }
210        let mut buf = [0u8; Self::MAX_LEN];
211        buf[..value.len()].copy_from_slice(value);
212        Ok(Self {
213            buf,
214            used: value.len(),
215        })
216    }
217
218    /// Return the IV length.
219    #[expect(clippy::len_without_is_empty)]
220    pub fn len(&self) -> usize {
221        self.used
222    }
223
224    /// Maximum supported IV length.
225    pub const MAX_LEN: usize = 16;
226}
227
228impl From<[u8; NONCE_LEN]> for Iv {
229    fn from(bytes: [u8; NONCE_LEN]) -> Self {
230        Self::new(&bytes).expect("NONCE_LEN is within MAX_LEN")
231    }
232}
233
234impl AsRef<[u8]> for Iv {
235    fn as_ref(&self) -> &[u8] {
236        &self.buf[..self.used]
237    }
238}
239
240/// A nonce.  This is unique for all records on a connection.
241pub struct Nonce {
242    buf: [u8; Iv::MAX_LEN],
243    len: usize,
244}
245
246impl Nonce {
247    /// Combine an `Iv` and sequence number to produce a unique nonce.
248    ///
249    /// This is `iv ^ seq` where `seq` is encoded as a big-endian integer.
250    #[inline]
251    pub fn new(iv: &Iv, seq: u64) -> Self {
252        Self::new_inner(None, iv, seq)
253    }
254
255    /// Creates a unique nonce based on the multipath `path_id`, the `iv` and packet number `pn`.
256    ///
257    /// The nonce is computed as the XOR between the `iv` and the big-endian integer formed
258    /// by concatenating `path_id` (or 0) and `pn`.
259    pub fn quic(path_id: Option<u32>, iv: &Iv, pn: u64) -> Self {
260        Self::new_inner(path_id, iv, pn)
261    }
262
263    /// Creates a unique nonce based on the iv and sequence number seq.
264    #[inline]
265    fn new_inner(path_id: Option<u32>, iv: &Iv, seq: u64) -> Self {
266        let iv_len = iv.len();
267        let mut buf = [0u8; Iv::MAX_LEN];
268
269        if iv_len >= 8 {
270            put_u64(seq, &mut buf[iv_len - 8..iv_len]);
271            if let Some(path_id) = path_id {
272                if iv_len >= 12 {
273                    buf[iv_len - 12..iv_len - 8].copy_from_slice(&path_id.to_be_bytes());
274                }
275            }
276        } else {
277            let seq_bytes = seq.to_be_bytes();
278            buf[..iv_len].copy_from_slice(&seq_bytes[8 - iv_len..]);
279        }
280
281        buf[..iv_len]
282            .iter_mut()
283            .zip(iv.as_ref())
284            .for_each(|(s, iv)| *s ^= *iv);
285
286        Self { buf, len: iv_len }
287    }
288
289    /// Convert to a fixed-size array of length `N`.
290    ///
291    /// Returns an error if the nonce length is not `N`.
292    ///
293    /// For standard nonces, use `nonce.to_array::<NONCE_LEN>()?` or just `nonce.to_array()?`
294    /// which defaults to `NONCE_LEN`.
295    pub fn to_array<const N: usize>(&self) -> Result<[u8; N], Error> {
296        if self.len != N {
297            return Err(ApiMisuse::NonceArraySizeMismatch {
298                expected: N,
299                actual: self.len,
300            }
301            .into());
302        }
303        Ok(self.buf[..N]
304            .try_into()
305            .expect("nonce buffer conversion failed"))
306    }
307
308    /// Return the nonce value.
309    pub fn as_bytes(&self) -> &[u8] {
310        &self.buf[..self.len]
311    }
312
313    /// Return the nonce length.
314    #[expect(clippy::len_without_is_empty)]
315    pub fn len(&self) -> usize {
316        self.len
317    }
318}
319
320impl AsRef<[u8]> for Nonce {
321    fn as_ref(&self) -> &[u8] {
322        &self.buf[..self.len]
323    }
324}
325
326/// Size of TLS nonces (incorrectly termed "IV" in standard) for all supported ciphersuites
327/// (AES-GCM, Chacha20Poly1305)
328pub const NONCE_LEN: usize = 12;
329
330/// Returns a TLS1.3 `additional_data` encoding.
331///
332/// For decryption, the parameters should be those that were received on the wire.
333/// For encryption, the parameters should be those that will be put on the wire.
334///
335/// See RFC 9846 s5.2 for the `additional_data` definition.
336#[inline]
337pub fn make_tls13_aad(typ: ContentType, version: ProtocolVersion, payload_len: usize) -> [u8; 5] {
338    let version = version.to_array();
339    [
340        typ.into(),
341        version[0],
342        version[1],
343        (payload_len >> 8) as u8,
344        (payload_len & 0xff) as u8,
345    ]
346}
347
348/// Returns a TLS1.2 `additional_data` encoding.
349///
350/// See RFC 5246 s6.2.3.3 for the `additional_data` definition.
351#[inline]
352pub fn make_tls12_aad(
353    seq: u64,
354    typ: ContentType,
355    vers: ProtocolVersion,
356    len: usize,
357) -> [u8; TLS12_AAD_SIZE] {
358    let mut out = [0; TLS12_AAD_SIZE];
359    put_u64(seq, &mut out[0..]);
360    out[8] = typ.into();
361    put_u16(vers.into(), &mut out[9..]);
362    put_u16(len as u16, &mut out[11..]);
363    out
364}
365
366const TLS12_AAD_SIZE: usize = 8 + 1 + 2 + 2;
367
368/// A key for an AEAD algorithm.
369///
370/// This is a value type for a byte string up to `AeadKey::MAX_LEN` bytes in length.
371pub struct AeadKey {
372    buf: [u8; Self::MAX_LEN],
373    used: usize,
374}
375
376impl AeadKey {
377    pub(crate) fn new(buf: &[u8]) -> Self {
378        debug_assert!(buf.len() <= Self::MAX_LEN);
379        let mut key = Self::from([0u8; Self::MAX_LEN]);
380        key.buf[..buf.len()].copy_from_slice(buf);
381        key.used = buf.len();
382        key
383    }
384
385    pub(crate) fn with_length(self, len: usize) -> Self {
386        let Self { mut buf, used } = self;
387        assert!(len <= used);
388        buf[len..used].zeroize();
389        Self { buf, used: len }
390    }
391
392    /// Largest possible AEAD key in the ciphersuites we support.
393    pub(crate) const MAX_LEN: usize = 32;
394}
395
396impl Drop for AeadKey {
397    #[inline(never)]
398    fn drop(&mut self) {
399        self.buf[..self.used].zeroize();
400    }
401}
402
403impl AsRef<[u8]> for AeadKey {
404    fn as_ref(&self) -> &[u8] {
405        &self.buf[..self.used]
406    }
407}
408
409impl From<[u8; Self::MAX_LEN]> for AeadKey {
410    fn from(bytes: [u8; Self::MAX_LEN]) -> Self {
411        Self {
412            buf: bytes,
413            used: Self::MAX_LEN,
414        }
415    }
416}
417
418impl From<[u8; 16]> for AeadKey {
419    fn from(buf: [u8; 16]) -> Self {
420        Self {
421            buf: array::from_fn(|i| if i < 16 { buf[i] } else { 0 }),
422            used: 16,
423        }
424    }
425}
426
427#[cfg(test)]
428pub(crate) struct FakeAead;
429
430#[cfg(test)]
431impl Tls12AeadAlgorithm for FakeAead {
432    fn encrypter(&self, _: AeadKey, _: &[u8], _: &[u8]) -> Box<dyn RecordEncrypter> {
433        todo!()
434    }
435
436    fn decrypter(&self, _: AeadKey, _: &[u8]) -> Box<dyn RecordDecrypter> {
437        todo!()
438    }
439
440    fn key_block_shape(&self) -> KeyBlockShape {
441        todo!()
442    }
443
444    fn extract_keys(
445        &self,
446        _: AeadKey,
447        _: &[u8],
448        _: &[u8],
449    ) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
450        Err(UnsupportedOperationError)
451    }
452
453    fn fips(&self) -> FipsStatus {
454        FipsStatus::Unvalidated
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    /// Using test values provided in the spec in
463    /// <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-15.html#section-2.4>
464    #[test]
465    fn multipath_nonce() {
466        const PATH_ID: u32 = 3;
467        const PN: u64 = 54321;
468        const IV: [u8; 16] = 0x6b26114b9cba2b63a9e8dd4fu128.to_be_bytes();
469        const EXPECTED_NONCE: [u8; 16] = 0x6b2611489cba2b63a9e8097eu128.to_be_bytes();
470        let nonce = Nonce::quic(Some(PATH_ID), &Iv::new(&IV[4..]).unwrap(), PN);
471        assert_eq!(&EXPECTED_NONCE[4..], nonce.as_bytes());
472    }
473
474    #[test]
475    fn iv_len() {
476        let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap();
477        assert_eq!(iv.len(), NONCE_LEN);
478
479        let short_iv = Iv::new(&[1u8, 2, 3]).unwrap();
480        assert_eq!(short_iv.len(), 3);
481
482        let empty_iv = Iv::new(&[]).unwrap();
483        assert_eq!(empty_iv.len(), 0);
484    }
485
486    #[test]
487    fn iv_as_ref() {
488        let iv_data = [1u8, 2, 3, 4, 5];
489        let iv = Iv::new(&iv_data).unwrap();
490        let iv_ref: &[u8] = iv.as_ref();
491        assert_eq!(iv_ref, &iv_data);
492    }
493
494    #[test]
495    fn nonce_with_short_iv() {
496        let short_iv = Iv::new(&[0xAA, 0xBB, 0xCC, 0xDD]).unwrap();
497        let seq = 0x1122334455667788u64;
498        let nonce = Nonce::new(&short_iv, seq);
499
500        // The nonce should XOR the last 4 bytes of seq with the IV
501        assert_eq!(nonce.len(), 4);
502        let seq_bytes = seq.to_be_bytes();
503        let expected = [
504            0xAA ^ seq_bytes[4],
505            0xBB ^ seq_bytes[5],
506            0xCC ^ seq_bytes[6],
507            0xDD ^ seq_bytes[7],
508        ];
509        assert_eq!(nonce.as_bytes(), &expected);
510    }
511
512    #[test]
513    fn nonce_len() {
514        let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap();
515        let nonce = Nonce::new(&iv, 42);
516        assert_eq!(nonce.len(), NONCE_LEN);
517
518        let short_iv = Iv::new(&[1u8, 2]).unwrap();
519        let short_nonce = Nonce::new(&short_iv, 42);
520        assert_eq!(short_nonce.len(), 2);
521    }
522
523    #[test]
524    fn nonce_as_ref() {
525        let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap();
526        let nonce = Nonce::new(&iv, 42);
527        let nonce_ref: &[u8] = nonce.as_ref();
528        assert_eq!(nonce_ref.len(), NONCE_LEN);
529    }
530
531    #[test]
532    fn nonce_to_array_correct_size() {
533        let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap();
534        let nonce = Nonce::new(&iv, 42);
535        let array: [u8; NONCE_LEN] = nonce.to_array().unwrap();
536        assert_eq!(array.len(), NONCE_LEN);
537    }
538
539    #[test]
540    fn nonce_to_array_wrong_size() {
541        let iv = Iv::new(&[1u8; NONCE_LEN]).unwrap();
542        let nonce = Nonce::new(&iv, 42);
543        let result: Result<[u8; 16], Error> = nonce.to_array();
544        assert!(matches!(
545            result,
546            Err(Error::ApiMisuse(ApiMisuse::NonceArraySizeMismatch {
547                expected: 16,
548                actual: NONCE_LEN
549            }))
550        ));
551    }
552
553    #[test]
554    fn nonce_to_array_variable_length_error() {
555        // Create an IV with a non-standard length (8 bytes instead of 12)
556        let short_iv = Iv::new(&[0xAAu8; 8]).unwrap();
557        let nonce = Nonce::new(&short_iv, 42);
558
559        // Attempting to convert to standard NONCE_LEN should fail
560        let result: Result<[u8; NONCE_LEN], Error> = nonce.to_array();
561        if let Err(Error::ApiMisuse(ApiMisuse::NonceArraySizeMismatch { expected, actual })) =
562            result
563        {
564            assert_eq!(expected, NONCE_LEN);
565            assert_eq!(actual, 8);
566        } else {
567            panic!("Expected Error::ApiMisuse(NonceArraySizeMismatch)");
568        }
569
570        // But converting to the correct length should work
571        let result_correct: Result<[u8; 8], Error> = nonce.to_array();
572        assert!(result_correct.is_ok());
573    }
574
575    #[test]
576    fn nonce_xor_with_iv() {
577        let iv_data = [0xFFu8; NONCE_LEN];
578        let iv = Iv::new(&iv_data).unwrap();
579        let seq = 0x0000000000000001u64;
580        let nonce = Nonce::new(&iv, seq);
581
582        // The last byte should be 0xFF XOR 0x01 = 0xFE
583        let nonce_bytes = nonce.as_bytes();
584        assert_eq!(nonce_bytes[NONCE_LEN - 1], 0xFE);
585    }
586
587    #[test]
588    fn iv_length_exceeds_maximum() {
589        let too_long_iv = [0xAAu8; Iv::MAX_LEN + 1];
590        let result = Iv::new(&too_long_iv);
591
592        assert!(matches!(
593            result,
594            Err(Error::ApiMisuse(ApiMisuse::IvLengthExceedsMaximum {
595                actual: 17,
596                maximum: 16
597            }))
598        ));
599    }
600
601    #[test]
602    fn aead_key_16_bytes() {
603        let bytes = [0xABu8; 16];
604        let key = AeadKey::from(bytes);
605        assert_eq!(key.as_ref(), &bytes);
606    }
607}