mirror of https://github.com/ospab/ostp.git
security: Kerckhoffs's principle — all secrets derived from access key via HKDF
Applied Kerckhoffs's principle: the protocol's security and obfuscation
now depend SOLELY on the access key. An adversary who reverse-engineers
the binary cannot build a DPI filter without knowing the key.
Changes:
- Replaced hardcoded salt string ('-ostp-psk-salt') with HKDF-SHA256.
The salt is now derived from the key hash itself — no protocol-specific
strings remain in the binary.
- Unified all secret derivation into derive_all_secrets() which produces
PSK, obfuscation key, and handshake padding range from a single HKDF
invocation.
- Handshake padding range is now key-derived: different access keys
produce different size distributions (min: 16-79, max: +48..+175).
A universal size-based filter is impossible without the key.
- HKDF-SHA256 (RFC 5869) implemented inline using existing hmac+sha2
dependencies — no new crate required.
What remains identifiable in the binary:
- 'Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s' — standard Noise pattern
string, shared with many other projects, NOT OSTP-specific.
- Generic HMAC/SHA-256/ChaCha20-Poly1305 code — standard crypto
primitives used by millions of applications.
This commit is contained in:
parent
0418e5728c
commit
a4d8da2460
|
|
@ -592,23 +592,24 @@ impl Bridge {
|
||||||
handshake_payload.extend_from_slice(&session_id.to_be_bytes());
|
handshake_payload.extend_from_slice(&session_id.to_be_bytes());
|
||||||
handshake_payload.extend_from_slice(&self.access_key);
|
handshake_payload.extend_from_slice(&self.access_key);
|
||||||
|
|
||||||
let obf_key = ostp_core::crypto::derive_obfuscation_key(&self.access_key);
|
let secrets = ostp_core::crypto::derive_all_secrets(&self.access_key);
|
||||||
let psk = ostp_core::crypto::derive_psk(&self.access_key);
|
|
||||||
|
|
||||||
let mut machine = ProtocolMachine::new(ProtocolConfig {
|
let mut machine = ProtocolMachine::new(ProtocolConfig {
|
||||||
role: NoiseRole::Initiator,
|
role: NoiseRole::Initiator,
|
||||||
psk,
|
psk: secrets.psk,
|
||||||
session_id,
|
session_id,
|
||||||
handshake_payload,
|
handshake_payload,
|
||||||
max_padding: 1280, // Safe MTU size to avoid UDP fragmentation on Windows/PPPoE
|
max_padding: 1280, // Safe MTU size to avoid UDP fragmentation on Windows/PPPoE
|
||||||
padding_strategy: PaddingStrategy::Profile(self.profile),
|
padding_strategy: PaddingStrategy::Profile(self.profile),
|
||||||
obfuscation_key: obf_key,
|
obfuscation_key: secrets.obfuscation_key,
|
||||||
max_reorder: 16384, // Max gap between expected and received nonce
|
max_reorder: 16384, // Max gap between expected and received nonce
|
||||||
max_reorder_buffer: 8192, // Max buffered out-of-order frames
|
max_reorder_buffer: 8192, // Max buffered out-of-order frames
|
||||||
ack_delay_ms: 5,
|
ack_delay_ms: 5,
|
||||||
rto_ms: 100,
|
rto_ms: 100,
|
||||||
max_retries: 8,
|
max_retries: 8,
|
||||||
max_sent_history: 32768, // Reduced: gap recovery handles unrecoverable frames
|
max_sent_history: 32768, // Reduced: gap recovery handles unrecoverable frames
|
||||||
|
handshake_pad_min: secrets.handshake_pad_min,
|
||||||
|
handshake_pad_max: secrets.handshake_pad_max,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let addr = self.local_bind_addr.parse::<std::net::SocketAddr>().map_err(|e| anyhow::anyhow!("invalid bind addr: {}", e))?;
|
let addr = self.local_bind_addr.parse::<std::net::SocketAddr>().map_err(|e| anyhow::anyhow!("invalid bind addr: {}", e))?;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,7 @@ pub mod obfuscation;
|
||||||
pub use aead::SessionCipher;
|
pub use aead::SessionCipher;
|
||||||
pub use kex::{HybridSharedSecret, HybridKex};
|
pub use kex::{HybridSharedSecret, HybridKex};
|
||||||
pub use noise::{NoiseRole, NoiseSession};
|
pub use noise::{NoiseRole, NoiseSession};
|
||||||
pub use obfuscation::{deobfuscate_header_inplace, deobfuscate_packet_inplace, obfuscate_packet_inplace, derive_obfuscation_key, derive_psk};
|
pub use obfuscation::{
|
||||||
|
deobfuscate_header_inplace, deobfuscate_packet_inplace, obfuscate_packet_inplace,
|
||||||
|
derive_obfuscation_key, derive_psk, derive_all_secrets, DerivedSecrets,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,135 @@
|
||||||
|
// =============================================================================
|
||||||
|
// OSTP Key Derivation — Kerckhoffs's Principle
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// All protocol secrets (PSK, obfuscation key, padding parameters) are derived
|
||||||
|
// exclusively from the access key using HKDF-SHA256. There are NO hardcoded
|
||||||
|
// salt strings, protocol identifiers, or magic constants in this module.
|
||||||
|
//
|
||||||
|
// An adversary who reverse-engineers the binary sees only generic HMAC/SHA-256
|
||||||
|
// operations with no protocol-specific strings to search for. Building a DPI
|
||||||
|
// filter requires knowledge of the access key.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
pub fn derive_obfuscation_key(access_key: &[u8]) -> [u8; 8] {
|
// ── HKDF-SHA256 (RFC 5869) ──────────────────────────────────────────────────
|
||||||
|
// Implemented inline to avoid adding a dependency. Uses only hmac + sha2.
|
||||||
|
|
||||||
|
/// HKDF-Extract: PRK = HMAC-SHA256(salt, IKM)
|
||||||
|
fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; 32] {
|
||||||
|
let mut mac = HmacSha256::new_from_slice(salt).expect("HMAC accepts any key length");
|
||||||
|
mac.update(ikm);
|
||||||
|
let result = mac.finalize().into_bytes();
|
||||||
|
let mut prk = [0u8; 32];
|
||||||
|
prk.copy_from_slice(&result);
|
||||||
|
prk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HKDF-Expand: OKM = T(1) || T(2) || ... truncated to `len` bytes.
|
||||||
|
/// T(i) = HMAC-SHA256(PRK, T(i-1) || info || i)
|
||||||
|
fn hkdf_expand(prk: &[u8; 32], info: &[u8], len: usize) -> Vec<u8> {
|
||||||
|
let mut okm = Vec::with_capacity(len);
|
||||||
|
let mut t = Vec::new();
|
||||||
|
let mut counter = 1u8;
|
||||||
|
while okm.len() < len {
|
||||||
|
let mut mac = HmacSha256::new_from_slice(prk).expect("HMAC accepts any key length");
|
||||||
|
mac.update(&t);
|
||||||
|
mac.update(info);
|
||||||
|
mac.update(&[counter]);
|
||||||
|
let block = mac.finalize().into_bytes();
|
||||||
|
t = block.to_vec();
|
||||||
|
okm.extend_from_slice(&t[..t.len().min(len - okm.len() + t.len()).min(t.len())]);
|
||||||
|
counter = counter.wrapping_add(1);
|
||||||
|
}
|
||||||
|
okm.truncate(len);
|
||||||
|
okm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive all protocol secrets from a single access key.
|
||||||
|
/// Returns (obfuscation_key, psk, handshake_pad_min, handshake_pad_max).
|
||||||
|
///
|
||||||
|
/// The derivation uses the access key as both IKM and salt material,
|
||||||
|
/// split into two halves. No fixed strings are used — the access key
|
||||||
|
/// alone determines all derived values.
|
||||||
|
pub struct DerivedSecrets {
|
||||||
|
pub obfuscation_key: [u8; 8],
|
||||||
|
pub psk: [u8; 32],
|
||||||
|
pub handshake_pad_min: usize,
|
||||||
|
pub handshake_pad_max: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derive_all_secrets(access_key: &[u8]) -> DerivedSecrets {
|
||||||
|
// Split the key hash into two halves for salt/info separation.
|
||||||
|
// This avoids using any hardcoded strings while still providing
|
||||||
|
// domain separation between the derived values.
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
let mut hasher = Sha256::new();
|
let key_hash = sha2::Sha256::digest(access_key);
|
||||||
hasher.update(access_key);
|
let salt = &key_hash[..16];
|
||||||
let result = hasher.finalize();
|
let info_base = &key_hash[16..];
|
||||||
let mut key = [0u8; 8];
|
|
||||||
key.copy_from_slice(&result[0..8]);
|
// Extract PRK from access key using its own hash as salt
|
||||||
key
|
let prk = hkdf_extract(salt, access_key);
|
||||||
|
|
||||||
|
// Derive obfuscation key (8 bytes) — info = key_hash[16..] || 0x01
|
||||||
|
let mut obf_info = info_base.to_vec();
|
||||||
|
obf_info.push(0x01);
|
||||||
|
let obf_bytes = hkdf_expand(&prk, &obf_info, 8);
|
||||||
|
let mut obfuscation_key = [0u8; 8];
|
||||||
|
obfuscation_key.copy_from_slice(&obf_bytes);
|
||||||
|
|
||||||
|
// Derive PSK (32 bytes) — info = key_hash[16..] || 0x02
|
||||||
|
let mut psk_info = info_base.to_vec();
|
||||||
|
psk_info.push(0x02);
|
||||||
|
let psk_bytes = hkdf_expand(&prk, &psk_info, 32);
|
||||||
|
let mut psk = [0u8; 32];
|
||||||
|
psk.copy_from_slice(&psk_bytes);
|
||||||
|
|
||||||
|
// Derive handshake padding range (2 bytes) — info = key_hash[16..] || 0x03
|
||||||
|
// This makes different access keys produce different handshake sizes,
|
||||||
|
// preventing DPI from building a universal size-based filter.
|
||||||
|
let mut pad_info = info_base.to_vec();
|
||||||
|
pad_info.push(0x03);
|
||||||
|
let pad_bytes = hkdf_expand(&prk, &pad_info, 2);
|
||||||
|
// Map to range: min ∈ [16..80], max ∈ [min+48..min+176]
|
||||||
|
let pad_min = 16 + (pad_bytes[0] as usize % 64); // 16-79
|
||||||
|
let pad_max = pad_min + 48 + (pad_bytes[1] as usize % 128); // +48..+175
|
||||||
|
|
||||||
|
DerivedSecrets {
|
||||||
|
obfuscation_key,
|
||||||
|
psk,
|
||||||
|
handshake_pad_min: pad_min,
|
||||||
|
handshake_pad_max: pad_max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Legacy API (delegates to derive_all_secrets) ─────────────────────────────
|
||||||
|
|
||||||
|
pub fn derive_obfuscation_key(access_key: &[u8]) -> [u8; 8] {
|
||||||
|
derive_all_secrets(access_key).obfuscation_key
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive_psk(access_key: &[u8]) -> [u8; 32] {
|
pub fn derive_psk(access_key: &[u8]) -> [u8; 32] {
|
||||||
use sha2::Digest;
|
derive_all_secrets(access_key).psk
|
||||||
let mut hasher = Sha256::new();
|
}
|
||||||
hasher.update(access_key);
|
|
||||||
hasher.update(b"-ostp-psk-salt");
|
// ── Wire Obfuscation ─────────────────────────────────────────────────────────
|
||||||
let result = hasher.finalize();
|
|
||||||
let mut psk = [0u8; 32];
|
/// Derives a per-packet mask from the payload following the header.
|
||||||
psk.copy_from_slice(&result);
|
/// Used by both data and handshake packets so every mask is unique.
|
||||||
psk
|
fn derive_payload_mask(key: &[u8; 8], payload: &[u8]) -> [u8; 32] {
|
||||||
|
let mut sample = [0u8; 32];
|
||||||
|
let take_len = payload.len().min(32);
|
||||||
|
sample[..take_len].copy_from_slice(&payload[..take_len]);
|
||||||
|
|
||||||
|
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
|
||||||
|
mac.update(&sample);
|
||||||
|
let result = mac.finalize().into_bytes();
|
||||||
|
let mut mask = [0u8; 32];
|
||||||
|
mask.copy_from_slice(&result);
|
||||||
|
mask
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wire layout for DATA packets:
|
/// Wire layout for DATA packets:
|
||||||
|
|
@ -50,9 +159,6 @@ pub fn obfuscate_packet_inplace(raw: &mut [u8], key: &[u8; 8], is_handshake: boo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_handshake && raw.len() > 6 {
|
} else if is_handshake && raw.len() > 6 {
|
||||||
// Handshake: sample the Noise payload (starts at byte 6) to derive
|
|
||||||
// a per-packet mask. The Noise payload begins with a random ephemeral
|
|
||||||
// key, so the mask will be unique for every handshake.
|
|
||||||
let payload = &raw[6..];
|
let payload = &raw[6..];
|
||||||
let mask = derive_payload_mask(key, payload);
|
let mask = derive_payload_mask(key, payload);
|
||||||
|
|
||||||
|
|
@ -74,7 +180,6 @@ pub fn deobfuscate_header_inplace(
|
||||||
header[i] ^= mask[i];
|
header[i] ^= mask[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handshake deobfuscation is not done via this function — use deobfuscate_packet_inplace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deobfuscate_packet_inplace(raw: &mut [u8], key: &[u8; 8], is_handshake: bool) {
|
pub fn deobfuscate_packet_inplace(raw: &mut [u8], key: &[u8; 8], is_handshake: bool) {
|
||||||
|
|
@ -85,9 +190,6 @@ pub fn deobfuscate_packet_inplace(raw: &mut [u8], key: &[u8; 8], is_handshake: b
|
||||||
deobfuscate_header_inplace(&mut header, ciphertext, key, is_handshake);
|
deobfuscate_header_inplace(&mut header, ciphertext, key, is_handshake);
|
||||||
header_slice.copy_from_slice(&header);
|
header_slice.copy_from_slice(&header);
|
||||||
} else if is_handshake && raw.len() > 6 {
|
} else if is_handshake && raw.len() > 6 {
|
||||||
// Handshake: the payload (Noise data) starts at byte 6,
|
|
||||||
// and was NOT masked — only the header [0..6] was.
|
|
||||||
// Derive the same mask from the unmasked payload.
|
|
||||||
let payload = &raw[6..];
|
let payload = &raw[6..];
|
||||||
let mask = derive_payload_mask(key, payload);
|
let mask = derive_payload_mask(key, payload);
|
||||||
|
|
||||||
|
|
@ -96,18 +198,3 @@ pub fn deobfuscate_packet_inplace(raw: &mut [u8], key: &[u8; 8], is_handshake: b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derives a 32-byte mask from a payload sample using HMAC-SHA256.
|
|
||||||
/// Used by both data and handshake obfuscation to produce per-packet unique masks.
|
|
||||||
fn derive_payload_mask(key: &[u8; 8], payload: &[u8]) -> [u8; 32] {
|
|
||||||
let mut sample = [0u8; 32];
|
|
||||||
let take_len = payload.len().min(32);
|
|
||||||
sample[..take_len].copy_from_slice(&payload[..take_len]);
|
|
||||||
|
|
||||||
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
|
|
||||||
mac.update(&sample);
|
|
||||||
let result = mac.finalize().into_bytes();
|
|
||||||
let mut mask = [0u8; 32];
|
|
||||||
mask.copy_from_slice(&result);
|
|
||||||
mask
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ pub struct ProtocolConfig {
|
||||||
pub rto_ms: u64,
|
pub rto_ms: u64,
|
||||||
pub max_retries: u8,
|
pub max_retries: u8,
|
||||||
pub max_sent_history: usize,
|
pub max_sent_history: usize,
|
||||||
|
/// Key-derived handshake padding range (Kerckhoffs's principle).
|
||||||
|
/// Different access keys produce different handshake packet sizes.
|
||||||
|
pub handshake_pad_min: usize,
|
||||||
|
pub handshake_pad_max: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -89,6 +93,9 @@ pub struct ProtocolMachine {
|
||||||
/// evicted from sent_history, this timer detects the deadlock and skips
|
/// evicted from sent_history, this timer detects the deadlock and skips
|
||||||
/// the gap to restore liveness.
|
/// the gap to restore liveness.
|
||||||
last_recv_advance: Instant,
|
last_recv_advance: Instant,
|
||||||
|
/// Key-derived handshake padding range
|
||||||
|
handshake_pad_min: usize,
|
||||||
|
handshake_pad_max: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -131,6 +138,8 @@ impl ProtocolMachine {
|
||||||
last_ack_sent: Instant::now(),
|
last_ack_sent: Instant::now(),
|
||||||
last_nack_sent: Instant::now() - Duration::from_secs(1),
|
last_nack_sent: Instant::now() - Duration::from_secs(1),
|
||||||
last_recv_advance: Instant::now(),
|
last_recv_advance: Instant::now(),
|
||||||
|
handshake_pad_min: config.handshake_pad_min.max(8),
|
||||||
|
handshake_pad_max: config.handshake_pad_max.max(config.handshake_pad_min + 16),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,11 +399,12 @@ impl ProtocolMachine {
|
||||||
|
|
||||||
fn wrap_datagram_handshake(&self, noise_payload: &[u8]) -> Result<Bytes, ProtocolError> {
|
fn wrap_datagram_handshake(&self, noise_payload: &[u8]) -> Result<Bytes, ProtocolError> {
|
||||||
// Anti-DPI: add random padding after the Noise payload to prevent
|
// Anti-DPI: add random padding after the Noise payload to prevent
|
||||||
// size fingerprinting. Without this, every handshake is exactly 52 bytes
|
// size fingerprinting. The padding range is derived from the access key
|
||||||
// which is trivially detectable by TSPU/DPI systems.
|
// (Kerckhoffs's principle), so different keys produce different size
|
||||||
|
// distributions — no universal filter can be built from the binary alone.
|
||||||
//
|
//
|
||||||
// Wire format: [session_id:4][noise_len:2][noise_payload:N][random_padding:32-128]
|
// Wire format: [session_id:4][noise_len:2][noise_payload:N][random_padding]
|
||||||
let pad_len: usize = rand::thread_rng().gen_range(32..=128);
|
let pad_len: usize = rand::thread_rng().gen_range(self.handshake_pad_min..=self.handshake_pad_max);
|
||||||
let mut pad = vec![0u8; pad_len];
|
let mut pad = vec![0u8; pad_len];
|
||||||
rand::thread_rng().fill(&mut pad[..]);
|
rand::thread_rng().fill(&mut pad[..]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,22 +154,23 @@ impl Dispatcher {
|
||||||
let keys_snapshot: Vec<String> = self.access_keys.read().unwrap().keys().cloned().collect();
|
let keys_snapshot: Vec<String> = self.access_keys.read().unwrap().keys().cloned().collect();
|
||||||
|
|
||||||
for candidate_key in keys_snapshot {
|
for candidate_key in keys_snapshot {
|
||||||
let obf_key = ostp_core::crypto::derive_obfuscation_key(candidate_key.as_bytes());
|
let secrets = ostp_core::crypto::derive_all_secrets(candidate_key.as_bytes());
|
||||||
let psk = ostp_core::crypto::derive_psk(candidate_key.as_bytes());
|
|
||||||
|
|
||||||
// Decode the session_id using this key's obfuscation
|
// Decode the session_id using this key's obfuscation
|
||||||
// The handshake mask is derived from the Noise payload at bytes [6..],
|
// The handshake mask is derived from the Noise payload at bytes [6..],
|
||||||
// so we must deobfuscate the full packet, not just the header.
|
// so we must deobfuscate the full packet, not just the header.
|
||||||
if packet.len() < 7 { continue; }
|
if packet.len() < 7 { continue; }
|
||||||
let mut trial = packet.to_vec();
|
let mut trial = packet.to_vec();
|
||||||
ostp_core::crypto::deobfuscate_packet_inplace(&mut trial, &obf_key, true);
|
ostp_core::crypto::deobfuscate_packet_inplace(&mut trial, &secrets.obfuscation_key, true);
|
||||||
let candidate_session_id = u32::from_be_bytes([trial[0], trial[1], trial[2], trial[3]]);
|
let candidate_session_id = u32::from_be_bytes([trial[0], trial[1], trial[2], trial[3]]);
|
||||||
|
|
||||||
let mut cfg = self.machine_config.clone();
|
let mut cfg = self.machine_config.clone();
|
||||||
cfg.session_id = candidate_session_id;
|
cfg.session_id = candidate_session_id;
|
||||||
cfg.psk = psk;
|
cfg.psk = secrets.psk;
|
||||||
cfg.handshake_payload = vec![];
|
cfg.handshake_payload = vec![];
|
||||||
cfg.obfuscation_key = obf_key;
|
cfg.obfuscation_key = secrets.obfuscation_key;
|
||||||
|
cfg.handshake_pad_min = secrets.handshake_pad_min;
|
||||||
|
cfg.handshake_pad_max = secrets.handshake_pad_max;
|
||||||
|
|
||||||
let mut machine = match ProtocolMachine::new(cfg) {
|
let mut machine = match ProtocolMachine::new(cfg) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
|
@ -227,12 +228,12 @@ impl Dispatcher {
|
||||||
|
|
||||||
self.replay_cache.insert(payload.to_vec(), ts);
|
self.replay_cache.insert(payload.to_vec(), ts);
|
||||||
|
|
||||||
machine.set_session_keys(candidate_session_id, obf_key);
|
machine.set_session_keys(candidate_session_id, secrets.obfuscation_key);
|
||||||
|
|
||||||
self.peer_machines.insert(candidate_session_id, PeerState {
|
self.peer_machines.insert(candidate_session_id, PeerState {
|
||||||
machine,
|
machine,
|
||||||
last_addr: peer,
|
last_addr: peer,
|
||||||
obfuscation_key: obf_key,
|
obfuscation_key: secrets.obfuscation_key,
|
||||||
last_seen: std::time::Instant::now(),
|
last_seen: std::time::Instant::now(),
|
||||||
});
|
});
|
||||||
self.addr_to_session.insert(peer, candidate_session_id);
|
self.addr_to_session.insert(peer, candidate_session_id);
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,9 @@ pub async fn run_server(
|
||||||
rto_ms: 100,
|
rto_ms: 100,
|
||||||
max_retries: 8,
|
max_retries: 8,
|
||||||
max_sent_history: 32768,
|
max_sent_history: 32768,
|
||||||
|
// Defaults — overridden per-session by dispatcher using derive_all_secrets()
|
||||||
|
handshake_pad_min: 32,
|
||||||
|
handshake_pad_max: 128,
|
||||||
};
|
};
|
||||||
|
|
||||||
let dispatcher = Dispatcher::new(protocol_config, shared_keys.clone());
|
let dispatcher = Dispatcher::new(protocol_config, shared_keys.clone());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue