qssh 0.5.0

Post-quantum secure shell with NIST PQC algorithms (Falcon, SPHINCS+, ML-KEM), configurable security tiers, and quantum-resistant protocol design
Documentation
//! QSSH - Quantum Secure Shell
//!
//! A quantum-secure replacement for SSH using post-quantum cryptography
//! and optional QKD (Quantum Key Distribution) integration.

#![recursion_limit = "4096"]
#![cfg_attr(not(kani), allow(unexpected_cfgs))]

pub mod crypto;
pub mod transport;
#[cfg(feature = "qkd")]
pub mod qkd;
pub mod handshake;
pub mod client;
pub mod server;
pub mod config;
pub mod agent;
pub mod x11;
pub mod pty;
pub mod auth;
pub mod audit;
pub mod vault;
pub mod p2p;
pub mod pty_thread;
pub mod shell_handler_thread;
pub mod port_forward;

// Re-export port forwarding types
pub use port_forward::{PortForwardManager, ForwardType};
pub mod subsystems;
pub mod sftp_client;
pub mod multiplex;
pub mod proxy;
pub mod known_hosts;
pub mod compression;
pub mod session;
pub mod certificate;
#[cfg(feature = "gssapi")]
pub mod gssapi;
pub mod security_tiers;

use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::security_tiers::SecurityTier;

#[derive(Error, Debug)]
pub enum QsshError {
    #[error("Connection error: {0}")]
    Connection(String),
    
    #[error("Cryptographic error: {0}")]
    Crypto(String),
    
    #[error("QKD error: {0}")]
    Qkd(String),
    
    #[error("Protocol error: {0}")]
    Protocol(String),
    
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("Configuration error: {0}")]
    Config(String),
}

pub type Result<T> = std::result::Result<T, QsshError>;

/// QSSH configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QsshConfig {
    /// Server endpoint (host:port)
    pub server: String,

    /// Username for authentication
    pub username: String,

    /// Password for authentication (optional)
    #[serde(skip_serializing)]
    pub password: Option<String>,

    /// Port forwarding rules
    pub port_forwards: Vec<PortForward>,

    /// Enable QKD for quantum key distribution
    pub use_qkd: bool,

    /// QKD endpoint URL (e.g., "https://192.168.0.4/api/v1/keys")
    pub qkd_endpoint: Option<String>,

    /// Path to QKD client certificate
    pub qkd_cert_path: Option<String>,

    /// Path to QKD client private key
    pub qkd_key_path: Option<String>,

    /// Path to QKD CA certificate
    pub qkd_ca_path: Option<String>,

    /// Post-quantum signature algorithm to use
    pub pq_algorithm: PqAlgorithm,

    /// Key exchange algorithm (default: ML-KEM-1024, FIPS 203 — confidential PQ KEX)
    #[serde(default)]
    pub kex_algorithm: KexAlgorithm,

    /// Key rotation interval (seconds)
    pub key_rotation_interval: u64,

    /// Security tier for this connection
    pub security_tier: SecurityTier,

    /// Use quantum-native transport (768-byte indistinguishable frames)
    pub quantum_native: bool,
}

impl Default for QsshConfig {
    fn default() -> Self {
        Self {
            server: "localhost:22222".to_string(),
            username: "user".to_string(),
            password: None,
            port_forwards: Vec::new(),
            use_qkd: false,
            qkd_endpoint: None,
            qkd_cert_path: None,
            qkd_key_path: None,
            qkd_ca_path: None,
            pq_algorithm: PqAlgorithm::Falcon512,
            kex_algorithm: KexAlgorithm::MlKem1024,
            key_rotation_interval: 3600,
            security_tier: SecurityTier::default(),  // T2: Hardened PQ
            quantum_native: true,  // Default to quantum-native transport
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortForward {
    pub local_port: u16,
    pub remote_host: String,
    pub remote_port: u16,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PqAlgorithm {
    /// SPHINCS+ - Hash-based signature (NIST Level 1)
    SphincsPlus,
    /// Falcon-512 - NTRU lattice-based signature (NIST Level 1)
    Falcon512,
    /// Falcon-1024 - NTRU lattice-based signature (NIST Level 5)
    Falcon1024,
}

/// Key exchange algorithm for QSSH handshake
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
pub enum KexAlgorithm {
    /// Falcon-signed ephemeral shares (original QSSH, backward compatible).
    /// NOTE: authentication only — the session key is derived from values sent
    /// in cleartext, so this KEX provides no confidentiality. Kept for interop;
    /// never selected as a silent default.
    FalconSignedShares,
    /// ML-KEM-768 (FIPS 203, NIST Level 3)
    MlKem768,
    /// ML-KEM-1024 (FIPS 203, NIST Level 5)
    #[default]
    MlKem1024,
    /// X25519 + ML-KEM-768 hybrid (requires hybrid-kex feature)
    #[cfg(feature = "hybrid-kex")]
    HybridX25519MlKem768,
}

impl std::fmt::Display for KexAlgorithm {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KexAlgorithm::FalconSignedShares => write!(f, "falcon-signed-shares"),
            KexAlgorithm::MlKem768 => write!(f, "mlkem768 (FIPS 203, Level 3)"),
            KexAlgorithm::MlKem1024 => write!(f, "mlkem1024 (FIPS 203, Level 5)"),
            #[cfg(feature = "hybrid-kex")]
            KexAlgorithm::HybridX25519MlKem768 => write!(f, "hybrid-x25519-mlkem768"),
        }
    }
}

impl std::fmt::Display for PqAlgorithm {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PqAlgorithm::SphincsPlus => write!(f, "SPHINCS+ (hash-based, quantum-safe)"),
            PqAlgorithm::Falcon512 => write!(f, "Falcon-512 (NTRU lattice, NIST Level 1)"),
            PqAlgorithm::Falcon1024 => write!(f, "Falcon-1024 (NTRU lattice, NIST Level 5)"),
        }
    }
}

/// Quantum capabilities
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantumCapabilities {
    pub supports_qkd: bool,
    pub supports_sphincs: bool,
    pub supports_falcon: bool,
    pub qkd_endpoints: Vec<String>,
    // Note: Kyber support removed due to vulnerabilities
}

/// Re-exports for convenience
pub use client::QsshClient;
pub use client::ReconnectConfig;
pub use server::QsshServer;

#[cfg(test)]
mod default_kex_tests {
    use super::{KexAlgorithm, QsshConfig};

    // Security regression guard: the default KEX must be a real KEM.
    // FalconSignedShares authenticates but derives the session key from
    // values sent in cleartext, so it provides NO confidentiality. It must
    // never be the silent default. See PR fixing the auth-only default.

    #[test]
    fn enum_default_kex_is_confidential_mlkem1024() {
        assert_eq!(KexAlgorithm::default(), KexAlgorithm::MlKem1024);
        assert_ne!(KexAlgorithm::default(), KexAlgorithm::FalconSignedShares);
    }

    #[test]
    fn qsshconfig_default_kex_is_confidential_mlkem1024() {
        assert_eq!(QsshConfig::default().kex_algorithm, KexAlgorithm::MlKem1024);
        assert_ne!(
            QsshConfig::default().kex_algorithm,
            KexAlgorithm::FalconSignedShares
        );
    }
}