Coverage Report

Created: 2026-05-16 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.16.3/src/aead/tls.rs
Line
Count
Source
1
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4
use super::aead_ctx::{self, AeadCtx};
5
use super::{Aad, Algorithm, AlgorithmID, Nonce, Tag, UnboundKey};
6
use crate::error::Unspecified;
7
use core::fmt::Debug;
8
use 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]
14
pub 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)]
33
pub 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
40
impl 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
0
    pub fn new(
47
0
        algorithm: &'static Algorithm,
48
0
        protocol: TlsProtocolId,
49
0
        key_bytes: &[u8],
50
0
    ) -> Result<Self, Unspecified> {
51
0
        let ctx = match (algorithm.id, protocol) {
52
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
53
0
                key_bytes,
54
0
                algorithm.tag_len(),
55
0
                aead_ctx::AeadDirection::Seal,
56
            ),
57
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
58
0
                key_bytes,
59
0
                algorithm.tag_len(),
60
0
                aead_ctx::AeadDirection::Seal,
61
            ),
62
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
63
0
                key_bytes,
64
0
                algorithm.tag_len(),
65
0
                aead_ctx::AeadDirection::Seal,
66
            ),
67
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
68
0
                key_bytes,
69
0
                algorithm.tag_len(),
70
0
                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
0
            ) => Err(Unspecified),
79
0
        }?;
80
0
        Ok(Self {
81
0
            key: UnboundKey::from(ctx),
82
0
            protocol,
83
0
        })
84
0
    }
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
0
    pub fn seal_in_place_append_tag<A, InOut>(
97
0
        &mut self,
98
0
        nonce: Nonce,
99
0
        aad: Aad<A>,
100
0
        in_out: &mut InOut,
101
0
    ) -> Result<(), Unspecified>
102
0
    where
103
0
        A: AsRef<[u8]>,
104
0
        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
105
    {
106
0
        self.key
107
0
            .seal_in_place_append_tag(Some(nonce), aad.as_ref(), in_out)
108
0
            .map(|_| ())
109
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::seal_in_place_append_tag::<[u8; 5], rustls::msgs::message::outbound::PrefixedPayload>
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::seal_in_place_append_tag::<_, _>
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
0
    pub fn seal_in_place_separate_tag<A>(
130
0
        &mut self,
131
0
        nonce: Nonce,
132
0
        aad: Aad<A>,
133
0
        in_out: &mut [u8],
134
0
    ) -> Result<Tag, Unspecified>
135
0
    where
136
0
        A: AsRef<[u8]>,
137
    {
138
0
        self.key
139
0
            .seal_in_place_separate_tag(Some(nonce), aad.as_ref(), in_out)
140
0
            .map(|(_, tag)| tag)
141
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::seal_in_place_separate_tag::<[u8; 13]>
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::seal_in_place_separate_tag::<_>
142
143
    /// The key's AEAD algorithm.
144
    #[inline]
145
    #[must_use]
146
0
    pub fn algorithm(&self) -> &'static Algorithm {
147
0
        self.key.algorithm()
148
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::algorithm
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::algorithm
149
150
    /// The key's associated `TlsProtocolId`.
151
    #[must_use]
152
0
    pub fn tls_protocol_id(&self) -> TlsProtocolId {
153
0
        self.protocol
154
0
    }
155
}
156
157
#[allow(clippy::missing_fields_in_debug)]
158
impl Debug for TlsRecordSealingKey {
159
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160
0
        f.debug_struct("TlsRecordSealingKey")
161
0
            .field("key", &self.key)
162
0
            .field("protocol", &self.protocol)
163
0
            .finish()
164
0
    }
165
}
166
167
/// AEAD Encryption key used for TLS protocol record encryption.
168
///
169
/// This type encapsulates decryption operations for TLS AEAD algorithms.
170
///
171
/// The following algorithms are supported:
172
/// * `AES_128_GCM`
173
/// * `AES_256_GCM`
174
///
175
/// Prefer this type in place of `LessSafeKey`, `OpeningKey`, `SealingKey` for TLS protocol implementations.
176
#[allow(clippy::module_name_repetitions)]
177
pub struct TlsRecordOpeningKey {
178
    // Unlike the seal path, the TLS-specific AEAD open operations in AWS-LC are stateless
179
    // and safe for concurrent use. The open methods take `&self`.
180
    key: UnboundKey,
181
    protocol: TlsProtocolId,
182
}
183
184
impl TlsRecordOpeningKey {
185
    /// New TLS record opening key. Only supports `AES_128_GCM` and `AES_256_GCM` Algorithms.
186
    ///
187
    /// # Errors
188
    /// * `Unspecified`: Returned if the length of `key_bytes` does not match the chosen algorithm,
189
    ///   or if an unsupported algorithm is provided.
190
0
    pub fn new(
191
0
        algorithm: &'static Algorithm,
192
0
        protocol: TlsProtocolId,
193
0
        key_bytes: &[u8],
194
0
    ) -> Result<Self, Unspecified> {
195
0
        let ctx = match (algorithm.id, protocol) {
196
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
197
0
                key_bytes,
198
0
                algorithm.tag_len(),
199
0
                aead_ctx::AeadDirection::Open,
200
            ),
201
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
202
0
                key_bytes,
203
0
                algorithm.tag_len(),
204
0
                aead_ctx::AeadDirection::Open,
205
            ),
206
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
207
0
                key_bytes,
208
0
                algorithm.tag_len(),
209
0
                aead_ctx::AeadDirection::Open,
210
            ),
211
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
212
0
                key_bytes,
213
0
                algorithm.tag_len(),
214
0
                aead_ctx::AeadDirection::Open,
215
            ),
216
            (
217
                AlgorithmID::AES_128_GCM_SIV
218
                | AlgorithmID::AES_192_GCM
219
                | AlgorithmID::AES_256_GCM_SIV
220
                | AlgorithmID::CHACHA20_POLY1305,
221
                _,
222
0
            ) => Err(Unspecified),
223
0
        }?;
224
0
        Ok(Self {
225
0
            key: UnboundKey::from(ctx),
226
0
            protocol,
227
0
        })
228
0
    }
229
230
    /// See [`super::OpeningKey::open_in_place()`] for details.
231
    ///
232
    /// # Errors
233
    /// `error::Unspecified` when ciphertext is invalid.
234
    #[inline]
235
    #[allow(clippy::needless_pass_by_value)]
236
0
    pub fn open_in_place<'in_out, A>(
237
0
        &self,
238
0
        nonce: Nonce,
239
0
        aad: Aad<A>,
240
0
        in_out: &'in_out mut [u8],
241
0
    ) -> Result<&'in_out mut [u8], Unspecified>
242
0
    where
243
0
        A: AsRef<[u8]>,
244
    {
245
0
        self.key.open_within(nonce, aad.as_ref(), in_out, 0..)
246
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::open_in_place::<[u8; 5]>
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::open_in_place::<[u8; 13]>
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::open_in_place::<_>
247
248
    /// See [`super::OpeningKey::open_within()`] for details.
249
    ///
250
    /// # Errors
251
    /// `error::Unspecified` when ciphertext is invalid.
252
    #[inline]
253
    #[allow(clippy::needless_pass_by_value)]
254
0
    pub fn open_within<'in_out, A>(
255
0
        &self,
256
0
        nonce: Nonce,
257
0
        aad: Aad<A>,
258
0
        in_out: &'in_out mut [u8],
259
0
        ciphertext_and_tag: RangeFrom<usize>,
260
0
    ) -> Result<&'in_out mut [u8], Unspecified>
261
0
    where
262
0
        A: AsRef<[u8]>,
263
    {
264
0
        self.key
265
0
            .open_within(nonce, aad.as_ref(), in_out, ciphertext_and_tag)
266
0
    }
267
268
    /// The key's AEAD algorithm.
269
    #[inline]
270
    #[must_use]
271
0
    pub fn algorithm(&self) -> &'static Algorithm {
272
0
        self.key.algorithm()
273
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::algorithm
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::algorithm
274
275
    /// The key's associated `TlsProtocolId`.
276
    #[must_use]
277
0
    pub fn tls_protocol_id(&self) -> TlsProtocolId {
278
0
        self.protocol
279
0
    }
280
}
281
282
#[allow(clippy::missing_fields_in_debug)]
283
impl Debug for TlsRecordOpeningKey {
284
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285
0
        f.debug_struct("TlsRecordOpeningKey")
286
0
            .field("key", &self.key)
287
0
            .field("protocol", &self.protocol)
288
0
            .finish()
289
0
    }
290
}
291
292
#[cfg(test)]
293
mod tests {
294
    use super::{TlsProtocolId, TlsRecordOpeningKey, TlsRecordSealingKey};
295
    use crate::aead::{Aad, Nonce, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305};
296
    use crate::test::from_hex;
297
    use paste::paste;
298
299
    const TEST_128_BIT_KEY: &[u8] = &[
300
        0xb0, 0x37, 0x9f, 0xf8, 0xfb, 0x8e, 0xa6, 0x31, 0xf4, 0x1c, 0xe6, 0x3e, 0xb5, 0xc5, 0x20,
301
        0x7c,
302
    ];
303
304
    const TEST_256_BIT_KEY: &[u8] = &[
305
        0x56, 0xd8, 0x96, 0x68, 0xbd, 0x96, 0xeb, 0xff, 0x5e, 0xa2, 0x0b, 0x34, 0xf2, 0x79, 0x84,
306
        0x6e, 0x2b, 0x13, 0x01, 0x3d, 0xab, 0x1d, 0xa4, 0x07, 0x5a, 0x16, 0xd5, 0x0b, 0x53, 0xb0,
307
        0xcc, 0x88,
308
    ];
309
310
    struct TlsNonceTestCase {
311
        nonce: &'static str,
312
        expect_err: bool,
313
    }
314
315
    const TLS_NONCE_TEST_CASES: &[TlsNonceTestCase] = &[
316
        TlsNonceTestCase {
317
            nonce: "9fab40177c900aad9fc28cc3",
318
            expect_err: false,
319
        },
320
        TlsNonceTestCase {
321
            nonce: "9fab40177c900aad9fc28cc4",
322
            expect_err: false,
323
        },
324
        TlsNonceTestCase {
325
            nonce: "9fab40177c900aad9fc28cc2",
326
            expect_err: true,
327
        },
328
    ];
329
330
    macro_rules! test_tls_aead {
331
        ($name:ident, $alg:expr, $proto:expr, $key:expr) => {
332
            paste! {
333
                #[test]
334
                fn [<test_ $name _tls_aead_unsupported>]() {
335
                    assert!(TlsRecordSealingKey::new($alg, $proto, $key).is_err());
336
                    assert!(TlsRecordOpeningKey::new($alg, $proto, $key).is_err());
337
                }
338
            }
339
        };
340
        ($name:ident, $alg:expr, $proto:expr, $key:expr, $expect_tag_len:expr, $expect_nonce_len:expr) => {
341
            paste! {
342
                #[test]
343
                fn [<test_ $name>]() {
344
                    let mut sealing_key =
345
                        TlsRecordSealingKey::new($alg, $proto, $key).unwrap();
346
347
                    let opening_key =
348
                        TlsRecordOpeningKey::new($alg, $proto, $key).unwrap();
349
350
                    for case in TLS_NONCE_TEST_CASES {
351
                        let plaintext = from_hex("00112233445566778899aabbccddeeff").unwrap();
352
353
                        assert_eq!($alg, sealing_key.algorithm());
354
                        assert_eq!(*$expect_tag_len, $alg.tag_len());
355
                        assert_eq!(*$expect_nonce_len, $alg.nonce_len());
356
357
                        let mut in_out = Vec::from(plaintext.as_slice());
358
359
                        let nonce = from_hex(case.nonce).unwrap();
360
361
                        let nonce_bytes = nonce.as_slice();
362
363
                        let result = sealing_key.seal_in_place_append_tag(
364
                            Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
365
                            Aad::empty(),
366
                            &mut in_out,
367
                        );
368
369
                        match (result, case.expect_err) {
370
                            (Ok(()), true) => panic!("expected error for seal_in_place_append_tag"),
371
                            (Ok(()), false) => {}
372
                            (Err(_), true) => return,
373
                            (Err(e), false) => panic!("{e}"),
374
                        }
375
376
                        assert_ne!(plaintext, in_out[..plaintext.len()]);
377
378
                        // copy ciphertext with prefix, to exercise `open_within`
379
                        let mut offset_cipher_text = vec![ 1, 2, 3, 4 ];
380
                        offset_cipher_text.extend_from_slice(&in_out);
381
382
                        opening_key
383
                            .open_in_place(
384
                                Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
385
                                Aad::empty(),
386
                                &mut in_out,
387
                            )
388
                            .unwrap();
389
390
                        assert_eq!(plaintext, in_out[..plaintext.len()]);
391
392
                        opening_key
393
                            .open_within(
394
                                         Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
395
                                         Aad::empty(),
396
                                         &mut offset_cipher_text,
397
                                         4..)
398
                            .unwrap();
399
                        assert_eq!(plaintext, offset_cipher_text[..plaintext.len()]);
400
                    }
401
                }
402
            }
403
        };
404
    }
405
406
    test_tls_aead!(
407
        aes_128_gcm_tls12,
408
        &AES_128_GCM,
409
        TlsProtocolId::TLS12,
410
        TEST_128_BIT_KEY,
411
        &16,
412
        &12
413
    );
414
    test_tls_aead!(
415
        aes_128_gcm_tls13,
416
        &AES_128_GCM,
417
        TlsProtocolId::TLS13,
418
        TEST_128_BIT_KEY,
419
        &16,
420
        &12
421
    );
422
    test_tls_aead!(
423
        aes_256_gcm_tls12,
424
        &AES_256_GCM,
425
        TlsProtocolId::TLS12,
426
        TEST_256_BIT_KEY,
427
        &16,
428
        &12
429
    );
430
    test_tls_aead!(
431
        aes_256_gcm_tls13,
432
        &AES_256_GCM,
433
        TlsProtocolId::TLS13,
434
        TEST_256_BIT_KEY,
435
        &16,
436
        &12
437
    );
438
    test_tls_aead!(
439
        chacha20_poly1305_tls12,
440
        &CHACHA20_POLY1305,
441
        TlsProtocolId::TLS12,
442
        TEST_256_BIT_KEY
443
    );
444
    test_tls_aead!(
445
        chacha20_poly1305_tls13,
446
        &CHACHA20_POLY1305,
447
        TlsProtocolId::TLS13,
448
        TEST_256_BIT_KEY
449
    );
450
}