mirror of
https://github.com/telemt/telemt.git
synced 2026-06-25 04:11:10 +03:00
Generate Valid X25519MLKEM768 ServerHello key shares
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
+28
-10
@@ -7,7 +7,6 @@ use crate::protocol::constants::{
|
||||
};
|
||||
use crate::protocol::tls::{
|
||||
ClientHelloTlsVersion, TLS_DIGEST_LEN, TLS_DIGEST_POS, TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
gen_fake_x25519mlkem768_server_key_share,
|
||||
};
|
||||
use crate::tls_front::types::{
|
||||
CachedTlsData, ParsedCertificateInfo, TlsExtension, TlsProfileSource,
|
||||
@@ -209,15 +208,18 @@ fn push_key_share_entry(extensions: &mut Vec<u8>, group: u16, key_exchange: &[u8
|
||||
extensions.extend_from_slice(key_exchange);
|
||||
}
|
||||
|
||||
fn push_key_share_extension(extensions: &mut Vec<u8>, rng: &SecureRandom) {
|
||||
let key = gen_fake_x25519mlkem768_server_key_share(rng);
|
||||
push_key_share_entry(extensions, TLS_NAMED_GROUP_X25519MLKEM768, &key);
|
||||
fn push_key_share_extension(extensions: &mut Vec<u8>, server_key_share: &[u8]) {
|
||||
push_key_share_entry(
|
||||
extensions,
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
server_key_share,
|
||||
);
|
||||
}
|
||||
|
||||
fn replay_profiled_server_hello_extension(
|
||||
ext: &TlsExtension,
|
||||
extensions: &mut Vec<u8>,
|
||||
rng: &SecureRandom,
|
||||
server_key_share: &[u8],
|
||||
saw_supported_versions: &mut bool,
|
||||
saw_key_share: &mut bool,
|
||||
) {
|
||||
@@ -227,7 +229,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, server_key_share);
|
||||
*saw_key_share = true;
|
||||
}
|
||||
EXT_ALPN => {}
|
||||
@@ -235,7 +237,10 @@ fn replay_profiled_server_hello_extension(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profiled_server_hello_extensions(cached: &CachedTlsData, rng: &SecureRandom) -> Vec<u8> {
|
||||
fn build_profiled_server_hello_extensions(
|
||||
cached: &CachedTlsData,
|
||||
server_key_share: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let capacity = cached
|
||||
.server_hello_template
|
||||
.extensions
|
||||
@@ -251,14 +256,14 @@ fn build_profiled_server_hello_extensions(cached: &CachedTlsData, rng: &SecureRa
|
||||
replay_profiled_server_hello_extension(
|
||||
ext,
|
||||
&mut extensions,
|
||||
rng,
|
||||
server_key_share,
|
||||
&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, server_key_share);
|
||||
}
|
||||
if !saw_supported_versions {
|
||||
push_supported_versions_extension(&mut extensions);
|
||||
@@ -277,12 +282,13 @@ pub fn build_emulated_server_hello(
|
||||
serverhello_compact: bool,
|
||||
client_tls_version: ClientHelloTlsVersion,
|
||||
selected_cipher_suite: [u8; 2],
|
||||
server_key_share: &[u8],
|
||||
rng: &SecureRandom,
|
||||
alpn: Option<Vec<u8>>,
|
||||
new_session_tickets: u8,
|
||||
) -> Vec<u8> {
|
||||
// --- ServerHello ---
|
||||
let extensions = build_profiled_server_hello_extensions(cached, rng);
|
||||
let extensions = build_profiled_server_hello_extensions(cached, server_key_share);
|
||||
let extensions_len = extensions.len() as u16;
|
||||
|
||||
let body_len = 2 + 32 + 1 + session_id.len() + 2 + 1 + 2 + extensions.len();
|
||||
@@ -534,6 +540,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_server_key_share() -> Vec<u8> {
|
||||
vec![0x42; 1120]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_emulated_server_hello_uses_cached_cert_payload() {
|
||||
let cert_msg = vec![0x0b, 0x00, 0x00, 0x05, 0x00, 0xaa, 0xbb, 0xcc, 0xdd];
|
||||
@@ -551,6 +561,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls12,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -580,6 +591,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x03],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -615,6 +627,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
Some(b"h2".to_vec()),
|
||||
0,
|
||||
@@ -639,6 +652,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls12,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -674,6 +688,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls12,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -715,6 +730,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -748,6 +764,7 @@ mod tests {
|
||||
false,
|
||||
ClientHelloTlsVersion::Tls12,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
Some(b"h2".to_vec()),
|
||||
0,
|
||||
@@ -780,6 +797,7 @@ mod tests {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
|
||||
+102
-17
@@ -9,6 +9,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use ml_kem::{
|
||||
DecapsulationKey as MlKemDecapsulationKey, KeyExport, MlKem768, Seed as MlKemSeed,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
@@ -33,6 +36,7 @@ use crate::network::dns_overrides::resolve_socket_addr;
|
||||
use crate::protocol::constants::{
|
||||
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
|
||||
};
|
||||
use crate::protocol::tls::{TLS_NAMED_GROUP_X25519, TLS_NAMED_GROUP_X25519MLKEM768};
|
||||
use crate::tls_front::types::{
|
||||
ParsedCertificateInfo, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsExtension,
|
||||
TlsFetchResult, TlsProfileSource,
|
||||
@@ -40,6 +44,10 @@ use crate::tls_front::types::{
|
||||
use crate::transport::UpstreamStream;
|
||||
use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder};
|
||||
|
||||
#[cfg(test)]
|
||||
const X25519_KEY_SHARE_LEN: usize = 32;
|
||||
const MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN: usize = 1184;
|
||||
|
||||
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
||||
#[derive(Debug)]
|
||||
struct NoVerify;
|
||||
@@ -393,8 +401,13 @@ fn profile_cipher_suites(profile: TlsFetchProfile) -> &'static [u16] {
|
||||
}
|
||||
|
||||
fn profile_groups(profile: TlsFetchProfile) -> &'static [u16] {
|
||||
const MODERN: &[u16] = &[0x001d, 0x0017, 0x0018]; // x25519, secp256r1, secp384r1
|
||||
const COMPAT: &[u16] = &[0x001d, 0x0017];
|
||||
const MODERN: &[u16] = &[
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
TLS_NAMED_GROUP_X25519,
|
||||
0x0017,
|
||||
0x0018,
|
||||
];
|
||||
const COMPAT: &[u16] = &[TLS_NAMED_GROUP_X25519, 0x0017];
|
||||
const LEGACY: &[u16] = &[0x0017];
|
||||
|
||||
match profile {
|
||||
@@ -475,6 +488,50 @@ fn grease_value(rng: &SecureRandom, deterministic: bool, seed: &str) -> u16 {
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_mlkem768_client_encapsulation_key(
|
||||
rng: &SecureRandom,
|
||||
deterministic: bool,
|
||||
seed: &str,
|
||||
) -> Option<Vec<u8>> {
|
||||
let seed_bytes = if deterministic {
|
||||
deterministic_bytes(seed, 64)
|
||||
} else {
|
||||
rng.bytes(64)
|
||||
};
|
||||
let seed = MlKemSeed::try_from(seed_bytes.as_slice()).ok()?;
|
||||
let decapsulation_key = MlKemDecapsulationKey::<MlKem768>::from_seed(seed);
|
||||
let encapsulation_key = decapsulation_key.encapsulation_key().to_bytes();
|
||||
let bytes = encapsulation_key.as_slice();
|
||||
if bytes.len() == MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN {
|
||||
Some(bytes.to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_x25519mlkem768_client_key_share(
|
||||
rng: &SecureRandom,
|
||||
deterministic: bool,
|
||||
seed: &str,
|
||||
) -> Option<Vec<u8>> {
|
||||
let mlkem_key = gen_mlkem768_client_encapsulation_key(
|
||||
rng,
|
||||
deterministic,
|
||||
&format!("{seed}:mlkem768"),
|
||||
)?;
|
||||
let x25519_key = gen_key_share(rng, deterministic, &format!("{seed}:x25519"));
|
||||
let mut key_share = Vec::with_capacity(MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN + x25519_key.len());
|
||||
key_share.extend_from_slice(&mlkem_key);
|
||||
key_share.extend_from_slice(&x25519_key);
|
||||
Some(key_share)
|
||||
}
|
||||
|
||||
fn push_client_key_share_entry(keyshare: &mut Vec<u8>, group: u16, key: &[u8]) {
|
||||
keyshare.extend_from_slice(&group.to_be_bytes());
|
||||
keyshare.extend_from_slice(&(key.len() as u16).to_be_bytes());
|
||||
keyshare.extend_from_slice(key);
|
||||
}
|
||||
|
||||
fn build_client_hello(
|
||||
sni: &str,
|
||||
rng: &SecureRandom,
|
||||
@@ -597,16 +654,21 @@ fn build_client_hello(
|
||||
push_extension(0x002d, &[0x01, 0x01]);
|
||||
}
|
||||
|
||||
// key_share (x25519)
|
||||
let key = gen_key_share(
|
||||
rng,
|
||||
deterministic,
|
||||
&format!("tls-fetch-keyshare:{sni}:{}", profile.as_str()),
|
||||
);
|
||||
let mut keyshare = Vec::with_capacity(4 + key.len());
|
||||
keyshare.extend_from_slice(&0x001du16.to_be_bytes());
|
||||
keyshare.extend_from_slice(&(key.len() as u16).to_be_bytes());
|
||||
keyshare.extend_from_slice(&key);
|
||||
// key_share
|
||||
let key_share_seed = format!("tls-fetch-keyshare:{sni}:{}", profile.as_str());
|
||||
let mut keyshare = Vec::new();
|
||||
if matches!(
|
||||
profile,
|
||||
TlsFetchProfile::ModernChromeLike | TlsFetchProfile::ModernFirefoxLike
|
||||
) {
|
||||
if let Some(key) =
|
||||
gen_x25519mlkem768_client_key_share(rng, deterministic, &key_share_seed)
|
||||
{
|
||||
push_client_key_share_entry(&mut keyshare, TLS_NAMED_GROUP_X25519MLKEM768, &key);
|
||||
}
|
||||
}
|
||||
let key = gen_key_share(rng, deterministic, &key_share_seed);
|
||||
push_client_key_share_entry(&mut keyshare, TLS_NAMED_GROUP_X25519, &key);
|
||||
let mut keyshare_ext = Vec::with_capacity(2 + keyshare.len());
|
||||
keyshare_ext.extend_from_slice(&(keyshare.len() as u16).to_be_bytes());
|
||||
keyshare_ext.extend_from_slice(&keyshare);
|
||||
@@ -1788,11 +1850,34 @@ mod tests {
|
||||
key_share_data.len() - 2,
|
||||
"key_share list length mismatch"
|
||||
);
|
||||
let group = u16::from_be_bytes([key_share_data[2], key_share_data[3]]);
|
||||
let key_len = u16::from_be_bytes([key_share_data[4], key_share_data[5]]) as usize;
|
||||
let key = &key_share_data[6..6 + key_len];
|
||||
assert_eq!(group, 0x001d, "key_share group must be x25519");
|
||||
assert_eq!(key_len, 32, "x25519 key length must be 32");
|
||||
let mut pos = 2usize;
|
||||
let hybrid_group = u16::from_be_bytes([key_share_data[pos], key_share_data[pos + 1]]);
|
||||
let hybrid_len =
|
||||
u16::from_be_bytes([key_share_data[pos + 2], key_share_data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
let hybrid_key = &key_share_data[pos..pos + hybrid_len];
|
||||
pos += hybrid_len;
|
||||
assert_eq!(
|
||||
hybrid_group, TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
"first key_share group must be X25519MLKEM768"
|
||||
);
|
||||
assert_eq!(
|
||||
hybrid_len,
|
||||
MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN + X25519_KEY_SHARE_LEN,
|
||||
"hybrid key length must match X25519MLKEM768"
|
||||
);
|
||||
assert!(
|
||||
hybrid_key.iter().any(|b| *b != 0),
|
||||
"hybrid key must not be all zero"
|
||||
);
|
||||
|
||||
let group = u16::from_be_bytes([key_share_data[pos], key_share_data[pos + 1]]);
|
||||
let key_len =
|
||||
u16::from_be_bytes([key_share_data[pos + 2], key_share_data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
let key = &key_share_data[pos..pos + key_len];
|
||||
assert_eq!(group, TLS_NAMED_GROUP_X25519, "second key_share group must be x25519");
|
||||
assert_eq!(key_len, X25519_KEY_SHARE_LEN, "x25519 key length must be 32");
|
||||
assert!(
|
||||
key.iter().any(|b| *b != 0),
|
||||
"x25519 key must not be all zero"
|
||||
|
||||
@@ -52,6 +52,10 @@ fn record_lengths_by_type(response: &[u8], wanted_type: u8) -> Vec<usize> {
|
||||
out
|
||||
}
|
||||
|
||||
fn test_server_key_share() -> Vec<u8> {
|
||||
vec![0x42; 1120]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emulated_server_hello_keeps_single_change_cipher_spec_for_client_compatibility() {
|
||||
let cached = make_cached();
|
||||
@@ -66,6 +70,7 @@ fn emulated_server_hello_keeps_single_change_cipher_spec_for_client_compatibilit
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -91,6 +96,7 @@ fn emulated_server_hello_does_not_emit_profile_ticket_tail_when_disabled() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
0,
|
||||
@@ -114,6 +120,7 @@ fn emulated_server_hello_uses_profile_ticket_lengths_when_enabled() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
None,
|
||||
2,
|
||||
|
||||
@@ -44,6 +44,10 @@ fn first_app_data_payload(response: &[u8]) -> &[u8] {
|
||||
&response[app_start + 5..app_start + 5 + app_len]
|
||||
}
|
||||
|
||||
fn test_server_key_share() -> Vec<u8> {
|
||||
vec![0x42; 1120]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emulated_server_hello_ignores_oversized_alpn_when_marker_would_not_fit() {
|
||||
let cached = make_cached(None);
|
||||
@@ -59,6 +63,7 @@ fn emulated_server_hello_ignores_oversized_alpn_when_marker_would_not_fit() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
Some(oversized_alpn),
|
||||
0,
|
||||
@@ -98,6 +103,7 @@ fn emulated_server_hello_keeps_alpn_marker_out_of_appdata() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
Some(b"h2".to_vec()),
|
||||
0,
|
||||
@@ -129,6 +135,7 @@ fn emulated_server_hello_prefers_cert_payload_over_alpn_marker() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls12,
|
||||
[0x13, 0x01],
|
||||
&test_server_key_share(),
|
||||
&rng,
|
||||
Some(b"h2".to_vec()),
|
||||
0,
|
||||
|
||||
Reference in New Issue
Block a user