qssh 0.0.2-alpha

Experimental quantum-safe SSH using post-quantum crypto. Research project - NOT for production. See LIMITATIONS.md
Documentation
//! Stable tests that work reliably

use qssh::crypto::SymmetricCrypto;
use qssh::{QsshConfig, PqAlgorithm, QsshError};
use std::collections::HashSet;

#[test]
fn test_symmetric_encryption() {
    // Test AES-256-GCM encryption
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    let plaintext = b"Hello, quantum world!";
    let (ciphertext, nonce) = crypto.encrypt(plaintext).unwrap();
    
    // Verify encryption worked
    assert_ne!(ciphertext, plaintext);
    assert_eq!(nonce.len(), 12); // AES-GCM uses 12-byte nonce
    
    // Test decryption
    let decrypted = crypto.decrypt(&ciphertext, &nonce).unwrap();
    assert_eq!(decrypted, plaintext);
}

#[test]
fn test_encryption_uniqueness() {
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    // Same plaintext should produce different ciphertexts
    let plaintext = b"Test message";
    let (ct1, nonce1) = crypto.encrypt(plaintext).unwrap();
    let (ct2, nonce2) = crypto.encrypt(plaintext).unwrap();
    
    // Nonces must be different
    assert_ne!(nonce1, nonce2);
    // Ciphertexts will be different due to different nonces
    assert_ne!(ct1, ct2);
}

#[test]
fn test_tampering_detection() {
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    let plaintext = b"Sensitive data";
    let (mut ciphertext, nonce) = crypto.encrypt(plaintext).unwrap();
    
    // Tamper with ciphertext
    ciphertext[0] ^= 0xFF;
    
    // Decryption should fail due to authentication tag mismatch
    assert!(crypto.decrypt(&ciphertext, &nonce).is_err());
}

#[test]
fn test_nonce_uniqueness() {
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    let mut nonces = HashSet::new();
    
    // Test 1000 encryptions for nonce uniqueness
    for i in 0..1000 {
        let (_, nonce) = crypto.encrypt(b"test").unwrap();
        assert!(nonces.insert(nonce), "Nonce reused at iteration {}", i);
    }
    
    assert_eq!(nonces.len(), 1000);
}

#[test]
fn test_different_key_sizes() {
    // Too short should fail
    let short_key = vec![0x42; 16];
    assert!(SymmetricCrypto::from_shared_secret(&short_key).is_err());
    
    // Exactly 32 bytes should work
    let valid_key = vec![0x42; 32];
    assert!(SymmetricCrypto::from_shared_secret(&valid_key).is_ok());
    
    // Longer keys should work (takes first 32 bytes)
    let long_key = vec![0x42; 64];
    assert!(SymmetricCrypto::from_shared_secret(&long_key).is_ok());
}

#[test]
fn test_config_defaults() {
    let config = QsshConfig::default();
    
    assert_eq!(config.server, "localhost:22222");
    assert_eq!(config.username, "user");
    assert_eq!(config.pq_algorithm, PqAlgorithm::Falcon512);
    assert!(!config.use_qkd);
    assert_eq!(config.key_rotation_interval, 3600);
    assert!(config.port_forwards.is_empty());
}

#[test]
fn test_config_serialization() {
    let config = QsshConfig {
        server: "quantum.server:2222".to_string(),
        username: "alice".to_string(),
        port_forwards: vec![],
        use_qkd: true,
        pq_algorithm: PqAlgorithm::SphincsPlus,
        key_rotation_interval: 1800,
    };
    
    // Test JSON serialization
    let json = serde_json::to_string(&config).unwrap();
    let parsed: QsshConfig = serde_json::from_str(&json).unwrap();
    
    assert_eq!(parsed.server, config.server);
    assert_eq!(parsed.use_qkd, config.use_qkd);
    assert_eq!(parsed.pq_algorithm, PqAlgorithm::SphincsPlus);
}

#[test] 
fn test_error_display() {
    let errors = vec![
        QsshError::Connection("Connection refused".to_string()),
        QsshError::Crypto("Invalid key".to_string()),
        QsshError::Qkd("QKD unavailable".to_string()),
        QsshError::Protocol("Version mismatch".to_string()),
    ];
    
    for error in errors {
        let msg = error.to_string();
        assert!(!msg.is_empty());
        // Verify error type is in message
        match error {
            QsshError::Connection(_) => assert!(msg.contains("Connection")),
            QsshError::Crypto(_) => assert!(msg.contains("Cryptographic")),
            QsshError::Qkd(_) => assert!(msg.contains("QKD")),
            QsshError::Protocol(_) => assert!(msg.contains("Protocol")),
            _ => {}
        }
    }
}

// Performance tests
#[test]
fn test_encryption_performance() {
    use std::time::Instant;
    
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    let data = vec![0xAB; 10240]; // 10KB
    let iterations = 100;
    
    let start = Instant::now();
    
    for _ in 0..iterations {
        let (ct, nonce) = crypto.encrypt(&data).unwrap();
        let _ = crypto.decrypt(&ct, &nonce).unwrap();
    }
    
    let elapsed = start.elapsed();
    let throughput_mbps = (data.len() * iterations * 2) as f64 
        / elapsed.as_secs_f64() / 1_000_000.0;
    
    println!("AES-256-GCM throughput: {:.2} MB/s", throughput_mbps);
    
    // Should be reasonably fast (lowered for VM performance)
    assert!(throughput_mbps > 3.0, "Encryption too slow: {:.2} MB/s", throughput_mbps);
}

#[test]
fn test_large_data_encryption() {
    let key = vec![0x42; 32];
    let crypto = SymmetricCrypto::from_shared_secret(&key).unwrap();
    
    // Test various sizes
    let sizes = vec![
        1,           // 1 byte
        100,         // Small
        10240,       // 10KB
        1048576,     // 1MB
    ];
    
    for size in sizes {
        let data = vec![0xCD; size];
        let (ct, nonce) = crypto.encrypt(&data).unwrap();
        let decrypted = crypto.decrypt(&ct, &nonce).unwrap();
        
        assert_eq!(decrypted, data, "Failed for size {}", size);
    }
}