use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::mesh::{MeshError, MeshStream};
#[async_trait]
pub trait EncryptedChannel: Send + Sync + 'static {
async fn send_encrypted(&mut self, plaintext: &[u8]) -> Result<(), MeshError>;
async fn recv_encrypted(&mut self) -> Result<Vec<u8>, MeshError>;
fn remote_static_key(&self) -> Option<&[u8]>;
async fn close(&mut self) -> Result<(), MeshError>;
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NoisePattern {
XX,
IK,
}
#[derive(Debug, Clone)]
pub struct NoiseConfig {
pub pattern: NoisePattern,
pub local_private_key: [u8; 32],
pub remote_static_key: Option<[u8; 32]>,
}
pub struct PassthroughChannel {
stream: Box<dyn MeshStream>,
}
impl PassthroughChannel {
pub fn new(stream: Box<dyn MeshStream>) -> Self {
Self { stream }
}
}
#[async_trait]
impl EncryptedChannel for PassthroughChannel {
async fn send_encrypted(&mut self, plaintext: &[u8]) -> Result<(), MeshError> {
self.stream.send(plaintext).await
}
async fn recv_encrypted(&mut self) -> Result<Vec<u8>, MeshError> {
self.stream.recv().await
}
fn remote_static_key(&self) -> Option<&[u8]> {
None
}
async fn close(&mut self) -> Result<(), MeshError> {
self.stream.close().await
}
}
#[derive(Debug, Clone)]
pub struct KeyRotationState {
pub generation: u64,
pub established_at: std::time::Instant,
pub max_lifetime: std::time::Duration,
pub grace_period: std::time::Duration,
pub rotating: bool,
}
impl KeyRotationState {
pub fn new(
max_lifetime: std::time::Duration,
grace_period: std::time::Duration,
) -> Self {
Self {
generation: 0,
established_at: std::time::Instant::now(),
max_lifetime,
grace_period,
rotating: false,
}
}
pub fn needs_rotation(&self) -> bool {
self.established_at.elapsed() > self.max_lifetime
}
pub fn begin_rotation(&mut self) {
self.rotating = true;
}
pub fn complete_rotation(&mut self) {
self.generation += 1;
self.established_at = std::time::Instant::now();
self.rotating = false;
}
pub fn old_key_valid(&self) -> bool {
self.rotating || self.established_at.elapsed() < self.grace_period
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KemConfig {
pub kem_supported: bool,
pub kem_public_key: Option<Vec<u8>>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KemNegotiationResult {
BothSupported,
GracefulDegradation,
ClassicalOnly,
}
pub fn negotiate_kem(local: &KemConfig, remote: &KemConfig) -> KemNegotiationResult {
match (local.kem_supported, remote.kem_supported) {
(true, true) => KemNegotiationResult::BothSupported,
(true, false) | (false, true) => KemNegotiationResult::GracefulDegradation,
(false, false) => KemNegotiationResult::ClassicalOnly,
}
}
#[derive(Debug, Clone)]
pub struct HybridKeyExchange {
pub classical_secret: Vec<u8>,
pub pq_secret: Option<Vec<u8>>,
pub final_key: Vec<u8>,
pub pq_active: bool,
}
impl HybridKeyExchange {
pub fn combine(classical: Vec<u8>, pq: Option<Vec<u8>>) -> Self {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(&classical);
if let Some(ref pq_s) = pq {
hasher.update(pq_s);
}
hasher.update(b"weftos-hybrid-kem-v1");
let final_key = hasher.finalize().to_vec();
Self {
pq_active: pq.is_some(),
classical_secret: classical,
pq_secret: pq,
final_key,
}
}
}
pub struct KemUpgradeProtocol {
pub local: KemConfig,
pub remote: KemConfig,
pub result: KemNegotiationResult,
}
impl KemUpgradeProtocol {
pub fn new(local: KemConfig, remote: KemConfig) -> Self {
let result = negotiate_kem(&local, &remote);
Self { local, remote, result }
}
pub fn requires_upgrade(&self) -> bool {
self.result == KemNegotiationResult::BothSupported
}
pub fn execute(&self, classical_secret: Vec<u8>) -> HybridKeyExchange {
match self.result {
KemNegotiationResult::BothSupported => {
let pq_secret = {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(b"ml-kem-768-simulated");
if let Some(ref pk) = self.remote.kem_public_key {
h.update(pk);
}
h.finalize().to_vec()
};
HybridKeyExchange::combine(classical_secret, Some(pq_secret))
}
_ => HybridKeyExchange::combine(classical_secret, None),
}
}
}
#[cfg(test)]
mod mock {
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use crate::mesh::{MeshError, MeshStream};
#[derive(Debug, Default)]
pub struct MockBuffer {
pub data: VecDeque<Vec<u8>>,
}
pub struct MockStream {
pub tx: Arc<Mutex<MockBuffer>>,
pub rx: Arc<Mutex<MockBuffer>>,
pub closed: bool,
}
impl MockStream {
pub fn pair() -> (Self, Self) {
let a_to_b = Arc::new(Mutex::new(MockBuffer::default()));
let b_to_a = Arc::new(Mutex::new(MockBuffer::default()));
let a = MockStream {
tx: Arc::clone(&a_to_b),
rx: Arc::clone(&b_to_a),
closed: false,
};
let b = MockStream {
tx: b_to_a,
rx: a_to_b,
closed: false,
};
(a, b)
}
}
#[async_trait]
impl MeshStream for MockStream {
async fn send(&mut self, data: &[u8]) -> Result<(), MeshError> {
if self.closed {
return Err(MeshError::ConnectionClosed);
}
self.tx.lock().unwrap().data.push_back(data.to_vec());
Ok(())
}
async fn recv(&mut self) -> Result<Vec<u8>, MeshError> {
if self.closed {
return Err(MeshError::ConnectionClosed);
}
self.rx
.lock()
.unwrap()
.data
.pop_front()
.ok_or(MeshError::ConnectionClosed)
}
async fn close(&mut self) -> Result<(), MeshError> {
self.closed = true;
Ok(())
}
fn remote_addr(&self) -> Option<SocketAddr> {
Some("127.0.0.1:0".parse().unwrap())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::mock::MockStream;
#[test]
fn noise_pattern_variants() {
assert_eq!(NoisePattern::XX, NoisePattern::XX);
assert_ne!(NoisePattern::XX, NoisePattern::IK);
}
#[test]
fn noise_pattern_serde_roundtrip() {
for pattern in [NoisePattern::XX, NoisePattern::IK] {
let json = serde_json::to_string(&pattern).unwrap();
let restored: NoisePattern = serde_json::from_str(&json).unwrap();
assert_eq!(restored, pattern);
}
}
#[test]
fn noise_config_fields() {
let cfg = NoiseConfig {
pattern: NoisePattern::XX,
local_private_key: [0x42; 32],
remote_static_key: None,
};
assert_eq!(cfg.pattern, NoisePattern::XX);
assert_eq!(cfg.local_private_key, [0x42; 32]);
assert!(cfg.remote_static_key.is_none());
let cfg_ik = NoiseConfig {
pattern: NoisePattern::IK,
local_private_key: [1; 32],
remote_static_key: Some([2; 32]),
};
assert_eq!(cfg_ik.pattern, NoisePattern::IK);
assert_eq!(cfg_ik.remote_static_key, Some([2; 32]));
}
#[tokio::test]
async fn passthrough_send_recv() {
let (a, b) = MockStream::pair();
let mut chan_a = PassthroughChannel::new(Box::new(a));
let mut chan_b = PassthroughChannel::new(Box::new(b));
chan_a.send_encrypted(b"hello mesh").await.unwrap();
let received = chan_b.recv_encrypted().await.unwrap();
assert_eq!(received, b"hello mesh");
}
#[tokio::test]
async fn passthrough_bidirectional() {
let (a, b) = MockStream::pair();
let mut chan_a = PassthroughChannel::new(Box::new(a));
let mut chan_b = PassthroughChannel::new(Box::new(b));
chan_a.send_encrypted(b"ping").await.unwrap();
let msg = chan_b.recv_encrypted().await.unwrap();
assert_eq!(msg, b"ping");
chan_b.send_encrypted(b"pong").await.unwrap();
let msg = chan_a.recv_encrypted().await.unwrap();
assert_eq!(msg, b"pong");
}
#[test]
fn passthrough_no_remote_key() {
let (a, _b) = MockStream::pair();
let chan = PassthroughChannel::new(Box::new(a));
assert!(chan.remote_static_key().is_none());
}
#[test]
fn key_rotation_initial_state() {
let state = KeyRotationState::new(
std::time::Duration::from_secs(3600),
std::time::Duration::from_secs(60),
);
assert_eq!(state.generation, 0);
assert!(!state.rotating);
assert!(!state.needs_rotation());
}
#[test]
fn key_rotation_needs_rotation_after_lifetime() {
let mut state = KeyRotationState::new(
std::time::Duration::from_secs(0), std::time::Duration::from_secs(60),
);
assert!(state.needs_rotation());
assert!(state.old_key_valid());
state.begin_rotation();
assert!(state.rotating);
assert!(state.old_key_valid()); }
#[test]
fn key_rotation_complete_lifecycle() {
let mut state = KeyRotationState::new(
std::time::Duration::from_secs(0),
std::time::Duration::from_secs(3600), );
assert_eq!(state.generation, 0);
state.begin_rotation();
assert!(state.rotating);
state.complete_rotation();
assert_eq!(state.generation, 1);
assert!(!state.rotating);
assert!(state.old_key_valid());
}
#[test]
fn key_rotation_multiple_generations() {
let mut state = KeyRotationState::new(
std::time::Duration::from_secs(0),
std::time::Duration::from_secs(3600),
);
for i in 0..5 {
assert_eq!(state.generation, i);
state.begin_rotation();
state.complete_rotation();
}
assert_eq!(state.generation, 5);
}
#[tokio::test]
async fn passthrough_close() {
let (a, _b) = MockStream::pair();
let mut chan = PassthroughChannel::new(Box::new(a));
chan.close().await.unwrap();
let result = chan.send_encrypted(b"after close").await;
assert!(result.is_err());
}
#[test]
fn kem_negotiation_both_supported() {
let local = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xAA; 1184]),
};
let remote = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xBB; 1184]),
};
assert_eq!(
negotiate_kem(&local, &remote),
KemNegotiationResult::BothSupported,
);
}
#[test]
fn kem_negotiation_graceful_degradation() {
let local = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xAA; 1184]),
};
let remote = KemConfig {
kem_supported: false,
kem_public_key: None,
};
assert_eq!(
negotiate_kem(&local, &remote),
KemNegotiationResult::GracefulDegradation,
);
assert_eq!(
negotiate_kem(&remote, &local),
KemNegotiationResult::GracefulDegradation,
);
}
#[test]
fn kem_negotiation_classical_only() {
let local = KemConfig {
kem_supported: false,
kem_public_key: None,
};
let remote = KemConfig {
kem_supported: false,
kem_public_key: None,
};
assert_eq!(
negotiate_kem(&local, &remote),
KemNegotiationResult::ClassicalOnly,
);
}
#[test]
fn hybrid_key_exchange_with_pq() {
let classical = vec![0x01; 32];
let pq = vec![0x02; 32];
let hke = HybridKeyExchange::combine(classical.clone(), Some(pq.clone()));
assert!(hke.pq_active, "PQ should be active");
assert_eq!(hke.classical_secret, classical);
assert_eq!(hke.pq_secret.as_ref().unwrap(), &pq);
assert_eq!(hke.final_key.len(), 32, "SHA-256 output is 32 bytes");
let classical_only = HybridKeyExchange::combine(classical.clone(), None);
assert_ne!(
hke.final_key, classical_only.final_key,
"hybrid key must differ from classical-only key"
);
}
#[test]
fn hybrid_key_exchange_classical_only() {
let classical = vec![0x01; 32];
let hke = HybridKeyExchange::combine(classical.clone(), None);
assert!(!hke.pq_active, "PQ should not be active");
assert!(hke.pq_secret.is_none());
assert_eq!(hke.final_key.len(), 32);
}
#[test]
fn kem_upgrade_requires_upgrade_when_both_support() {
let local = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xAA; 1184]),
};
let remote = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xBB; 1184]),
};
let protocol = KemUpgradeProtocol::new(local, remote);
assert!(
protocol.requires_upgrade(),
"must require upgrade when both sides support KEM"
);
assert_eq!(protocol.result, KemNegotiationResult::BothSupported);
}
#[test]
fn kem_upgrade_no_upgrade_when_degraded() {
let local = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xAA; 1184]),
};
let remote = KemConfig {
kem_supported: false,
kem_public_key: None,
};
let protocol = KemUpgradeProtocol::new(local, remote);
assert!(
!protocol.requires_upgrade(),
"should not require upgrade with graceful degradation"
);
}
#[test]
fn kem_upgrade_before_sync_streams() {
let local = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xAA; 1184]),
};
let remote = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0xBB; 1184]),
};
let protocol = KemUpgradeProtocol::new(local, remote);
assert!(protocol.requires_upgrade());
let classical_secret = vec![0xCC; 32];
let hke = protocol.execute(classical_secret.clone());
assert!(hke.pq_active, "PQ must be active after KEM upgrade");
assert!(hke.pq_secret.is_some(), "PQ secret must be present");
assert_eq!(hke.final_key.len(), 32);
let classical_only = HybridKeyExchange::combine(classical_secret, None);
assert_ne!(
hke.final_key, classical_only.final_key,
"hybrid final key must differ from classical-only"
);
}
#[test]
fn kem_upgrade_classical_fallback() {
let local = KemConfig {
kem_supported: false,
kem_public_key: None,
};
let remote = KemConfig {
kem_supported: false,
kem_public_key: None,
};
let protocol = KemUpgradeProtocol::new(local, remote);
assert!(!protocol.requires_upgrade());
let classical_secret = vec![0xCC; 32];
let hke = protocol.execute(classical_secret);
assert!(!hke.pq_active);
assert!(hke.pq_secret.is_none());
}
#[test]
fn kem_config_serde_roundtrip() {
let cfg = KemConfig {
kem_supported: true,
kem_public_key: Some(vec![0x42; 1184]),
};
let json = serde_json::to_string(&cfg).unwrap();
let restored: KemConfig = serde_json::from_str(&json).unwrap();
assert_eq!(restored.kem_supported, true);
assert_eq!(restored.kem_public_key.unwrap().len(), 1184);
}
#[test]
fn kem_negotiation_result_serde_roundtrip() {
for result in [
KemNegotiationResult::BothSupported,
KemNegotiationResult::GracefulDegradation,
KemNegotiationResult::ClassicalOnly,
] {
let json = serde_json::to_string(&result).unwrap();
let restored: KemNegotiationResult = serde_json::from_str(&json).unwrap();
assert_eq!(restored, result);
}
}
}