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