Skip to main content

aws_lc_rs/aead/
tls.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use super::aead_ctx::{self, AeadCtx};
5use super::{Aad, Algorithm, AlgorithmID, Nonce, Tag, UnboundKey};
6use crate::error::Unspecified;
7use core::fmt::Debug;
8use core::ops::RangeFrom;
9
10/// The Transport Layer Security (TLS) protocol version.
11#[allow(clippy::module_name_repetitions)]
12#[derive(Debug, PartialEq, Eq, Clone, Copy)]
13#[non_exhaustive]
14pub enum TlsProtocolId {
15    /// TLS 1.2 (RFC 5246)
16    TLS12,
17
18    /// TLS 1.3 (RFC 8446)
19    TLS13,
20}
21
22/// AEAD Encryption key used for TLS protocol record encryption.
23///
24/// This type encapsulates encryption operations for TLS AEAD algorithms.
25/// It validates that the provides nonce values are monotonically increasing for each invocation.
26///
27/// The following algorithms are supported:
28/// * `AES_128_GCM`
29/// * `AES_256_GCM`
30///
31/// Prefer this type in place of `LessSafeKey`, `OpeningKey`, `SealingKey` for TLS protocol implementations.
32#[allow(clippy::module_name_repetitions)]
33pub struct TlsRecordSealingKey {
34    // The TLS-specific AEAD seal constructions in AWS-LC maintain internal mutable state
35    // (nonce counter). The seal methods take `&mut self` to prevent concurrent access.
36    key: UnboundKey,
37    protocol: TlsProtocolId,
38}
39
40impl TlsRecordSealingKey {
41    /// New TLS record sealing key. Only supports `AES_128_GCM` and `AES_256_GCM`.
42    ///
43    /// # Errors
44    /// * `Unspecified`: Returned if the length of `key_bytes` does not match the chosen algorithm,
45    ///   or if an unsupported algorithm is provided.
46    pub fn new(
47        algorithm: &'static Algorithm,
48        protocol: TlsProtocolId,
49        key_bytes: &[u8],
50    ) -> Result<Self, Unspecified> {
51        let ctx = match (algorithm.id, protocol) {
52            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
53                key_bytes,
54                algorithm.tag_len(),
55                aead_ctx::AeadDirection::Seal,
56            ),
57            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
58                key_bytes,
59                algorithm.tag_len(),
60                aead_ctx::AeadDirection::Seal,
61            ),
62            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
63                key_bytes,
64                algorithm.tag_len(),
65                aead_ctx::AeadDirection::Seal,
66            ),
67            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
68                key_bytes,
69                algorithm.tag_len(),
70                aead_ctx::AeadDirection::Seal,
71            ),
72            (
73                AlgorithmID::AES_128_GCM_SIV
74                | AlgorithmID::AES_192_GCM
75                | AlgorithmID::AES_256_GCM_SIV
76                | AlgorithmID::CHACHA20_POLY1305,
77                _,
78            ) => Err(Unspecified),
79        }?;
80        Ok(Self {
81            key: UnboundKey::from(ctx),
82            protocol,
83        })
84    }
85
86    /// Accepts a `Nonce` and `Aad` construction that is unique for this key and
87    /// TLS record sealing operation for the configured TLS protocol version.
88    ///
89    /// `nonce` must be unique and incremented per each sealing operation,
90    /// otherwise an error is returned.
91    ///
92    /// # Errors
93    /// `error::Unspecified` if encryption operation fails.
94    #[inline]
95    #[allow(clippy::needless_pass_by_value)]
96    pub fn seal_in_place_append_tag<A, InOut>(
97        &mut self,
98        nonce: Nonce,
99        aad: Aad<A>,
100        in_out: &mut InOut,
101    ) -> Result<(), Unspecified>
102    where
103        A: AsRef<[u8]>,
104        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
105    {
106        self.key
107            .seal_in_place_append_tag(Some(nonce), aad.as_ref(), in_out)
108            .map(|_| ())
109    }
110
111    /// Encrypts and signs (“seals”) data in place.
112    ///
113    /// `aad` is the additional authenticated data (AAD), if any. This is
114    /// authenticated but not encrypted. The type `A` could be a byte slice
115    /// `&[u8]`, a byte array `[u8; N]` for some constant `N`, `Vec<u8>`, etc.
116    /// If there is no AAD then use `Aad::empty()`.
117    ///
118    /// The plaintext is given as the input value of `in_out`. `seal_in_place()`
119    /// will overwrite the plaintext with the ciphertext and return the tag.
120    /// For most protocols, the caller must append the tag to the ciphertext.
121    /// The tag will be `self.algorithm.tag_len()` bytes long.
122    ///
123    /// The Nonce used for the operation is randomly generated, and returned to the caller.
124    ///
125    /// # Errors
126    /// `error::Unspecified` if encryption operation fails.
127    #[inline]
128    #[allow(clippy::needless_pass_by_value)]
129    pub fn seal_in_place_separate_tag<A>(
130        &mut self,
131        nonce: Nonce,
132        aad: Aad<A>,
133        in_out: &mut [u8],
134    ) -> Result<Tag, Unspecified>
135    where
136        A: AsRef<[u8]>,
137    {
138        self.key
139            .seal_in_place_separate_tag(Some(nonce), aad.as_ref(), in_out)
140            .map(|(_, tag)| tag)
141    }
142
143    /// Encrypts and signs (“seals”) `in_plaintext` into a separate `out_ciphertext`
144    /// buffer, leaving `in_plaintext` untouched.
145    ///
146    /// This is the out-of-place counterpart to [`Self::seal_in_place_separate_tag`].
147    ///
148    /// `out_ciphertext` must be exactly `in_plaintext.len()` bytes. `extra_in` is
149    /// additional plaintext, such as TLS 1.3's inner content-type byte, that is
150    /// encrypted into `extra_out_and_tag` ahead of the tag, so `extra_out_and_tag` must
151    /// be `extra_in.len() + self.algorithm().tag_len()` bytes. A caller with no extra
152    /// plaintext passes an empty `extra_in` and an `extra_out_and_tag` of
153    /// `self.algorithm().tag_len()` bytes.
154    ///
155    /// `nonce` must be unique and incremented per sealing operation, as for the in-place
156    /// methods: both advance the same counter.
157    ///
158    /// # Errors
159    /// `error::Unspecified` if the buffer lengths are wrong or the encryption operation
160    /// fails. A length mismatch is rejected before the AEAD runs, leaving both output
161    /// buffers untouched.
162    #[inline]
163    #[allow(clippy::needless_pass_by_value)]
164    pub fn seal_out_of_place_scatter<A>(
165        &mut self,
166        nonce: Nonce,
167        aad: Aad<A>,
168        in_plaintext: &[u8],
169        out_ciphertext: &mut [u8],
170        extra_in: &[u8],
171        extra_out_and_tag: &mut [u8],
172    ) -> Result<(), Unspecified>
173    where
174        A: AsRef<[u8]>,
175    {
176        self.key.seal_out_of_place_scatter(
177            nonce,
178            aad.as_ref(),
179            in_plaintext,
180            out_ciphertext,
181            extra_in,
182            extra_out_and_tag,
183        )
184    }
185
186    /// The key's AEAD algorithm.
187    #[inline]
188    #[must_use]
189    pub fn algorithm(&self) -> &'static Algorithm {
190        self.key.algorithm()
191    }
192
193    /// The key's associated `TlsProtocolId`.
194    #[must_use]
195    pub fn tls_protocol_id(&self) -> TlsProtocolId {
196        self.protocol
197    }
198}
199
200#[allow(clippy::missing_fields_in_debug)]
201impl Debug for TlsRecordSealingKey {
202    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203        f.debug_struct("TlsRecordSealingKey")
204            .field("key", &self.key)
205            .field("protocol", &self.protocol)
206            .finish()
207    }
208}
209
210/// AEAD Encryption key used for TLS protocol record encryption.
211///
212/// This type encapsulates decryption operations for TLS AEAD algorithms.
213///
214/// The following algorithms are supported:
215/// * `AES_128_GCM`
216/// * `AES_256_GCM`
217///
218/// Prefer this type in place of `LessSafeKey`, `OpeningKey`, `SealingKey` for TLS protocol implementations.
219#[allow(clippy::module_name_repetitions)]
220pub struct TlsRecordOpeningKey {
221    // Unlike the seal path, the TLS-specific AEAD open operations in AWS-LC are stateless
222    // and safe for concurrent use. The open methods take `&self`.
223    key: UnboundKey,
224    protocol: TlsProtocolId,
225}
226
227impl TlsRecordOpeningKey {
228    /// New TLS record opening key. Only supports `AES_128_GCM` and `AES_256_GCM` Algorithms.
229    ///
230    /// # Errors
231    /// * `Unspecified`: Returned if the length of `key_bytes` does not match the chosen algorithm,
232    ///   or if an unsupported algorithm is provided.
233    pub fn new(
234        algorithm: &'static Algorithm,
235        protocol: TlsProtocolId,
236        key_bytes: &[u8],
237    ) -> Result<Self, Unspecified> {
238        let ctx = match (algorithm.id, protocol) {
239            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
240                key_bytes,
241                algorithm.tag_len(),
242                aead_ctx::AeadDirection::Open,
243            ),
244            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
245                key_bytes,
246                algorithm.tag_len(),
247                aead_ctx::AeadDirection::Open,
248            ),
249            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
250                key_bytes,
251                algorithm.tag_len(),
252                aead_ctx::AeadDirection::Open,
253            ),
254            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
255                key_bytes,
256                algorithm.tag_len(),
257                aead_ctx::AeadDirection::Open,
258            ),
259            (
260                AlgorithmID::AES_128_GCM_SIV
261                | AlgorithmID::AES_192_GCM
262                | AlgorithmID::AES_256_GCM_SIV
263                | AlgorithmID::CHACHA20_POLY1305,
264                _,
265            ) => Err(Unspecified),
266        }?;
267        Ok(Self {
268            key: UnboundKey::from(ctx),
269            protocol,
270        })
271    }
272
273    /// See [`super::OpeningKey::open_in_place()`] for details.
274    ///
275    /// # Errors
276    /// `error::Unspecified` when ciphertext is invalid.
277    #[inline]
278    #[allow(clippy::needless_pass_by_value)]
279    pub fn open_in_place<'in_out, A>(
280        &self,
281        nonce: Nonce,
282        aad: Aad<A>,
283        in_out: &'in_out mut [u8],
284    ) -> Result<&'in_out mut [u8], Unspecified>
285    where
286        A: AsRef<[u8]>,
287    {
288        self.key.open_within(nonce, aad.as_ref(), in_out, 0..)
289    }
290
291    /// See [`super::OpeningKey::open_within()`] for details.
292    ///
293    /// # Errors
294    /// `error::Unspecified` when ciphertext is invalid.
295    #[inline]
296    #[allow(clippy::needless_pass_by_value)]
297    pub fn open_within<'in_out, A>(
298        &self,
299        nonce: Nonce,
300        aad: Aad<A>,
301        in_out: &'in_out mut [u8],
302        ciphertext_and_tag: RangeFrom<usize>,
303    ) -> Result<&'in_out mut [u8], Unspecified>
304    where
305        A: AsRef<[u8]>,
306    {
307        self.key
308            .open_within(nonce, aad.as_ref(), in_out, ciphertext_and_tag)
309    }
310
311    /// The key's AEAD algorithm.
312    #[inline]
313    #[must_use]
314    pub fn algorithm(&self) -> &'static Algorithm {
315        self.key.algorithm()
316    }
317
318    /// The key's associated `TlsProtocolId`.
319    #[must_use]
320    pub fn tls_protocol_id(&self) -> TlsProtocolId {
321        self.protocol
322    }
323}
324
325#[allow(clippy::missing_fields_in_debug)]
326impl Debug for TlsRecordOpeningKey {
327    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328        f.debug_struct("TlsRecordOpeningKey")
329            .field("key", &self.key)
330            .field("protocol", &self.protocol)
331            .finish()
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::{TlsProtocolId, TlsRecordOpeningKey, TlsRecordSealingKey};
338    use crate::aead::{Aad, Nonce, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305};
339    use crate::test::from_hex;
340    use paste::paste;
341
342    const TEST_128_BIT_KEY: &[u8] = &[
343        0xb0, 0x37, 0x9f, 0xf8, 0xfb, 0x8e, 0xa6, 0x31, 0xf4, 0x1c, 0xe6, 0x3e, 0xb5, 0xc5, 0x20,
344        0x7c,
345    ];
346
347    const TEST_256_BIT_KEY: &[u8] = &[
348        0x56, 0xd8, 0x96, 0x68, 0xbd, 0x96, 0xeb, 0xff, 0x5e, 0xa2, 0x0b, 0x34, 0xf2, 0x79, 0x84,
349        0x6e, 0x2b, 0x13, 0x01, 0x3d, 0xab, 0x1d, 0xa4, 0x07, 0x5a, 0x16, 0xd5, 0x0b, 0x53, 0xb0,
350        0xcc, 0x88,
351    ];
352
353    struct TlsNonceTestCase {
354        nonce: &'static str,
355        expect_err: bool,
356    }
357
358    const TLS_NONCE_TEST_CASES: &[TlsNonceTestCase] = &[
359        TlsNonceTestCase {
360            nonce: "9fab40177c900aad9fc28cc3",
361            expect_err: false,
362        },
363        TlsNonceTestCase {
364            nonce: "9fab40177c900aad9fc28cc4",
365            expect_err: false,
366        },
367        TlsNonceTestCase {
368            nonce: "9fab40177c900aad9fc28cc2",
369            expect_err: true,
370        },
371    ];
372
373    macro_rules! test_tls_aead {
374        ($name:ident, $alg:expr, $proto:expr, $key:expr) => {
375            paste! {
376                #[test]
377                fn [<test_ $name _tls_aead_unsupported>]() {
378                    assert!(TlsRecordSealingKey::new($alg, $proto, $key).is_err());
379                    assert!(TlsRecordOpeningKey::new($alg, $proto, $key).is_err());
380                }
381            }
382        };
383        ($name:ident, $alg:expr, $proto:expr, $key:expr, $expect_tag_len:expr, $expect_nonce_len:expr) => {
384            paste! {
385                #[test]
386                fn [<test_ $name>]() {
387                    let mut sealing_key =
388                        TlsRecordSealingKey::new($alg, $proto, $key).unwrap();
389
390                    let opening_key =
391                        TlsRecordOpeningKey::new($alg, $proto, $key).unwrap();
392
393                    for case in TLS_NONCE_TEST_CASES {
394                        let plaintext = from_hex("00112233445566778899aabbccddeeff").unwrap();
395
396                        assert_eq!($alg, sealing_key.algorithm());
397                        assert_eq!(*$expect_tag_len, $alg.tag_len());
398                        assert_eq!(*$expect_nonce_len, $alg.nonce_len());
399
400                        let mut in_out = Vec::from(plaintext.as_slice());
401
402                        let nonce = from_hex(case.nonce).unwrap();
403
404                        let nonce_bytes = nonce.as_slice();
405
406                        let result = sealing_key.seal_in_place_append_tag(
407                            Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
408                            Aad::empty(),
409                            &mut in_out,
410                        );
411
412                        match (result, case.expect_err) {
413                            (Ok(()), true) => panic!("expected error for seal_in_place_append_tag"),
414                            (Ok(()), false) => {}
415                            (Err(_), true) => return,
416                            (Err(e), false) => panic!("{e}"),
417                        }
418
419                        assert_ne!(plaintext, in_out[..plaintext.len()]);
420
421                        // copy ciphertext with prefix, to exercise `open_within`
422                        let mut offset_cipher_text = vec![ 1, 2, 3, 4 ];
423                        offset_cipher_text.extend_from_slice(&in_out);
424
425                        opening_key
426                            .open_in_place(
427                                Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
428                                Aad::empty(),
429                                &mut in_out,
430                            )
431                            .unwrap();
432
433                        assert_eq!(plaintext, in_out[..plaintext.len()]);
434
435                        opening_key
436                            .open_within(
437                                         Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
438                                         Aad::empty(),
439                                         &mut offset_cipher_text,
440                                         4..)
441                            .unwrap();
442                        assert_eq!(plaintext, offset_cipher_text[..plaintext.len()]);
443                    }
444                }
445            }
446        };
447    }
448
449    test_tls_aead!(
450        aes_128_gcm_tls12,
451        &AES_128_GCM,
452        TlsProtocolId::TLS12,
453        TEST_128_BIT_KEY,
454        &16,
455        &12
456    );
457    test_tls_aead!(
458        aes_128_gcm_tls13,
459        &AES_128_GCM,
460        TlsProtocolId::TLS13,
461        TEST_128_BIT_KEY,
462        &16,
463        &12
464    );
465    test_tls_aead!(
466        aes_256_gcm_tls12,
467        &AES_256_GCM,
468        TlsProtocolId::TLS12,
469        TEST_256_BIT_KEY,
470        &16,
471        &12
472    );
473    test_tls_aead!(
474        aes_256_gcm_tls13,
475        &AES_256_GCM,
476        TlsProtocolId::TLS13,
477        TEST_256_BIT_KEY,
478        &16,
479        &12
480    );
481    test_tls_aead!(
482        chacha20_poly1305_tls12,
483        &CHACHA20_POLY1305,
484        TlsProtocolId::TLS12,
485        TEST_256_BIT_KEY
486    );
487    test_tls_aead!(
488        chacha20_poly1305_tls13,
489        &CHACHA20_POLY1305,
490        TlsProtocolId::TLS13,
491        TEST_256_BIT_KEY
492    );
493}