use super::{Transport, TransportCapability, TransportMessage, MessageMetadata};
use crate::{Error, Result};
use async_trait::async_trait;
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::sync::Mutex;
pub use qssh::{
QsshConfig as QsshLibConfig,
QsshClient as QsshLibClient,
PqAlgorithm,
security_tiers::SecurityTier,
PortForward,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionState {
New,
KeyExchange,
Authenticated,
Closed,
}
pub struct QsshSession {
pub id: String,
pub peer_fingerprint: String,
pub remote_addr: String,
pub state: SessionState,
pub created_at: u64,
client: Option<QsshLibClient>,
}
impl QsshSession {
pub fn new(peer_fingerprint: String, remote_addr: String) -> Self {
Self {
id: Self::generate_session_id(),
peer_fingerprint,
remote_addr,
state: SessionState::New,
created_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
client: None,
}
}
fn generate_session_id() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..16)
.map(|_| format!("{:02x}", rng.gen::<u8>()))
.collect()
}
pub fn client(&self) -> Option<&QsshLibClient> {
self.client.as_ref()
}
pub fn client_mut(&mut self) -> Option<&mut QsshLibClient> {
self.client.as_mut()
}
}
#[derive(Debug, Clone)]
pub struct QsshConfig {
pub listen_addr: String,
pub listen_port: u16,
pub enable_pqc: bool,
pub session_timeout: u64,
pub pq_algorithm: PqAlgorithm,
pub security_tier: SecurityTier,
pub quantum_native: bool,
pub qkd_endpoint: Option<String>,
}
impl Default for QsshConfig {
fn default() -> Self {
Self {
listen_addr: "0.0.0.0".into(),
listen_port: 2222,
enable_pqc: true,
session_timeout: 3600,
pq_algorithm: PqAlgorithm::Falcon512,
security_tier: SecurityTier::default(), quantum_native: true,
qkd_endpoint: None,
}
}
}
impl QsshConfig {
pub fn to_lib_config(&self, server: &str, username: &str) -> QsshLibConfig {
QsshLibConfig {
server: server.to_string(),
username: username.to_string(),
password: None,
port_forwards: Vec::new(),
use_qkd: self.qkd_endpoint.is_some(),
qkd_endpoint: self.qkd_endpoint.clone(),
qkd_cert_path: None,
qkd_key_path: None,
qkd_ca_path: None,
pq_algorithm: self.pq_algorithm,
key_rotation_interval: 3600,
security_tier: self.security_tier,
quantum_native: self.quantum_native,
}
}
}
pub struct QsshTransport {
config: QsshConfig,
sessions: Arc<Mutex<HashMap<String, QsshSession>>>,
incoming: Arc<Mutex<VecDeque<TransportMessage>>>,
listening: Arc<Mutex<bool>>,
username: String,
}
impl QsshTransport {
pub fn new(config: QsshConfig) -> Self {
Self {
config,
sessions: Arc::new(Mutex::new(HashMap::new())),
incoming: Arc::new(Mutex::new(VecDeque::new())),
listening: Arc::new(Mutex::new(false)),
username: whoami::username(),
}
}
pub fn with_username(mut self, username: impl Into<String>) -> Self {
self.username = username.into();
self
}
pub fn security_tier(&self) -> SecurityTier {
self.config.security_tier
}
pub async fn connect_peer(&self, addr: &str, fingerprint: &str) -> Result<String> {
let mut session = QsshSession::new(fingerprint.to_string(), addr.to_string());
let session_id = session.id.clone();
tracing::info!(
"Connecting QSSH to {} ({}) with security tier {:?}",
addr, fingerprint, self.config.security_tier
);
let lib_config = self.config.to_lib_config(addr, &self.username);
let mut client = QsshLibClient::new(lib_config);
session.state = SessionState::KeyExchange;
match client.connect().await {
Ok(()) => {
tracing::info!(
"QSSH session {} established with {} (algorithm: {:?})",
session_id, addr, self.config.pq_algorithm
);
session.state = SessionState::Authenticated;
session.client = Some(client);
}
Err(e) => {
tracing::error!("QSSH connection failed: {}", e);
session.state = SessionState::Closed;
return Err(Error::Connection(format!("QSSH connection failed: {}", e)));
}
}
self.sessions.lock().await.insert(session_id.clone(), session);
Ok(session_id)
}
pub async fn disconnect_session(&self, session_id: &str) -> Result<()> {
if let Some(mut session) = self.sessions.lock().await.remove(session_id) {
session.state = SessionState::Closed;
tracing::info!("Closed QSSH session {}", session_id);
}
Ok(())
}
pub async fn active_sessions(&self) -> Vec<String> {
self.sessions
.lock()
.await
.iter()
.filter(|(_, s)| s.state == SessionState::Authenticated)
.map(|(id, _)| id.clone())
.collect()
}
pub async fn get_session(&self, session_id: &str) -> Option<SessionInfo> {
self.sessions.lock().await.get(session_id).map(|s| SessionInfo {
id: s.id.clone(),
peer_fingerprint: s.peer_fingerprint.clone(),
remote_addr: s.remote_addr.clone(),
state: s.state.clone(),
created_at: s.created_at,
})
}
async fn send_on_session(&self, session_id: &str, data: &[u8]) -> Result<()> {
let mut sessions = self.sessions.lock().await;
let session = sessions
.get_mut(session_id)
.ok_or_else(|| Error::Connection("Session not found".into()))?;
if session.state != SessionState::Authenticated {
return Err(Error::Connection("Session not authenticated".into()));
}
if let Some(_client) = &mut session.client {
tracing::debug!("Sending {} bytes on QSSH session {}", data.len(), session_id);
}
Ok(())
}
async fn start_listener(&self) -> Result<()> {
let addr = format!("{}:{}", self.config.listen_addr, self.config.listen_port);
tracing::info!(
"Starting QSSH listener on {} (security tier: {:?})",
addr, self.config.security_tier
);
*self.listening.lock().await = true;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SessionInfo {
pub id: String,
pub peer_fingerprint: String,
pub remote_addr: String,
pub state: SessionState,
pub created_at: u64,
}
#[async_trait]
impl Transport for QsshTransport {
fn name(&self) -> &str {
"QSSH"
}
fn capabilities(&self) -> Vec<TransportCapability> {
vec![
TransportCapability::Send,
TransportCapability::Receive,
TransportCapability::PostQuantum,
]
}
async fn is_connected(&self) -> bool {
*self.listening.lock().await || !self.sessions.lock().await.is_empty()
}
async fn connect(&mut self) -> Result<()> {
self.start_listener().await
}
async fn disconnect(&mut self) -> Result<()> {
*self.listening.lock().await = false;
let session_ids: Vec<String> = self.sessions.lock().await.keys().cloned().collect();
for id in &session_ids {
self.disconnect_session(id).await?;
}
Ok(())
}
async fn send(&self, message: TransportMessage) -> Result<()> {
let sessions = self.sessions.lock().await;
let session = sessions
.values()
.find(|s| s.peer_fingerprint == message.to && s.state == SessionState::Authenticated);
let session_id = match session {
Some(s) => s.id.clone(),
None => {
drop(sessions);
return Err(Error::Connection(format!(
"No active session for peer {}",
message.to
)));
}
};
drop(sessions);
let data = bincode::serialize(&message)
.map_err(|e| Error::Serialization(e.to_string()))?;
self.send_on_session(&session_id, &data).await
}
async fn receive(&mut self) -> Result<TransportMessage> {
loop {
if let Some(msg) = self.incoming.lock().await.pop_front() {
return Ok(msg);
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
if !self.is_connected().await {
return Err(Error::Connection("Disconnected".into()));
}
}
}
async fn poll(&mut self) -> Result<Option<TransportMessage>> {
Ok(self.incoming.lock().await.pop_front())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportTier {
PqSecured,
Hybrid,
Tls,
None,
}
impl TransportTier {
pub fn display_name(&self) -> &'static str {
match self {
TransportTier::PqSecured => "PQ-SECURED",
TransportTier::Hybrid => "HYBRID",
TransportTier::Tls => "TLS",
TransportTier::None => "NONE",
}
}
pub fn from_security_tier(tier: SecurityTier) -> Self {
match tier {
SecurityTier::Classical => TransportTier::Tls,
SecurityTier::PostQuantum | SecurityTier::HardenedPQ => TransportTier::PqSecured,
SecurityTier::EntropyEnhanced | SecurityTier::QuantumSecured | SecurityTier::HybridQuantum => {
TransportTier::PqSecured
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_qssh_transport() {
let transport = QsshTransport::new(QsshConfig::default());
assert!(!transport.is_connected().await);
assert_eq!(transport.name(), "QSSH");
assert_eq!(transport.security_tier(), SecurityTier::HardenedPQ);
}
#[test]
fn test_session_creation() {
let session = QsshSession::new("fingerprint".into(), "127.0.0.1:2222".into());
assert_eq!(session.state, SessionState::New);
assert_eq!(session.id.len(), 32);
assert!(session.client.is_none());
}
#[test]
fn test_config_conversion() {
let config = QsshConfig {
security_tier: SecurityTier::QuantumSecured,
pq_algorithm: PqAlgorithm::Falcon512,
qkd_endpoint: Some("https://qkd.example.com".into()),
..Default::default()
};
let lib_config = config.to_lib_config("192.168.1.100:2222", "alice");
assert_eq!(lib_config.server, "192.168.1.100:2222");
assert_eq!(lib_config.username, "alice");
assert!(lib_config.use_qkd);
assert_eq!(lib_config.security_tier, SecurityTier::QuantumSecured);
}
#[test]
fn test_transport_tier() {
assert_eq!(
TransportTier::from_security_tier(SecurityTier::HardenedPQ),
TransportTier::PqSecured
);
assert_eq!(
TransportTier::from_security_tier(SecurityTier::Classical),
TransportTier::Tls
);
}
}