1use 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#[allow(clippy::module_name_repetitions)]
12#[derive(Debug, PartialEq, Eq, Clone, Copy)]
13#[non_exhaustive]
14pub enum TlsProtocolId {
15 TLS12,
17
18 TLS13,
20}
21
22#[allow(clippy::module_name_repetitions)]
33pub struct TlsRecordSealingKey {
34 key: UnboundKey,
37 protocol: TlsProtocolId,
38}
39
40impl TlsRecordSealingKey {
41 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 #[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 #[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 #[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 #[inline]
188 #[must_use]
189 pub fn algorithm(&self) -> &'static Algorithm {
190 self.key.algorithm()
191 }
192
193 #[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#[allow(clippy::module_name_repetitions)]
220pub struct TlsRecordOpeningKey {
221 key: UnboundKey,
224 protocol: TlsProtocolId,
225}
226
227impl TlsRecordOpeningKey {
228 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 #[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 #[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 #[inline]
313 #[must_use]
314 pub fn algorithm(&self) -> &'static Algorithm {
315 self.key.algorithm()
316 }
317
318 #[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 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}