Skip to content

Commit dc05687

Browse files
authored
Add AEAD, encrypt_sn, and key exchange validation to CryptoProvider
1 parent 4d3d477 commit dc05687

2 files changed

Lines changed: 353 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
* Add AEAD, encrypt_sn, and key exchange validation to CryptoProvider #68
34
* Add #[non_exhaustive] to public API enums likely to grow (breaking) #69
45
* feat: Add protocol_version() accessor to Dtls #59
56
* Bump all deps (possible with the current MSRV) #67

src/crypto/validation/mod.rs

Lines changed: 352 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
//! This module defines the validation rules for crypto providers used with dimpl,
44
//! based on the documented support in lib.rs.
55
6-
use super::{CryptoProvider, SupportedDtls12CipherSuite, SupportedKxGroup};
7-
use crate::buffer::Buf;
6+
use arrayvec::ArrayVec;
7+
8+
use super::{Aad, CryptoProvider, Nonce, SupportedDtls12CipherSuite, SupportedKxGroup};
9+
use crate::buffer::{Buf, TmpBuf};
810
use crate::dtls12::message::Dtls12CipherSuite;
9-
use crate::types::{HashAlgorithm, NamedGroup, SignatureAlgorithm};
11+
use crate::types::{Dtls13CipherSuite, HashAlgorithm, NamedGroup, SignatureAlgorithm};
1012
use crate::Error;
1113

1214
impl CryptoProvider {
@@ -83,6 +85,9 @@ impl CryptoProvider {
8385
self.validate_signature_verifier(&validated_hashes)?;
8486
self.validate_hmac_provider()?;
8587
self.validate_dtls13_cipher_suites()?;
88+
self.validate_dtls13_aead()?;
89+
self.validate_dtls13_encrypt_sn()?;
90+
self.validate_kx_exchange()?;
8691
Ok(())
8792
}
8893

@@ -291,6 +296,127 @@ impl CryptoProvider {
291296
Ok(())
292297
}
293298

299+
/// Validate AEAD encrypt/decrypt for each DTLS 1.3 cipher suite.
300+
fn validate_dtls13_aead(&self) -> Result<(), Error> {
301+
for cs in self.dtls13_cipher_suites {
302+
let suite = cs.suite();
303+
let tv = AEAD_TEST_VECTORS
304+
.iter()
305+
.find(|tv| tv.suite == suite)
306+
.ok_or_else(|| {
307+
Error::ConfigError(format!(
308+
"No AEAD test vector for DTLS 1.3 suite {:?}",
309+
suite
310+
))
311+
})?;
312+
313+
let nonce = Nonce(tv.nonce);
314+
let mut aad_vec = ArrayVec::new();
315+
// unwrap: AAD is at most 12 bytes, well within capacity 13
316+
aad_vec.try_extend_from_slice(tv.aad).unwrap();
317+
let aad = Aad(aad_vec);
318+
319+
// Encrypt
320+
let mut cipher = cs.create_cipher(tv.key).map_err(|e| {
321+
Error::ConfigError(format!("Failed to create cipher for {:?}: {}", suite, e))
322+
})?;
323+
let mut buf = Buf::new();
324+
buf.extend_from_slice(tv.plaintext);
325+
cipher.encrypt(&mut buf, aad.clone(), nonce).map_err(|e| {
326+
Error::ConfigError(format!("AEAD encrypt failed for {:?}: {}", suite, e))
327+
})?;
328+
if buf.as_ref() != tv.ciphertext_tag {
329+
return Err(Error::ConfigError(format!(
330+
"AEAD encrypt produced wrong output for {:?}",
331+
suite
332+
)));
333+
}
334+
335+
// Decrypt with a fresh cipher instance
336+
let mut cipher = cs.create_cipher(tv.key).map_err(|e| {
337+
Error::ConfigError(format!("Failed to create cipher for {:?}: {}", suite, e))
338+
})?;
339+
let mut ct = Vec::from(tv.ciphertext_tag);
340+
let mut tmp = TmpBuf::new(&mut ct);
341+
cipher.decrypt(&mut tmp, aad, nonce).map_err(|e| {
342+
Error::ConfigError(format!("AEAD decrypt failed for {:?}: {}", suite, e))
343+
})?;
344+
if tmp.as_ref() != tv.plaintext {
345+
return Err(Error::ConfigError(format!(
346+
"AEAD decrypt produced wrong output for {:?}",
347+
suite
348+
)));
349+
}
350+
}
351+
Ok(())
352+
}
353+
354+
/// Validate record number encryption for each DTLS 1.3 cipher suite.
355+
fn validate_dtls13_encrypt_sn(&self) -> Result<(), Error> {
356+
for cs in self.dtls13_cipher_suites {
357+
let suite = cs.suite();
358+
let tv = SN_TEST_VECTORS
359+
.iter()
360+
.find(|tv| tv.suite == suite)
361+
.ok_or_else(|| {
362+
Error::ConfigError(format!(
363+
"No encrypt_sn test vector for DTLS 1.3 suite {:?}",
364+
suite
365+
))
366+
})?;
367+
368+
let mask = cs.encrypt_sn(tv.sn_key, &tv.sample);
369+
if mask[..tv.check_len] != tv.expected_mask[..tv.check_len] {
370+
return Err(Error::ConfigError(format!(
371+
"encrypt_sn produced wrong mask for {:?}",
372+
suite
373+
)));
374+
}
375+
}
376+
Ok(())
377+
}
378+
379+
/// Validate key exchange round-trip for each supported group.
380+
fn validate_kx_exchange(&self) -> Result<(), Error> {
381+
for kx in self.supported_kx_groups() {
382+
let group = kx.name();
383+
384+
let alice = kx.start_exchange(Buf::new()).map_err(|e| {
385+
Error::ConfigError(format!("Key exchange start failed for {:?}: {}", group, e))
386+
})?;
387+
let bob = kx.start_exchange(Buf::new()).map_err(|e| {
388+
Error::ConfigError(format!("Key exchange start failed for {:?}: {}", group, e))
389+
})?;
390+
391+
let alice_pub = alice.pub_key().to_vec();
392+
let bob_pub = bob.pub_key().to_vec();
393+
394+
let mut alice_secret = Buf::new();
395+
alice.complete(&bob_pub, &mut alice_secret).map_err(|e| {
396+
Error::ConfigError(format!(
397+
"Key exchange complete failed for {:?}: {}",
398+
group, e
399+
))
400+
})?;
401+
402+
let mut bob_secret = Buf::new();
403+
bob.complete(&alice_pub, &mut bob_secret).map_err(|e| {
404+
Error::ConfigError(format!(
405+
"Key exchange complete failed for {:?}: {}",
406+
group, e
407+
))
408+
})?;
409+
410+
if alice_secret.as_ref() != bob_secret.as_ref() {
411+
return Err(Error::ConfigError(format!(
412+
"Key exchange produced different secrets for {:?}",
413+
group
414+
)));
415+
}
416+
}
417+
Ok(())
418+
}
419+
294420
/// Validate that HMAC provider supports required operations.
295421
///
296422
/// We require HMAC-SHA256 for DTLS cookie computation.
@@ -382,9 +508,185 @@ const VALIDATION_P256_SHA256_SIG: &[u8] = include_bytes!("p256_sha256_sig.der");
382508
const VALIDATION_P384_CERT: &[u8] = include_bytes!("p384_cert.der");
383509
const VALIDATION_P384_SHA384_SIG: &[u8] = include_bytes!("p384_sha384_sig.der");
384510

511+
// AEAD test vectors for DTLS 1.3 cipher suites
512+
struct AeadTestVector {
513+
suite: Dtls13CipherSuite,
514+
key: &'static [u8],
515+
nonce: [u8; 12],
516+
plaintext: &'static [u8],
517+
aad: &'static [u8],
518+
ciphertext_tag: &'static [u8], // ciphertext || tag
519+
}
520+
521+
// NIST SP 800-38D Test Case 3 plaintext (shared by AES-128 and AES-256)
522+
const GCM_PLAINTEXT: &[u8] = &[
523+
0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
524+
0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
525+
0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
526+
0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55,
527+
];
528+
529+
const AEAD_TEST_VECTORS: &[AeadTestVector] = &[
530+
// NIST SP 800-38D Test Case 3: AES-128-GCM, empty AAD
531+
AeadTestVector {
532+
suite: Dtls13CipherSuite::AES_128_GCM_SHA256,
533+
key: &[
534+
0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
535+
0x83, 0x08,
536+
],
537+
nonce: [
538+
0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88,
539+
],
540+
plaintext: GCM_PLAINTEXT,
541+
aad: &[],
542+
ciphertext_tag: &[
543+
// Ciphertext
544+
0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0,
545+
0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23,
546+
0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f,
547+
0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
548+
0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85, // Tag
549+
0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6,
550+
0xfa, 0xb4,
551+
],
552+
},
553+
// NIST SP 800-38D Test Case 15: AES-256-GCM, empty AAD
554+
AeadTestVector {
555+
suite: Dtls13CipherSuite::AES_256_GCM_SHA384,
556+
key: &[
557+
0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
558+
0x83, 0x08, 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94,
559+
0x67, 0x30, 0x83, 0x08,
560+
],
561+
nonce: [
562+
0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88,
563+
],
564+
plaintext: GCM_PLAINTEXT,
565+
aad: &[],
566+
ciphertext_tag: &[
567+
// Ciphertext
568+
0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84,
569+
0x42, 0x7d, 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, 0x75, 0x98, 0xa2, 0xbd,
570+
0x25, 0x55, 0xd1, 0xaa, 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, 0xa7, 0xb0,
571+
0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a,
572+
0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad, // Tag
573+
0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd, 0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3,
574+
0xcc, 0x6c,
575+
],
576+
},
577+
// RFC 7539 §2.8.2: ChaCha20-Poly1305
578+
AeadTestVector {
579+
suite: Dtls13CipherSuite::CHACHA20_POLY1305_SHA256,
580+
key: &[
581+
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
582+
0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
583+
0x9c, 0x9d, 0x9e, 0x9f,
584+
],
585+
nonce: [
586+
0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
587+
],
588+
plaintext: &[
589+
// "Ladies and Gentlemen of the class of '99: If I could offer you
590+
// only one tip for the future, sunscreen would be it."
591+
0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e,
592+
0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20,
593+
0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20,
594+
0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66,
595+
0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e,
596+
0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20,
597+
0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 0x63, 0x72,
598+
0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
599+
0x74, 0x2e,
600+
],
601+
aad: &[
602+
0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
603+
],
604+
ciphertext_tag: &[
605+
// Ciphertext
606+
0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef,
607+
0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7,
608+
0x36, 0xee, 0x62, 0xd6, 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa,
609+
0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29,
610+
0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77,
611+
0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, 0xfa, 0xb3, 0x24, 0xe4,
612+
0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, 0x3f, 0xf4,
613+
0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
614+
0x61, 0x16, // Tag
615+
0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
616+
0x06, 0x91,
617+
],
618+
},
619+
];
620+
621+
// Record number encryption (encrypt_sn) test vectors
622+
struct SnTestVector {
623+
suite: Dtls13CipherSuite,
624+
sn_key: &'static [u8],
625+
sample: [u8; 16],
626+
expected_mask: [u8; 16],
627+
check_len: usize, // number of leading bytes to verify
628+
}
629+
630+
const SN_TEST_VECTORS: &[SnTestVector] = &[
631+
// NIST FIPS 197 Appendix B: AES-128-ECB
632+
SnTestVector {
633+
suite: Dtls13CipherSuite::AES_128_GCM_SHA256,
634+
sn_key: &[
635+
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
636+
0x4f, 0x3c,
637+
],
638+
sample: [
639+
0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37,
640+
0x07, 0x34,
641+
],
642+
expected_mask: [
643+
0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a,
644+
0x0b, 0x32,
645+
],
646+
check_len: 16,
647+
},
648+
// NIST FIPS 197 Appendix C.3: AES-256-ECB
649+
SnTestVector {
650+
suite: Dtls13CipherSuite::AES_256_GCM_SHA384,
651+
sn_key: &[
652+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
653+
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
654+
0x1c, 0x1d, 0x1e, 0x1f,
655+
],
656+
sample: [
657+
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
658+
0xee, 0xff,
659+
],
660+
expected_mask: [
661+
0x8e, 0xa2, 0xb7, 0xca, 0x51, 0x67, 0x45, 0xbf, 0xea, 0xfc, 0x49, 0x90, 0x4b, 0x49,
662+
0x60, 0x89,
663+
],
664+
check_len: 16,
665+
},
666+
// RFC 9001 §A.5: ChaCha20 header protection
667+
// Only first 5 bytes are checked (aws-lc-rs zeros bytes 5-15, rust_crypto fills all 16)
668+
SnTestVector {
669+
suite: Dtls13CipherSuite::CHACHA20_POLY1305_SHA256,
670+
sn_key: &[
671+
0x25, 0xa2, 0x82, 0xb9, 0xe8, 0x2f, 0x06, 0xf2, 0x1f, 0x48, 0x89, 0x17, 0xa4, 0xfc,
672+
0x8f, 0x1b, 0x73, 0x57, 0x36, 0x85, 0x60, 0x85, 0x97, 0xd0, 0xef, 0xcb, 0x07, 0x6b,
673+
0x0a, 0xb7, 0xa7, 0xa4,
674+
],
675+
sample: [
676+
0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2, 0x5a,
677+
0x5b, 0xfb,
678+
],
679+
expected_mask: [
680+
0xae, 0xfe, 0xfe, 0x7d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
681+
0x00, 0x00,
682+
],
683+
check_len: 5,
684+
},
685+
];
686+
385687
#[cfg(test)]
386688
#[cfg(feature = "aws-lc-rs")]
387-
mod tests {
689+
mod tests_aws_lc_rs {
388690
use super::*;
389691
use crate::crypto::aws_lc_rs;
390692

@@ -427,3 +729,49 @@ mod tests {
427729
assert!(ecdsa_suites.contains(&Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384));
428730
}
429731
}
732+
733+
#[cfg(test)]
734+
#[cfg(feature = "rust-crypto")]
735+
mod tests_rust_crypto {
736+
use super::*;
737+
use crate::crypto::rust_crypto;
738+
739+
#[test]
740+
fn test_default_provider_validates() {
741+
let provider = rust_crypto::default_provider();
742+
assert!(provider.validate().is_ok());
743+
}
744+
745+
#[test]
746+
fn test_default_provider_has_cipher_suites() {
747+
let provider = rust_crypto::default_provider();
748+
let count = provider.supported_cipher_suites().count();
749+
assert_eq!(count, 2); // AES-128 and AES-256
750+
}
751+
752+
#[test]
753+
fn test_default_provider_has_kx_groups() {
754+
let provider = rust_crypto::default_provider();
755+
let count = provider.supported_kx_groups().count();
756+
assert_eq!(count, 3); // X25519, P-256, and P-384
757+
}
758+
759+
#[test]
760+
fn test_default_provider_has_ecdh() {
761+
let provider = rust_crypto::default_provider();
762+
assert!(provider.has_ecdh());
763+
}
764+
765+
#[test]
766+
fn test_supported_cipher_suites_for_signature_algorithm() {
767+
let provider = rust_crypto::default_provider();
768+
let ecdsa_suites: Vec<_> = provider
769+
.supported_cipher_suites_for_signature_algorithm(SignatureAlgorithm::ECDSA)
770+
.map(|cs| cs.suite())
771+
.collect();
772+
773+
assert_eq!(ecdsa_suites.len(), 2);
774+
assert!(ecdsa_suites.contains(&Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256));
775+
assert!(ecdsa_suites.contains(&Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384));
776+
}
777+
}

0 commit comments

Comments
 (0)