mirror of
https://github.com/telemt/telemt.git
synced 2026-06-12 22:11:43 +03:00
Generate Valid X25519MLKEM768 ServerHello key shares
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
179
Cargo.lock
generated
179
Cargo.lock
generated
@@ -8,7 +8,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
@@ -249,6 +249,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
@@ -397,7 +406,7 @@ version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -436,6 +445,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -452,6 +467,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
@@ -611,6 +632,16 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -620,6 +651,15 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctutils"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
|
||||
dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -672,7 +712,17 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"const-oid 0.9.6",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b"
|
||||
dependencies = [
|
||||
"const-oid 0.10.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -705,11 +755,21 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"crypto-common 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.6"
|
||||
@@ -753,7 +813,7 @@ version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"pkcs8 0.10.2",
|
||||
"signature",
|
||||
]
|
||||
|
||||
@@ -1135,7 +1195,7 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1183,6 +1243,17 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
|
||||
dependencies = [
|
||||
"ctutils",
|
||||
"typenum",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.10.0"
|
||||
@@ -1532,6 +1603,26 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kem"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
|
||||
dependencies = [
|
||||
"crypto-common 0.2.2",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
@@ -1634,7 +1725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1670,6 +1761,33 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ml-kem"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
"kem",
|
||||
"module-lattice",
|
||||
"pkcs8 0.11.0",
|
||||
"rand_core 0.10.1",
|
||||
"sha3",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "module-lattice"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
|
||||
dependencies = [
|
||||
"ctutils",
|
||||
"hybrid-array",
|
||||
"num-traits",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.15"
|
||||
@@ -1888,8 +2006,18 @@ version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
"der 0.7.10",
|
||||
"spki 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
|
||||
dependencies = [
|
||||
"der 0.8.0",
|
||||
"spki 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2280,7 +2408,7 @@ dependencies = [
|
||||
"aead",
|
||||
"ed25519",
|
||||
"generic-array",
|
||||
"pkcs8",
|
||||
"pkcs8 0.10.2",
|
||||
"ring",
|
||||
]
|
||||
|
||||
@@ -2567,7 +2695,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2578,7 +2706,17 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
|
||||
dependencies = [
|
||||
"digest 0.11.3",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2724,7 +2862,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
"der 0.7.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2816,6 +2964,7 @@ dependencies = [
|
||||
"libc",
|
||||
"lru",
|
||||
"md-5",
|
||||
"ml-kem",
|
||||
"nix",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
@@ -3259,7 +3408,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ crc32c = "0.6"
|
||||
zeroize = { version = "1.8", features = ["derive"] }
|
||||
subtle = "2.6"
|
||||
static_assertions = "1.1"
|
||||
ml-kem = { version = "0.3.2", default-features = false, features = ["alloc", "zeroize"] }
|
||||
|
||||
# Network
|
||||
socket2 = { version = "0.6", features = ["all"] }
|
||||
|
||||
@@ -1457,6 +1457,7 @@ fn emulated_server_hello_never_places_alpn_in_server_hello_extensions() {
|
||||
true,
|
||||
ClientHelloTlsVersion::Tls13,
|
||||
[0x13, 0x01],
|
||||
&vec![0x42; X25519MLKEM768_SERVER_KEY_SHARE_LEN],
|
||||
&rng,
|
||||
Some(b"h2".to_vec()),
|
||||
0,
|
||||
@@ -1545,6 +1546,7 @@ fn test_build_server_hello_with_cipher_always_uses_hybrid_key_share() {
|
||||
let secret = b"test secret";
|
||||
let client_digest = [0x42u8; 32];
|
||||
let session_id = vec![0xAA; 32];
|
||||
let key_share = vec![0x55u8; X25519MLKEM768_SERVER_KEY_SHARE_LEN];
|
||||
|
||||
let rng = crate::crypto::SecureRandom::new();
|
||||
let response = build_server_hello_with_cipher(
|
||||
@@ -1554,6 +1556,7 @@ fn test_build_server_hello_with_cipher_always_uses_hybrid_key_share() {
|
||||
2048,
|
||||
&rng,
|
||||
[0x13, 0x01],
|
||||
&key_share,
|
||||
None,
|
||||
0,
|
||||
);
|
||||
@@ -1643,6 +1646,22 @@ fn client_key_share_extension(entries: &[(u16, usize)]) -> Vec<u8> {
|
||||
extension
|
||||
}
|
||||
|
||||
fn client_key_share_extension_with_payloads(entries: &[(u16, &[u8])]) -> Vec<u8> {
|
||||
let mut shares = Vec::new();
|
||||
for (group, key_exchange) 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());
|
||||
shares.extend_from_slice(key_exchange);
|
||||
}
|
||||
|
||||
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<u8>)>,
|
||||
@@ -1868,6 +1887,64 @@ fn select_server_hello_key_share_group_prefers_hybrid_when_valid_share_is_offere
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_x25519mlkem768_server_key_share_accepts_tdesktop_canonical_share() {
|
||||
let key_share = client_key_share_extension(&[
|
||||
(
|
||||
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");
|
||||
let rng = crate::crypto::SecureRandom::new();
|
||||
|
||||
let server_key_share = build_x25519mlkem768_server_key_share(&ch, &rng)
|
||||
.expect("tdesktop-like canonical share must build a ServerHello share");
|
||||
|
||||
assert_eq!(server_key_share.len(), X25519MLKEM768_SERVER_KEY_SHARE_LEN);
|
||||
assert!(
|
||||
server_key_share[..MLKEM768_SERVER_CIPHERTEXT_LEN]
|
||||
.iter()
|
||||
.any(|byte| *byte != 0),
|
||||
"ML-KEM ciphertext must not be all zero"
|
||||
);
|
||||
assert!(
|
||||
server_key_share[MLKEM768_SERVER_CIPHERTEXT_LEN..]
|
||||
.iter()
|
||||
.any(|byte| *byte != 0),
|
||||
"X25519 server share must not be all zero"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_x25519mlkem768_server_key_share_rejects_noncanonical_mlkem_key() {
|
||||
let mut key_exchange = vec![0x42; X25519MLKEM768_CLIENT_KEY_SHARE_LEN];
|
||||
key_exchange[..3].copy_from_slice(&[0xff, 0xff, 0xff]);
|
||||
let key_share = client_key_share_extension_with_payloads(&[(
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
&key_exchange,
|
||||
)]);
|
||||
let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com");
|
||||
let rng = crate::crypto::SecureRandom::new();
|
||||
|
||||
assert!(build_x25519mlkem768_server_key_share(&ch, &rng).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_x25519mlkem768_server_key_share_rejects_all_zero_x25519_share() {
|
||||
let mut key_exchange = vec![0x42; X25519MLKEM768_CLIENT_KEY_SHARE_LEN];
|
||||
key_exchange[MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN..].fill(0);
|
||||
let key_share = client_key_share_extension_with_payloads(&[(
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
&key_exchange,
|
||||
)]);
|
||||
let ch = build_client_hello_with_exts(vec![(0x0033, key_share)], "example.com");
|
||||
let rng = crate::crypto::SecureRandom::new();
|
||||
|
||||
assert!(build_x25519mlkem768_server_key_share(&ch, &rng).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_server_hello_key_share_group_rejects_without_hybrid_share() {
|
||||
let key_share =
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
|
||||
use super::constants::*;
|
||||
use crate::crypto::{SecureRandom, sha256_hmac};
|
||||
use ml_kem::{B32, EncapsulationKey as MlKemEncapsulationKey, Key as MlKemKey, MlKem768};
|
||||
#[cfg(test)]
|
||||
use crate::error::ProxyError;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -123,6 +124,7 @@ pub(crate) const TLS_NAMED_GROUP_X25519MLKEM768: u16 = named_curve::X25519MLKEM7
|
||||
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_CLIENT_ENCAPSULATION_KEY_LEN: usize = 1184;
|
||||
const MLKEM768_SERVER_CIPHERTEXT_LEN: usize = 1088;
|
||||
|
||||
// ============= TLS Validation Result =============
|
||||
@@ -521,9 +523,15 @@ 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 (_scalar, public_key) = gen_x25519_key_pair(rng);
|
||||
public_key
|
||||
}
|
||||
|
||||
fn gen_x25519_key_pair(rng: &SecureRandom) -> ([u8; 32], [u8; 32]) {
|
||||
let mut scalar = [0u8; X25519_KEY_SHARE_LEN];
|
||||
scalar.copy_from_slice(&rng.bytes(X25519_KEY_SHARE_LEN));
|
||||
x25519(scalar, X25519_BASEPOINT_BYTES)
|
||||
rng.fill(&mut scalar);
|
||||
let public_key = x25519(scalar, X25519_BASEPOINT_BYTES);
|
||||
(scalar, public_key)
|
||||
}
|
||||
|
||||
/// Generate a fake X25519MLKEM768 ServerHello key_share payload.
|
||||
@@ -537,6 +545,49 @@ pub(crate) fn gen_fake_x25519mlkem768_server_key_share(rng: &SecureRandom) -> Ve
|
||||
key_share
|
||||
}
|
||||
|
||||
fn mlkem768_encapsulate_to_client(client_key: &[u8], rng: &SecureRandom) -> Option<Vec<u8>> {
|
||||
let key_bytes = MlKemKey::<MlKemEncapsulationKey<MlKem768>>::try_from(client_key).ok()?;
|
||||
let encapsulation_key = MlKemEncapsulationKey::<MlKem768>::new(&key_bytes).ok()?;
|
||||
let mut randomness = [0u8; 32];
|
||||
rng.fill(&mut randomness);
|
||||
let randomness = B32::try_from(randomness.as_slice()).ok()?;
|
||||
let (ciphertext, _shared_key) = encapsulation_key.encapsulate_deterministic(&randomness);
|
||||
let ciphertext = ciphertext.as_slice().to_vec();
|
||||
if ciphertext.len() == MLKEM768_SERVER_CIPHERTEXT_LEN {
|
||||
Some(ciphertext)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a valid X25519MLKEM768 ServerHello key_share for the authenticated ClientHello.
|
||||
pub(crate) fn build_x25519mlkem768_server_key_share(
|
||||
handshake: &[u8],
|
||||
rng: &SecureRandom,
|
||||
) -> Option<Vec<u8>> {
|
||||
let client_key_exchange = client_hello_key_share_group_entry(
|
||||
handshake,
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
X25519MLKEM768_CLIENT_KEY_SHARE_LEN,
|
||||
)?;
|
||||
let client_mlkem_key = client_key_exchange.get(..MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN)?;
|
||||
let client_x25519_key = client_key_exchange.get(MLKEM768_CLIENT_ENCAPSULATION_KEY_LEN..)?;
|
||||
let mlkem_ciphertext = mlkem768_encapsulate_to_client(client_mlkem_key, rng)?;
|
||||
|
||||
let mut client_x25519 = [0u8; X25519_KEY_SHARE_LEN];
|
||||
client_x25519.copy_from_slice(client_x25519_key);
|
||||
let (server_x25519_scalar, server_x25519_key) = gen_x25519_key_pair(rng);
|
||||
let x25519_shared = x25519(server_x25519_scalar, client_x25519);
|
||||
if bool::from(x25519_shared.ct_eq(&[0u8; X25519_KEY_SHARE_LEN])) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut key_share = Vec::with_capacity(X25519MLKEM768_SERVER_KEY_SHARE_LEN);
|
||||
key_share.extend_from_slice(&mlkem_ciphertext);
|
||||
key_share.extend_from_slice(&server_x25519_key);
|
||||
Some(key_share)
|
||||
}
|
||||
|
||||
/// Build TLS ServerHello response
|
||||
///
|
||||
/// This builds a complete TLS 1.3-like response including:
|
||||
@@ -561,6 +612,7 @@ pub fn build_server_hello(
|
||||
fake_cert_len,
|
||||
rng,
|
||||
cipher_suite::TLS_AES_128_GCM_SHA256,
|
||||
&gen_fake_x25519mlkem768_server_key_share(rng),
|
||||
alpn,
|
||||
new_session_tickets,
|
||||
)
|
||||
@@ -578,6 +630,7 @@ pub(crate) fn build_server_hello_with_cipher(
|
||||
fake_cert_len: usize,
|
||||
rng: &SecureRandom,
|
||||
selected_cipher_suite: [u8; 2],
|
||||
server_key_share: &[u8],
|
||||
alpn: Option<Vec<u8>>,
|
||||
new_session_tickets: u8,
|
||||
) -> Vec<u8> {
|
||||
@@ -586,10 +639,9 @@ pub(crate) fn build_server_hello_with_cipher(
|
||||
let fake_cert_len = fake_cert_len.clamp(MIN_APP_DATA, MAX_APP_DATA);
|
||||
|
||||
// Build ServerHello
|
||||
let key_share = gen_fake_x25519mlkem768_server_key_share(rng);
|
||||
let server_hello = ServerHelloBuilder::new(session_id.to_vec())
|
||||
.with_cipher_suite(selected_cipher_suite)
|
||||
.with_key_share(TLS_NAMED_GROUP_X25519MLKEM768, &key_share)
|
||||
.with_key_share(TLS_NAMED_GROUP_X25519MLKEM768, server_key_share)
|
||||
.with_tls13_version()
|
||||
.build_record();
|
||||
|
||||
@@ -1096,49 +1148,56 @@ fn client_hello_extensions_range(handshake: &[u8]) -> Option<(usize, usize)> {
|
||||
Some((pos, extensions_end))
|
||||
}
|
||||
|
||||
fn key_share_extension_has_group(
|
||||
data: &[u8],
|
||||
fn key_share_extension_group_entry<'a>(
|
||||
data: &'a [u8],
|
||||
group: u16,
|
||||
expected_key_exchange_len: usize,
|
||||
) -> bool {
|
||||
) -> Option<&'a [u8]> {
|
||||
if data.len() < 2 {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let shares_len = u16::from_be_bytes([data[0], data[1]]) as usize;
|
||||
if shares_len != data.len().saturating_sub(2) {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pos = 2usize;
|
||||
let shares_end = 2 + shares_len;
|
||||
let mut found_group = false;
|
||||
let mut found_group = None;
|
||||
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;
|
||||
return None;
|
||||
};
|
||||
if key_exchange_end > shares_end {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
if entry_group == group && key_exchange_len == expected_key_exchange_len {
|
||||
found_group = true;
|
||||
if entry_group == group {
|
||||
if key_exchange_len != expected_key_exchange_len || found_group.is_some() {
|
||||
return None;
|
||||
}
|
||||
found_group = Some(&data[pos..key_exchange_end]);
|
||||
}
|
||||
pos = key_exchange_end;
|
||||
}
|
||||
|
||||
found_group && pos == shares_end
|
||||
if pos == shares_end {
|
||||
found_group
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn client_hello_offers_key_share_group(
|
||||
handshake: &[u8],
|
||||
fn client_hello_key_share_group_entry<'a>(
|
||||
handshake: &'a [u8],
|
||||
group: u16,
|
||||
expected_key_exchange_len: usize,
|
||||
) -> bool {
|
||||
) -> Option<&'a [u8]> {
|
||||
let Some((mut pos, extensions_end)) = client_hello_extensions_range(handshake) else {
|
||||
return false;
|
||||
return None;
|
||||
};
|
||||
|
||||
while pos + 4 <= extensions_end {
|
||||
@@ -1146,14 +1205,14 @@ fn client_hello_offers_key_share_group(
|
||||
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;
|
||||
return None;
|
||||
};
|
||||
if ext_end > extensions_end {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
if ext_type == extension_type::KEY_SHARE {
|
||||
return key_share_extension_has_group(
|
||||
return key_share_extension_group_entry(
|
||||
&handshake[pos..ext_end],
|
||||
group,
|
||||
expected_key_exchange_len,
|
||||
@@ -1163,7 +1222,7 @@ fn client_hello_offers_key_share_group(
|
||||
pos = ext_end;
|
||||
}
|
||||
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
fn client_hello_offers_cipher_suite(
|
||||
@@ -1227,11 +1286,13 @@ pub(crate) fn select_server_hello_cipher_suite(
|
||||
/// Malformed or non-hybrid key_share structures fail closed so authenticated
|
||||
/// but DPI-inconsistent ClientHellos take the ordinary masking fallback path.
|
||||
pub(crate) fn select_server_hello_key_share_group(handshake: &[u8]) -> Option<u16> {
|
||||
if client_hello_offers_key_share_group(
|
||||
if client_hello_key_share_group_entry(
|
||||
handshake,
|
||||
TLS_NAMED_GROUP_X25519MLKEM768,
|
||||
X25519MLKEM768_CLIENT_KEY_SHARE_LEN,
|
||||
) {
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
Some(TLS_NAMED_GROUP_X25519MLKEM768)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1473,15 +1473,15 @@ where
|
||||
return HandshakeResult::BadClient { reader, writer };
|
||||
}
|
||||
|
||||
if tls::select_server_hello_key_share_group(handshake).is_none() {
|
||||
let Some(server_key_share) = tls::build_x25519mlkem768_server_key_share(handshake, rng) else {
|
||||
auth_probe_record_failure_in(shared, peer.ip(), Instant::now());
|
||||
maybe_apply_server_hello_delay(config).await;
|
||||
debug!(
|
||||
peer = %peer,
|
||||
"TLS handshake rejected: ClientHello did not offer a valid X25519MLKEM768 key_share"
|
||||
"TLS handshake rejected: ClientHello did not offer a usable X25519MLKEM768 key_share"
|
||||
);
|
||||
return HandshakeResult::BadClient { reader, writer };
|
||||
}
|
||||
};
|
||||
|
||||
let cached_entry = if config.censorship.tls_emulation {
|
||||
if let Some(cache) = tls_cache.as_ref() {
|
||||
@@ -1553,6 +1553,7 @@ where
|
||||
config.censorship.serverhello_compact,
|
||||
client_tls_version,
|
||||
selected_cipher_suite,
|
||||
&server_key_share,
|
||||
rng,
|
||||
selected_alpn.clone(),
|
||||
config.censorship.tls_new_session_tickets,
|
||||
@@ -1565,6 +1566,7 @@ where
|
||||
config.censorship.fake_cert_len,
|
||||
rng,
|
||||
selected_cipher_suite,
|
||||
&server_key_share,
|
||||
selected_alpn.clone(),
|
||||
config.censorship.tls_new_session_tickets,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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