diff --git a/src/protocol/tests/tls_security_tests.rs b/src/protocol/tests/tls_security_tests.rs index f8b1b2b..c3c934d 100644 --- a/src/protocol/tests/tls_security_tests.rs +++ b/src/protocol/tests/tls_security_tests.rs @@ -1239,6 +1239,18 @@ fn test_gen_fake_x25519_key() { assert_ne!(key1, key2); } +#[test] +fn test_gen_fake_x25519mlkem768_server_key_share_shape() { + let rng = crate::crypto::SecureRandom::new(); + let key_share = gen_fake_x25519mlkem768_server_key_share(&rng); + + assert_eq!(key_share.len(), X25519MLKEM768_SERVER_KEY_SHARE_LEN); + assert!( + key_share.iter().any(|byte| *byte != 0), + "hybrid ServerHello key_share must not collapse to all-zero bytes" + ); +} + #[test] fn test_fake_x25519_key_is_nonzero_and_varies() { let rng = crate::crypto::SecureRandom::new(); @@ -1325,6 +1337,65 @@ fn server_hello_extension_types(record: &[u8]) -> Vec { out } +fn server_hello_key_share(record: &[u8]) -> Option<(u16, usize)> { + if record.len() < 9 || record[0] != TLS_RECORD_HANDSHAKE || record[5] != 0x02 { + return None; + } + + let record_len = u16::from_be_bytes([record[3], record[4]]) as usize; + if record.len() < 5 + record_len { + return None; + } + + let hs_len = u32::from_be_bytes([0, record[6], record[7], record[8]]) as usize; + let hs_start = 5; + let hs_end = hs_start + 4 + hs_len; + if hs_end > record.len() { + return None; + } + + let mut pos = hs_start + 4 + 2 + 32; + if pos >= hs_end { + return None; + } + let sid_len = record[pos] as usize; + pos += 1 + sid_len; + if pos + 2 + 1 + 2 > hs_end { + return None; + } + + pos += 2 + 1; + let ext_len = u16::from_be_bytes([record[pos], record[pos + 1]]) as usize; + pos += 2; + let ext_end = pos + ext_len; + if ext_end > hs_end { + return None; + } + + while pos + 4 <= ext_end { + let etype = u16::from_be_bytes([record[pos], record[pos + 1]]); + let elen = u16::from_be_bytes([record[pos + 2], record[pos + 3]]) as usize; + pos += 4; + if pos + elen > ext_end { + return None; + } + if etype == extension_type::KEY_SHARE { + if elen < 4 { + return None; + } + let group = u16::from_be_bytes([record[pos], record[pos + 1]]); + let key_exchange_len = u16::from_be_bytes([record[pos + 2], record[pos + 3]]) as usize; + if 4 + key_exchange_len != elen { + return None; + } + return Some((group, key_exchange_len)); + } + pos += elen; + } + + None +} + #[test] fn build_server_hello_never_places_alpn_in_server_hello_extensions() { let secret = b"alpn_sh_forbidden"; @@ -1386,6 +1457,7 @@ fn emulated_server_hello_never_places_alpn_in_server_hello_extensions() { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(b"h2".to_vec()), 0, @@ -1402,7 +1474,7 @@ fn test_tls_extension_builder() { let key = [0x42u8; 32]; let mut builder = TlsExtensionBuilder::new(); - builder.add_key_share(&key); + builder.add_key_share(TLS_NAMED_GROUP_X25519, &key); builder.add_supported_versions(0x0304); let result = builder.build(); @@ -1418,7 +1490,7 @@ fn test_server_hello_builder() { let key = [0x55u8; 32]; let builder = ServerHelloBuilder::new(session_id.clone()) - .with_x25519_key(&key) + .with_key_share(TLS_NAMED_GROUP_X25519, &key) .with_tls13_version(); let record = builder.build_record(); @@ -1452,6 +1524,39 @@ fn test_build_server_hello_structure() { let app_start = ccs_start + ccs_len; assert!(response.len() > app_start + 5); assert_eq!(response[app_start], TLS_RECORD_APPLICATION); + + assert_eq!( + server_hello_key_share(&response), + Some(( + TLS_NAMED_GROUP_X25519MLKEM768, + X25519MLKEM768_SERVER_KEY_SHARE_LEN + )) + ); +} + +#[test] +fn test_build_server_hello_with_cipher_can_keep_x25519_key_share() { + let secret = b"test secret"; + let client_digest = [0x42u8; 32]; + let session_id = vec![0xAA; 32]; + + let rng = crate::crypto::SecureRandom::new(); + let response = build_server_hello_with_cipher( + secret, + &client_digest, + &session_id, + 2048, + &rng, + [0x13, 0x01], + TLS_NAMED_GROUP_X25519, + None, + 0, + ); + + assert_eq!( + server_hello_key_share(&response), + Some((TLS_NAMED_GROUP_X25519, X25519_KEY_SHARE_LEN)) + ); } #[test] @@ -1477,7 +1582,7 @@ fn test_server_hello_extensions_length() { let key = [0x55u8; 32]; let builder = ServerHelloBuilder::new(session_id) - .with_x25519_key(&key) + .with_key_share(TLS_NAMED_GROUP_X25519, &key) .with_tls13_version(); let record = builder.build_record(); @@ -1513,6 +1618,23 @@ fn build_client_hello_with_exts(exts: Vec<(u16, Vec)>, host: &str) -> Vec Vec { + let mut shares = Vec::new(); + for (group, key_exchange_len) in entries { + assert!(*key_exchange_len <= u16::MAX as usize); + shares.extend_from_slice(&group.to_be_bytes()); + shares.extend_from_slice(&(*key_exchange_len as u16).to_be_bytes()); + let start = shares.len(); + shares.resize(start + *key_exchange_len, 0x42); + } + + assert!(shares.len() <= u16::MAX as usize); + let mut extension = Vec::new(); + extension.extend_from_slice(&(shares.len() as u16).to_be_bytes()); + extension.extend_from_slice(&shares); + extension +} + fn build_client_hello_with_ciphers_and_exts( cipher_suites: &[[u8; 2]], exts: Vec<(u16, Vec)>, @@ -1711,6 +1833,67 @@ fn select_server_hello_cipher_suite_keeps_preferred_for_malformed_clienthello() ); } +#[test] +fn select_server_hello_key_share_group_prefers_hybrid_when_valid_share_is_offered() { + let key_share = client_key_share_extension(&[ + (0x0a0a, 1), + ( + TLS_NAMED_GROUP_X25519MLKEM768, + X25519MLKEM768_CLIENT_KEY_SHARE_LEN, + ), + (TLS_NAMED_GROUP_X25519, X25519_KEY_SHARE_LEN), + ]); + let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com"); + + assert_eq!( + select_server_hello_key_share_group(&ch), + TLS_NAMED_GROUP_X25519MLKEM768 + ); +} + +#[test] +fn select_server_hello_key_share_group_falls_back_without_hybrid_share() { + let key_share = + client_key_share_extension(&[(TLS_NAMED_GROUP_X25519, X25519_KEY_SHARE_LEN)]); + let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com"); + + assert_eq!( + select_server_hello_key_share_group(&ch), + TLS_NAMED_GROUP_X25519 + ); +} + +#[test] +fn select_server_hello_key_share_group_falls_back_for_malformed_hybrid_len() { + let key_share = client_key_share_extension(&[( + TLS_NAMED_GROUP_X25519MLKEM768, + X25519MLKEM768_CLIENT_KEY_SHARE_LEN - 1, + )]); + let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com"); + + assert_eq!( + select_server_hello_key_share_group(&ch), + TLS_NAMED_GROUP_X25519 + ); +} + +#[test] +fn select_server_hello_key_share_group_falls_back_for_malformed_key_share_tail() { + let mut key_share = client_key_share_extension(&[( + TLS_NAMED_GROUP_X25519MLKEM768, + X25519MLKEM768_CLIENT_KEY_SHARE_LEN, + )]); + let shares_len = u16::from_be_bytes([key_share[0], key_share[1]]) + 1; + key_share[0..2].copy_from_slice(&shares_len.to_be_bytes()); + key_share.push(0); + let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com"); + + assert_eq!( + select_server_hello_key_share_group(&ch), + TLS_NAMED_GROUP_X25519 + ); +} + #[test] fn extract_sni_rejects_zero_length_host_name() { let mut sni_ext = Vec::new(); diff --git a/src/protocol/tls.rs b/src/protocol/tls.rs index 19cb3aa..518e2ba 100644 --- a/src/protocol/tls.rs +++ b/src/protocol/tls.rs @@ -109,11 +109,22 @@ mod cipher_suite { pub const TLS_CHACHA20_POLY1305_SHA256: [u8; 2] = [0x13, 0x03]; } -/// TLS Named Curves +/// TLS named groups used in KeyShare extensions. mod named_curve { pub const X25519: u16 = 0x001d; + pub const X25519MLKEM768: u16 = 0x11ec; } +/// TLS X25519 named group. +pub(crate) const TLS_NAMED_GROUP_X25519: u16 = named_curve::X25519; +/// TLS X25519MLKEM768 named group. +pub(crate) const TLS_NAMED_GROUP_X25519MLKEM768: u16 = named_curve::X25519MLKEM768; + +const X25519_KEY_SHARE_LEN: usize = 32; +const X25519MLKEM768_CLIENT_KEY_SHARE_LEN: usize = 1216; +const X25519MLKEM768_SERVER_KEY_SHARE_LEN: usize = 1120; +const MLKEM768_SERVER_CIPHERTEXT_LEN: usize = 1088; + // ============= TLS Validation Result ============= /// Result of validating TLS handshake @@ -144,26 +155,28 @@ impl TlsExtensionBuilder { } } - /// Add Key Share extension with X25519 key - fn add_key_share(&mut self, public_key: &[u8; 32]) -> &mut Self { + /// Add KeyShare extension with the selected named group. + fn add_key_share(&mut self, group: u16, key_exchange: &[u8]) -> &mut Self { + let Ok(key_exchange_len) = u16::try_from(key_exchange.len()) else { + return self; + }; + let Some(entry_len) = key_exchange.len().checked_add(4) else { + return self; + }; + let Ok(entry_len) = u16::try_from(entry_len) else { + return self; + }; + // Extension type: key_share (0x0033) self.extensions .extend_from_slice(&extension_type::KEY_SHARE.to_be_bytes()); - // Key share entry: curve (2) + key_len (2) + key (32) = 36 bytes - // Extension data length - let entry_len: u16 = 2 + 2 + 32; // curve + length + key + // ServerHello key_share data is exactly one KeyShareEntry. self.extensions.extend_from_slice(&entry_len.to_be_bytes()); - - // Named curve: x25519 + self.extensions.extend_from_slice(&group.to_be_bytes()); self.extensions - .extend_from_slice(&named_curve::X25519.to_be_bytes()); - - // Key length - self.extensions.extend_from_slice(&(32u16).to_be_bytes()); - - // Key data - self.extensions.extend_from_slice(public_key); + .extend_from_slice(&key_exchange_len.to_be_bytes()); + self.extensions.extend_from_slice(key_exchange); self } @@ -232,8 +245,8 @@ impl ServerHelloBuilder { } } - fn with_x25519_key(mut self, key: &[u8; 32]) -> Self { - self.extensions.add_key_share(key); + fn with_key_share(mut self, group: u16, key_exchange: &[u8]) -> Self { + self.extensions.add_key_share(group, key_exchange); self } @@ -508,11 +521,22 @@ fn validate_tls_handshake_at_time_with_boot_cap( /// Uses RFC 7748 X25519 scalar multiplication over the canonical basepoint, /// yielding distribution-consistent public keys for anti-fingerprinting. pub fn gen_fake_x25519_key(rng: &SecureRandom) -> [u8; 32] { - let mut scalar = [0u8; 32]; - scalar.copy_from_slice(&rng.bytes(32)); + let mut scalar = [0u8; X25519_KEY_SHARE_LEN]; + scalar.copy_from_slice(&rng.bytes(X25519_KEY_SHARE_LEN)); x25519(scalar, X25519_BASEPOINT_BYTES) } +/// Generate a fake X25519MLKEM768 ServerHello key_share payload. +pub(crate) fn gen_fake_x25519mlkem768_server_key_share(rng: &SecureRandom) -> Vec { + let mut key_share = vec![0u8; X25519MLKEM768_SERVER_KEY_SHARE_LEN]; + // FakeTLS never derives TLS traffic secrets from this payload; only the + // externally visible named group and vector lengths are protocol-facing. + rng.fill(&mut key_share[..MLKEM768_SERVER_CIPHERTEXT_LEN]); + let x25519_key = gen_fake_x25519_key(rng); + key_share[MLKEM768_SERVER_CIPHERTEXT_LEN..].copy_from_slice(&x25519_key); + key_share +} + /// Build TLS ServerHello response /// /// This builds a complete TLS 1.3-like response including: @@ -537,6 +561,7 @@ pub fn build_server_hello( fake_cert_len, rng, cipher_suite::TLS_AES_128_GCM_SHA256, + TLS_NAMED_GROUP_X25519MLKEM768, alpn, new_session_tickets, ) @@ -554,20 +579,30 @@ pub(crate) fn build_server_hello_with_cipher( fake_cert_len: usize, rng: &SecureRandom, selected_cipher_suite: [u8; 2], + selected_key_share_group: u16, alpn: Option>, new_session_tickets: u8, ) -> Vec { const MIN_APP_DATA: usize = 64; const MAX_APP_DATA: usize = MAX_TLS_CIPHERTEXT_SIZE; let fake_cert_len = fake_cert_len.clamp(MIN_APP_DATA, MAX_APP_DATA); - let x25519_key = gen_fake_x25519_key(rng); // Build ServerHello - let server_hello = ServerHelloBuilder::new(session_id.to_vec()) - .with_cipher_suite(selected_cipher_suite) - .with_x25519_key(&x25519_key) - .with_tls13_version() - .build_record(); + let server_hello = if selected_key_share_group == TLS_NAMED_GROUP_X25519MLKEM768 { + let key_share = gen_fake_x25519mlkem768_server_key_share(rng); + ServerHelloBuilder::new(session_id.to_vec()) + .with_cipher_suite(selected_cipher_suite) + .with_key_share(TLS_NAMED_GROUP_X25519MLKEM768, &key_share) + .with_tls13_version() + .build_record() + } else { + let key_share = gen_fake_x25519_key(rng); + ServerHelloBuilder::new(session_id.to_vec()) + .with_cipher_suite(selected_cipher_suite) + .with_key_share(TLS_NAMED_GROUP_X25519, &key_share) + .with_tls13_version() + .build_record() + }; // Build Change Cipher Spec record let change_cipher_spec = [ @@ -1003,6 +1038,145 @@ fn client_hello_cipher_suites_range(handshake: &[u8]) -> Option<(usize, usize)> Some((pos, cipher_end)) } +fn client_hello_extensions_range(handshake: &[u8]) -> Option<(usize, usize)> { + if handshake.len() < 5 || handshake[0] != TLS_RECORD_HANDSHAKE { + return None; + } + + let record_len = u16::from_be_bytes([handshake[3], handshake[4]]) as usize; + let record_end = 5usize.checked_add(record_len)?; + if record_end > handshake.len() { + return None; + } + + let mut pos = 5; + if handshake.get(pos) != Some(&0x01) { + return None; + } + pos += 1; + + if pos + 3 > record_end { + return None; + } + let handshake_len = ((handshake[pos] as usize) << 16) + | ((handshake[pos + 1] as usize) << 8) + | handshake[pos + 2] as usize; + pos += 3; + let handshake_end = pos.checked_add(handshake_len)?; + if handshake_end > record_end { + return None; + } + + if pos + 2 + 32 > handshake_end { + return None; + } + pos += 2 + 32; + + let session_id_len = *handshake.get(pos)? as usize; + pos = pos.checked_add(1)?.checked_add(session_id_len)?; + if pos + 2 > handshake_end { + return None; + } + + let cipher_len = u16::from_be_bytes([handshake[pos], handshake[pos + 1]]) as usize; + if cipher_len == 0 || cipher_len % 2 != 0 { + return None; + } + pos += 2; + pos = pos.checked_add(cipher_len)?; + if pos + 1 > handshake_end { + return None; + } + + let compression_len = *handshake.get(pos)? as usize; + pos = pos.checked_add(1)?.checked_add(compression_len)?; + if pos == handshake_end { + return Some((handshake_end, handshake_end)); + } + if pos + 2 > handshake_end { + return None; + } + + let extensions_len = u16::from_be_bytes([handshake[pos], handshake[pos + 1]]) as usize; + pos += 2; + let extensions_end = pos.checked_add(extensions_len)?; + if extensions_end > handshake_end { + return None; + } + + Some((pos, extensions_end)) +} + +fn key_share_extension_has_group( + data: &[u8], + group: u16, + expected_key_exchange_len: usize, +) -> bool { + if data.len() < 2 { + return false; + } + + let shares_len = u16::from_be_bytes([data[0], data[1]]) as usize; + if shares_len != data.len().saturating_sub(2) { + return false; + } + + let mut pos = 2usize; + let shares_end = 2 + shares_len; + let mut found_group = false; + while pos + 4 <= shares_end { + let entry_group = u16::from_be_bytes([data[pos], data[pos + 1]]); + let key_exchange_len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize; + pos += 4; + let Some(key_exchange_end) = pos.checked_add(key_exchange_len) else { + return false; + }; + if key_exchange_end > shares_end { + return false; + } + if entry_group == group && key_exchange_len == expected_key_exchange_len { + found_group = true; + } + pos = key_exchange_end; + } + + found_group && pos == shares_end +} + +fn client_hello_offers_key_share_group( + handshake: &[u8], + group: u16, + expected_key_exchange_len: usize, +) -> bool { + let Some((mut pos, extensions_end)) = client_hello_extensions_range(handshake) else { + return false; + }; + + while pos + 4 <= extensions_end { + let ext_type = u16::from_be_bytes([handshake[pos], handshake[pos + 1]]); + let ext_len = u16::from_be_bytes([handshake[pos + 2], handshake[pos + 3]]) as usize; + pos += 4; + let Some(ext_end) = pos.checked_add(ext_len) else { + return false; + }; + if ext_end > extensions_end { + return false; + } + + if ext_type == extension_type::KEY_SHARE { + return key_share_extension_has_group( + &handshake[pos..ext_end], + group, + expected_key_exchange_len, + ); + } + + pos = ext_end; + } + + false +} + fn client_hello_offers_cipher_suite( handshake: &[u8], range: (usize, usize), @@ -1056,6 +1230,22 @@ pub(crate) fn select_server_hello_cipher_suite(handshake: &[u8], preferred: [u8; preferred } +/// Select the ServerHello key_share named group from the authenticated ClientHello. +/// +/// Malformed key_share structures intentionally keep the legacy X25519 response +/// to avoid breaking older clients that do not advertise the hybrid group. +pub(crate) fn select_server_hello_key_share_group(handshake: &[u8]) -> u16 { + if client_hello_offers_key_share_group( + handshake, + TLS_NAMED_GROUP_X25519MLKEM768, + X25519MLKEM768_CLIENT_KEY_SHARE_LEN, + ) { + TLS_NAMED_GROUP_X25519MLKEM768 + } else { + TLS_NAMED_GROUP_X25519 + } +} + /// Check if bytes look like a TLS ClientHello pub fn is_tls_handshake(first_bytes: &[u8]) -> bool { if first_bytes.len() < 3 { diff --git a/src/proxy/handshake.rs b/src/proxy/handshake.rs index a765575..1d6b4e6 100644 --- a/src/proxy/handshake.rs +++ b/src/proxy/handshake.rs @@ -1502,6 +1502,7 @@ where replay_checker.add_tls_digest(digest_half); let validation_session_id_slice = &validation_session_id[..validation_session_id_len]; + let selected_key_share_group = tls::select_server_hello_key_share_group(handshake); let response = if let Some((cached_entry, use_full_cert_payload)) = cached { let preferred_cipher_suite = if cached_entry.server_hello_template.cipher_suite == [0, 0] { @@ -1520,6 +1521,7 @@ where config.censorship.serverhello_compact, client_tls_version, selected_cipher_suite, + selected_key_share_group, rng, selected_alpn.clone(), config.censorship.tls_new_session_tickets, @@ -1533,6 +1535,7 @@ where config.censorship.fake_cert_len, rng, selected_cipher_suite, + selected_key_share_group, selected_alpn.clone(), config.censorship.tls_new_session_tickets, ) diff --git a/src/tls_front/emulator.rs b/src/tls_front/emulator.rs index 5bf307c..b0b1205 100644 --- a/src/tls_front/emulator.rs +++ b/src/tls_front/emulator.rs @@ -6,7 +6,9 @@ use crate::protocol::constants::{ TLS_RECORD_HANDSHAKE, TLS_VERSION, }; use crate::protocol::tls::{ - ClientHelloTlsVersion, TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key, + ClientHelloTlsVersion, TLS_DIGEST_LEN, TLS_DIGEST_POS, TLS_NAMED_GROUP_X25519, + TLS_NAMED_GROUP_X25519MLKEM768, gen_fake_x25519_key, + gen_fake_x25519mlkem768_server_key_share, }; use crate::tls_front::types::{ CachedTlsData, ParsedCertificateInfo, TlsExtension, TlsProfileSource, @@ -196,19 +198,43 @@ fn push_supported_versions_extension(extensions: &mut Vec) { extensions.extend_from_slice(&0x0304u16.to_be_bytes()); } -fn push_key_share_extension(extensions: &mut Vec, rng: &SecureRandom) { - let key = gen_fake_x25519_key(rng); +fn push_key_share_entry(extensions: &mut Vec, group: u16, key_exchange: &[u8]) { + let Ok(key_exchange_len) = u16::try_from(key_exchange.len()) else { + return; + }; + let Some(entry_len) = key_exchange.len().checked_add(4) else { + return; + }; + let Ok(entry_len) = u16::try_from(entry_len) else { + return; + }; + extensions.extend_from_slice(&EXT_KEY_SHARE.to_be_bytes()); - extensions.extend_from_slice(&(2 + 2 + 32u16).to_be_bytes()); - extensions.extend_from_slice(&0x001du16.to_be_bytes()); - extensions.extend_from_slice(&(32u16).to_be_bytes()); - extensions.extend_from_slice(&key); + extensions.extend_from_slice(&entry_len.to_be_bytes()); + extensions.extend_from_slice(&group.to_be_bytes()); + extensions.extend_from_slice(&key_exchange_len.to_be_bytes()); + extensions.extend_from_slice(key_exchange); +} + +fn push_key_share_extension( + extensions: &mut Vec, + rng: &SecureRandom, + selected_key_share_group: u16, +) { + if selected_key_share_group == TLS_NAMED_GROUP_X25519MLKEM768 { + let key = gen_fake_x25519mlkem768_server_key_share(rng); + push_key_share_entry(extensions, TLS_NAMED_GROUP_X25519MLKEM768, &key); + } else { + let key = gen_fake_x25519_key(rng); + push_key_share_entry(extensions, TLS_NAMED_GROUP_X25519, &key); + } } fn replay_profiled_server_hello_extension( ext: &TlsExtension, extensions: &mut Vec, rng: &SecureRandom, + selected_key_share_group: u16, saw_supported_versions: &mut bool, saw_key_share: &mut bool, ) { @@ -218,7 +244,7 @@ fn replay_profiled_server_hello_extension( *saw_supported_versions = true; } EXT_KEY_SHARE if !*saw_key_share => { - push_key_share_extension(extensions, rng); + push_key_share_extension(extensions, rng, selected_key_share_group); *saw_key_share = true; } EXT_ALPN => {} @@ -226,7 +252,11 @@ fn replay_profiled_server_hello_extension( } } -fn build_profiled_server_hello_extensions(cached: &CachedTlsData, rng: &SecureRandom) -> Vec { +fn build_profiled_server_hello_extensions( + cached: &CachedTlsData, + rng: &SecureRandom, + selected_key_share_group: u16, +) -> Vec { let capacity = cached .server_hello_template .extensions @@ -243,13 +273,14 @@ fn build_profiled_server_hello_extensions(cached: &CachedTlsData, rng: &SecureRa ext, &mut extensions, rng, + selected_key_share_group, &mut saw_supported_versions, &mut saw_key_share, ); } if !saw_key_share { - push_key_share_extension(&mut extensions, rng); + push_key_share_extension(&mut extensions, rng, selected_key_share_group); } if !saw_supported_versions { push_supported_versions_extension(&mut extensions); @@ -268,12 +299,13 @@ pub fn build_emulated_server_hello( serverhello_compact: bool, client_tls_version: ClientHelloTlsVersion, selected_cipher_suite: [u8; 2], + selected_key_share_group: u16, rng: &SecureRandom, alpn: Option>, new_session_tickets: u8, ) -> Vec { // --- ServerHello --- - let extensions = build_profiled_server_hello_extensions(cached, rng); + let extensions = build_profiled_server_hello_extensions(cached, rng, selected_key_share_group); let extensions_len = extensions.len() as u16; let body_len = 2 + 32 + 1 + session_id.len() + 2 + 1 + 2 + extensions.len(); @@ -458,7 +490,7 @@ mod tests { use crate::protocol::constants::{ TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, }; - use crate::protocol::tls::ClientHelloTlsVersion; + use crate::protocol::tls::{ClientHelloTlsVersion, TLS_NAMED_GROUP_X25519MLKEM768}; fn first_app_data_payload(response: &[u8]) -> &[u8] { let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize; @@ -540,6 +572,7 @@ mod tests { true, ClientHelloTlsVersion::Tls12, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -569,6 +602,7 @@ mod tests { true, ClientHelloTlsVersion::Tls13, [0x13, 0x03], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -604,6 +638,7 @@ mod tests { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(b"h2".to_vec()), 0, @@ -628,6 +663,7 @@ mod tests { true, ClientHelloTlsVersion::Tls12, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -663,6 +699,7 @@ mod tests { true, ClientHelloTlsVersion::Tls12, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -704,6 +741,7 @@ mod tests { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -737,6 +775,7 @@ mod tests { false, ClientHelloTlsVersion::Tls12, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(b"h2".to_vec()), 0, @@ -769,6 +808,7 @@ mod tests { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, diff --git a/src/tls_front/tests/emulator_profile_fidelity_security_tests.rs b/src/tls_front/tests/emulator_profile_fidelity_security_tests.rs index 3fbba07..c87ee89 100644 --- a/src/tls_front/tests/emulator_profile_fidelity_security_tests.rs +++ b/src/tls_front/tests/emulator_profile_fidelity_security_tests.rs @@ -4,7 +4,7 @@ use crate::crypto::SecureRandom; use crate::protocol::constants::{ TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, }; -use crate::protocol::tls::ClientHelloTlsVersion; +use crate::protocol::tls::{ClientHelloTlsVersion, TLS_NAMED_GROUP_X25519MLKEM768}; use crate::tls_front::emulator::build_emulated_server_hello; use crate::tls_front::types::{ CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsProfileSource, @@ -66,6 +66,7 @@ fn emulated_server_hello_keeps_single_change_cipher_spec_for_client_compatibilit true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -91,6 +92,7 @@ fn emulated_server_hello_does_not_emit_profile_ticket_tail_when_disabled() { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 0, @@ -114,6 +116,7 @@ fn emulated_server_hello_uses_profile_ticket_lengths_when_enabled() { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, None, 2, diff --git a/src/tls_front/tests/emulator_security_tests.rs b/src/tls_front/tests/emulator_security_tests.rs index c3ef96d..ea8159d 100644 --- a/src/tls_front/tests/emulator_security_tests.rs +++ b/src/tls_front/tests/emulator_security_tests.rs @@ -4,7 +4,7 @@ use crate::crypto::SecureRandom; use crate::protocol::constants::{ TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, }; -use crate::protocol::tls::ClientHelloTlsVersion; +use crate::protocol::tls::{ClientHelloTlsVersion, TLS_NAMED_GROUP_X25519MLKEM768}; use crate::tls_front::emulator::build_emulated_server_hello; use crate::tls_front::types::{ CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsProfileSource, @@ -59,6 +59,7 @@ fn emulated_server_hello_ignores_oversized_alpn_when_marker_would_not_fit() { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(oversized_alpn), 0, @@ -98,6 +99,7 @@ fn emulated_server_hello_keeps_alpn_marker_out_of_appdata() { true, ClientHelloTlsVersion::Tls13, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(b"h2".to_vec()), 0, @@ -129,6 +131,7 @@ fn emulated_server_hello_prefers_cert_payload_over_alpn_marker() { true, ClientHelloTlsVersion::Tls12, [0x13, 0x01], + TLS_NAMED_GROUP_X25519MLKEM768, &rng, Some(b"h2".to_vec()), 0,