Coverage Report

Created: 2025-10-29 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.12.4/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 construction for TLS ciphers in AWS-LC are not thread-safe!
35
    // The choice here was either wrap the underlying EVP_AEAD_CTX in a Mutex as done here,
36
    // or force this type to !Sync. Since this is an implementation detail of AWS-LC
37
    // we have optex to manage this behavior internally.
38
    key: UnboundKey,
39
    protocol: TlsProtocolId,
40
}
41
42
impl TlsRecordSealingKey {
43
    /// New TLS record sealing key. Only supports `AES_128_GCM` and `AES_256_GCM`.
44
    ///
45
    /// # Errors
46
    /// * `Unspecified`: Returned if the length of `key_bytes` does not match the chosen algorithm,
47
    ///   or if an unsupported algorithm is provided.
48
0
    pub fn new(
49
0
        algorithm: &'static Algorithm,
50
0
        protocol: TlsProtocolId,
51
0
        key_bytes: &[u8],
52
0
    ) -> Result<Self, Unspecified> {
53
0
        let ctx = match (algorithm.id, protocol) {
54
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
55
0
                key_bytes,
56
0
                algorithm.tag_len(),
57
0
                aead_ctx::AeadDirection::Seal,
58
            ),
59
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
60
0
                key_bytes,
61
0
                algorithm.tag_len(),
62
0
                aead_ctx::AeadDirection::Seal,
63
            ),
64
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
65
0
                key_bytes,
66
0
                algorithm.tag_len(),
67
0
                aead_ctx::AeadDirection::Seal,
68
            ),
69
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
70
0
                key_bytes,
71
0
                algorithm.tag_len(),
72
0
                aead_ctx::AeadDirection::Seal,
73
            ),
74
            (
75
                AlgorithmID::AES_128_GCM_SIV
76
                | AlgorithmID::AES_192_GCM
77
                | AlgorithmID::AES_256_GCM_SIV
78
                | AlgorithmID::CHACHA20_POLY1305,
79
                _,
80
0
            ) => Err(Unspecified),
81
0
        }?;
82
0
        Ok(Self {
83
0
            key: UnboundKey::from(ctx),
84
0
            protocol,
85
0
        })
86
0
    }
87
88
    /// Accepts a `Nonce` and `Aad` construction that is unique for this key and
89
    /// TLS record sealing operation for the configured TLS protocol version.
90
    ///
91
    /// `nonce` must be unique and incremented per each sealing operation,
92
    /// otherwise an error is returned.
93
    ///
94
    /// # Errors
95
    /// `error::Unspecified` if encryption operation fails.
96
    #[inline]
97
    #[allow(clippy::needless_pass_by_value)]
98
0
    pub fn seal_in_place_append_tag<A, InOut>(
99
0
        &mut self,
100
0
        nonce: Nonce,
101
0
        aad: Aad<A>,
102
0
        in_out: &mut InOut,
103
0
    ) -> Result<(), Unspecified>
104
0
    where
105
0
        A: AsRef<[u8]>,
106
0
        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
107
    {
108
0
        self.key
109
0
            .seal_in_place_append_tag(Some(nonce), aad.as_ref(), in_out)
110
0
            .map(|_| ())
111
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::<_, _>
112
113
    /// Encrypts and signs (“seals”) data in place.
114
    ///
115
    /// `aad` is the additional authenticated data (AAD), if any. This is
116
    /// authenticated but not encrypted. The type `A` could be a byte slice
117
    /// `&[u8]`, a byte array `[u8; N]` for some constant `N`, `Vec<u8>`, etc.
118
    /// If there is no AAD then use `Aad::empty()`.
119
    ///
120
    /// The plaintext is given as the input value of `in_out`. `seal_in_place()`
121
    /// will overwrite the plaintext with the ciphertext and return the tag.
122
    /// For most protocols, the caller must append the tag to the ciphertext.
123
    /// The tag will be `self.algorithm.tag_len()` bytes long.
124
    ///
125
    /// The Nonce used for the operation is randomly generated, and returned to the caller.
126
    ///
127
    /// # Errors
128
    /// `error::Unspecified` if encryption operation fails.
129
    #[inline]
130
    #[allow(clippy::needless_pass_by_value)]
131
0
    pub fn seal_in_place_separate_tag<A>(
132
0
        &mut self,
133
0
        nonce: Nonce,
134
0
        aad: Aad<A>,
135
0
        in_out: &mut [u8],
136
0
    ) -> Result<Tag, Unspecified>
137
0
    where
138
0
        A: AsRef<[u8]>,
139
    {
140
0
        self.key
141
0
            .seal_in_place_separate_tag(Some(nonce), aad.as_ref(), in_out)
142
0
            .map(|(_, tag)| tag)
143
0
    }
144
145
    /// The key's AEAD algorithm.
146
    #[inline]
147
    #[must_use]
148
0
    pub fn algorithm(&self) -> &'static Algorithm {
149
0
        self.key.algorithm()
150
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::algorithm
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordSealingKey>::algorithm
151
152
    /// The key's associated `TlsProtocolId`.
153
    #[must_use]
154
0
    pub fn tls_protocol_id(&self) -> TlsProtocolId {
155
0
        self.protocol
156
0
    }
157
}
158
159
#[allow(clippy::missing_fields_in_debug)]
160
impl Debug for TlsRecordSealingKey {
161
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162
0
        f.debug_struct("TlsRecordSealingKey")
163
0
            .field("key", &self.key)
164
0
            .field("protocol", &self.protocol)
165
0
            .finish()
166
0
    }
167
}
168
169
/// AEAD Encryption key used for TLS protocol record encryption.
170
///
171
/// This type encapsulates decryption operations for TLS AEAD algorithms.
172
///
173
/// The following algorithms are supported:
174
/// * `AES_128_GCM`
175
/// * `AES_256_GCM`
176
///
177
/// Prefer this type in place of `LessSafeKey`, `OpeningKey`, `SealingKey` for TLS protocol implementations.
178
#[allow(clippy::module_name_repetitions)]
179
pub struct TlsRecordOpeningKey {
180
    // The TLS specific construction for TLS ciphers in AWS-LC are not thread-safe!
181
    // The choice here was either wrap the underlying EVP_AEAD_CTX in a Mutex as done here,
182
    // or force this type to !Sync. Since this is an implementation detail of AWS-LC
183
    // we have optex to manage this behavior internally.
184
    key: UnboundKey,
185
    protocol: TlsProtocolId,
186
}
187
188
impl TlsRecordOpeningKey {
189
    /// New TLS record opening key. Only supports `AES_128_GCM` and `AES_256_GCM` Algorithms.
190
    ///
191
    /// # Errors
192
    /// * `Unspecified`: Returned if the length of `key_bytes` does not match the chosen algorithm,
193
    ///   or if an unsupported algorithm is provided.
194
0
    pub fn new(
195
0
        algorithm: &'static Algorithm,
196
0
        protocol: TlsProtocolId,
197
0
        key_bytes: &[u8],
198
0
    ) -> Result<Self, Unspecified> {
199
0
        let ctx = match (algorithm.id, protocol) {
200
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_128_gcm_tls12(
201
0
                key_bytes,
202
0
                algorithm.tag_len(),
203
0
                aead_ctx::AeadDirection::Open,
204
            ),
205
0
            (AlgorithmID::AES_128_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_128_gcm_tls13(
206
0
                key_bytes,
207
0
                algorithm.tag_len(),
208
0
                aead_ctx::AeadDirection::Open,
209
            ),
210
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS12) => AeadCtx::aes_256_gcm_tls12(
211
0
                key_bytes,
212
0
                algorithm.tag_len(),
213
0
                aead_ctx::AeadDirection::Open,
214
            ),
215
0
            (AlgorithmID::AES_256_GCM, TlsProtocolId::TLS13) => AeadCtx::aes_256_gcm_tls13(
216
0
                key_bytes,
217
0
                algorithm.tag_len(),
218
0
                aead_ctx::AeadDirection::Open,
219
            ),
220
            (
221
                AlgorithmID::AES_128_GCM_SIV
222
                | AlgorithmID::AES_192_GCM
223
                | AlgorithmID::AES_256_GCM_SIV
224
                | AlgorithmID::CHACHA20_POLY1305,
225
                _,
226
0
            ) => Err(Unspecified),
227
0
        }?;
228
0
        Ok(Self {
229
0
            key: UnboundKey::from(ctx),
230
0
            protocol,
231
0
        })
232
0
    }
233
234
    /// See [`super::OpeningKey::open_in_place()`] for details.
235
    ///
236
    /// # Errors
237
    /// `error::Unspecified` when ciphertext is invalid.
238
    #[inline]
239
    #[allow(clippy::needless_pass_by_value)]
240
0
    pub fn open_in_place<'in_out, A>(
241
0
        &self,
242
0
        nonce: Nonce,
243
0
        aad: Aad<A>,
244
0
        in_out: &'in_out mut [u8],
245
0
    ) -> Result<&'in_out mut [u8], Unspecified>
246
0
    where
247
0
        A: AsRef<[u8]>,
248
    {
249
0
        self.key.open_within(nonce, aad.as_ref(), in_out, 0..)
250
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::<_>
251
252
    /// See [`super::OpeningKey::open_within()`] for details.
253
    ///
254
    /// # Errors
255
    /// `error::Unspecified` when ciphertext is invalid.
256
    #[inline]
257
    #[allow(clippy::needless_pass_by_value)]
258
0
    pub fn open_within<'in_out, A>(
259
0
        &self,
260
0
        nonce: Nonce,
261
0
        aad: Aad<A>,
262
0
        in_out: &'in_out mut [u8],
263
0
        ciphertext_and_tag: RangeFrom<usize>,
264
0
    ) -> Result<&'in_out mut [u8], Unspecified>
265
0
    where
266
0
        A: AsRef<[u8]>,
267
    {
268
0
        self.key
269
0
            .open_within(nonce, aad.as_ref(), in_out, ciphertext_and_tag)
270
0
    }
271
272
    /// The key's AEAD algorithm.
273
    #[inline]
274
    #[must_use]
275
0
    pub fn algorithm(&self) -> &'static Algorithm {
276
0
        self.key.algorithm()
277
0
    }
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::algorithm
Unexecuted instantiation: <aws_lc_rs::aead::tls::TlsRecordOpeningKey>::algorithm
278
279
    /// The key's associated `TlsProtocolId`.
280
    #[must_use]
281
0
    pub fn tls_protocol_id(&self) -> TlsProtocolId {
282
0
        self.protocol
283
0
    }
284
}
285
286
#[allow(clippy::missing_fields_in_debug)]
287
impl Debug for TlsRecordOpeningKey {
288
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
289
0
        f.debug_struct("TlsRecordOpeningKey")
290
0
            .field("key", &self.key)
291
0
            .field("protocol", &self.protocol)
292
0
            .finish()
293
0
    }
294
}
295
296
#[cfg(test)]
297
mod tests {
298
    use super::{TlsProtocolId, TlsRecordOpeningKey, TlsRecordSealingKey};
299
    use crate::aead::{Aad, Nonce, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305};
300
    use crate::test::from_hex;
301
    use paste::paste;
302
303
    const TEST_128_BIT_KEY: &[u8] = &[
304
        0xb0, 0x37, 0x9f, 0xf8, 0xfb, 0x8e, 0xa6, 0x31, 0xf4, 0x1c, 0xe6, 0x3e, 0xb5, 0xc5, 0x20,
305
        0x7c,
306
    ];
307
308
    const TEST_256_BIT_KEY: &[u8] = &[
309
        0x56, 0xd8, 0x96, 0x68, 0xbd, 0x96, 0xeb, 0xff, 0x5e, 0xa2, 0x0b, 0x34, 0xf2, 0x79, 0x84,
310
        0x6e, 0x2b, 0x13, 0x01, 0x3d, 0xab, 0x1d, 0xa4, 0x07, 0x5a, 0x16, 0xd5, 0x0b, 0x53, 0xb0,
311
        0xcc, 0x88,
312
    ];
313
314
    struct TlsNonceTestCase {
315
        nonce: &'static str,
316
        expect_err: bool,
317
    }
318
319
    const TLS_NONCE_TEST_CASES: &[TlsNonceTestCase] = &[
320
        TlsNonceTestCase {
321
            nonce: "9fab40177c900aad9fc28cc3",
322
            expect_err: false,
323
        },
324
        TlsNonceTestCase {
325
            nonce: "9fab40177c900aad9fc28cc4",
326
            expect_err: false,
327
        },
328
        TlsNonceTestCase {
329
            nonce: "9fab40177c900aad9fc28cc2",
330
            expect_err: true,
331
        },
332
    ];
333
334
    macro_rules! test_tls_aead {
335
        ($name:ident, $alg:expr, $proto:expr, $key:expr) => {
336
            paste! {
337
                #[test]
338
                fn [<test_ $name _tls_aead_unsupported>]() {
339
                    assert!(TlsRecordSealingKey::new($alg, $proto, $key).is_err());
340
                    assert!(TlsRecordOpeningKey::new($alg, $proto, $key).is_err());
341
                }
342
            }
343
        };
344
        ($name:ident, $alg:expr, $proto:expr, $key:expr, $expect_tag_len:expr, $expect_nonce_len:expr) => {
345
            paste! {
346
                #[test]
347
                fn [<test_ $name>]() {
348
                    let mut sealing_key =
349
                        TlsRecordSealingKey::new($alg, $proto, $key).unwrap();
350
351
                    let opening_key =
352
                        TlsRecordOpeningKey::new($alg, $proto, $key).unwrap();
353
354
                    for case in TLS_NONCE_TEST_CASES {
355
                        let plaintext = from_hex("00112233445566778899aabbccddeeff").unwrap();
356
357
                        assert_eq!($alg, sealing_key.algorithm());
358
                        assert_eq!(*$expect_tag_len, $alg.tag_len());
359
                        assert_eq!(*$expect_nonce_len, $alg.nonce_len());
360
361
                        let mut in_out = Vec::from(plaintext.as_slice());
362
363
                        let nonce = from_hex(case.nonce).unwrap();
364
365
                        let nonce_bytes = nonce.as_slice();
366
367
                        let result = sealing_key.seal_in_place_append_tag(
368
                            Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
369
                            Aad::empty(),
370
                            &mut in_out,
371
                        );
372
373
                        match (result, case.expect_err) {
374
                            (Ok(()), true) => panic!("expected error for seal_in_place_append_tag"),
375
                            (Ok(()), false) => {}
376
                            (Err(_), true) => return,
377
                            (Err(e), false) => panic!("{e}"),
378
                        }
379
380
                        assert_ne!(plaintext, in_out[..plaintext.len()]);
381
382
                        // copy ciphertext with prefix, to exercise `open_within`
383
                        let mut offset_cipher_text = vec![ 1, 2, 3, 4 ];
384
                        offset_cipher_text.extend_from_slice(&in_out);
385
386
                        opening_key
387
                            .open_in_place(
388
                                Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
389
                                Aad::empty(),
390
                                &mut in_out,
391
                            )
392
                            .unwrap();
393
394
                        assert_eq!(plaintext, in_out[..plaintext.len()]);
395
396
                        opening_key
397
                            .open_within(
398
                                         Nonce::try_assume_unique_for_key(nonce_bytes).unwrap(),
399
                                         Aad::empty(),
400
                                         &mut offset_cipher_text,
401
                                         4..)
402
                            .unwrap();
403
                        assert_eq!(plaintext, offset_cipher_text[..plaintext.len()]);
404
                    }
405
                }
406
            }
407
        };
408
    }
409
410
    test_tls_aead!(
411
        aes_128_gcm_tls12,
412
        &AES_128_GCM,
413
        TlsProtocolId::TLS12,
414
        TEST_128_BIT_KEY,
415
        &16,
416
        &12
417
    );
418
    test_tls_aead!(
419
        aes_128_gcm_tls13,
420
        &AES_128_GCM,
421
        TlsProtocolId::TLS13,
422
        TEST_128_BIT_KEY,
423
        &16,
424
        &12
425
    );
426
    test_tls_aead!(
427
        aes_256_gcm_tls12,
428
        &AES_256_GCM,
429
        TlsProtocolId::TLS12,
430
        TEST_256_BIT_KEY,
431
        &16,
432
        &12
433
    );
434
    test_tls_aead!(
435
        aes_256_gcm_tls13,
436
        &AES_256_GCM,
437
        TlsProtocolId::TLS13,
438
        TEST_256_BIT_KEY,
439
        &16,
440
        &12
441
    );
442
    test_tls_aead!(
443
        chacha20_poly1305_tls12,
444
        &CHACHA20_POLY1305,
445
        TlsProtocolId::TLS12,
446
        TEST_256_BIT_KEY
447
    );
448
    test_tls_aead!(
449
        chacha20_poly1305_tls13,
450
        &CHACHA20_POLY1305,
451
        TlsProtocolId::TLS13,
452
        TEST_256_BIT_KEY
453
    );
454
}