qssh 0.0.2-alpha

Experimental quantum-safe SSH using post-quantum crypto. Research project - NOT for production. See LIMITATIONS.md
Documentation
use qssh::vault::{QuantumVault, VaultConfig};
use qssh::vault::lamport::{LamportKeypair, LamportSignature};
use qssh::vault::ratchet::{DoubleRatchet, RatchetHeader};

#[tokio::test]
async fn test_quantum_vault_creation() {
    let config = VaultConfig {
        enable_qrng: false, // Disable for testing
        enable_lamport: true,
        enable_ratchet: true,
        storage_path: None,
    };
    
    let vault = QuantumVault::new(config).await;
    assert!(vault.is_ok(), "Failed to create quantum vault");
}

#[tokio::test]
async fn test_vault_store_retrieve() {
    let config = VaultConfig {
        enable_qrng: false,
        enable_lamport: true,
        enable_ratchet: true,
        storage_path: None,
    };
    
    let mut vault = QuantumVault::new(config).await.expect("Failed to create vault");
    
    let key = "test-key";
    let secret = vec![1, 2, 3, 4, 5, 6, 7, 8];
    
    vault.store_secret(key, secret.clone()).await.expect("Failed to store secret");
    
    let retrieved = vault.get_secret(key).await.expect("Failed to get secret");
    assert_eq!(retrieved, Some(secret));
    
    // Non-existent key should return None
    let missing = vault.get_secret("missing-key").await.expect("Failed to get secret");
    assert_eq!(missing, None);
}

#[test]
fn test_lamport_signature() {
    let keypair = LamportKeypair::generate();
    let message = b"Test message for Lamport signature";
    
    let signature = keypair.sign(message);
    assert!(keypair.verify(message, &signature));
    
    // Wrong message should fail
    assert!(!keypair.verify(b"Wrong message", &signature));
    
    // Tampered signature should fail
    let mut bad_sig = signature.clone();
    if !bad_sig.signature_parts.is_empty() && !bad_sig.signature_parts[0].is_empty() {
        bad_sig.signature_parts[0][0] ^= 0xFF;
        assert!(!keypair.verify(message, &bad_sig));
    }
}

#[test]
fn test_lamport_public_key() {
    let keypair = LamportKeypair::generate();
    let public_key = keypair.public_key();
    
    assert_eq!(public_key.verification_keys.len(), 256);
    for key in &public_key.verification_keys {
        assert_eq!(key.len(), 32); // SHA256 output
    }
}

#[tokio::test]
async fn test_double_ratchet() {
    let shared_secret = b"initial shared secret";
    let mut alice_ratchet = DoubleRatchet::new(shared_secret, true);
    let mut bob_ratchet = DoubleRatchet::new(shared_secret, false);
    
    // Alice sends first message
    let plaintext1 = b"Hello Bob!";
    let (header1, ciphertext1) = alice_ratchet.encrypt(plaintext1)
        .await.expect("Failed to encrypt");
    
    // Bob receives and decrypts
    let decrypted1 = bob_ratchet.decrypt(&header1, &ciphertext1)
        .await.expect("Failed to decrypt");
    assert_eq!(decrypted1, plaintext1);
    
    // Bob responds
    let plaintext2 = b"Hello Alice!";
    let (header2, ciphertext2) = bob_ratchet.encrypt(plaintext2)
        .await.expect("Failed to encrypt");
    
    // Alice receives and decrypts
    let decrypted2 = alice_ratchet.decrypt(&header2, &ciphertext2)
        .await.expect("Failed to decrypt");
    assert_eq!(decrypted2, plaintext2);
}

#[tokio::test]
async fn test_ratchet_forward_secrecy() {
    let shared_secret = b"forward secrecy test";
    let mut ratchet = DoubleRatchet::new(shared_secret, true);
    
    // Encrypt multiple messages
    let messages = vec![
        b"Message 1".as_ref(),
        b"Message 2".as_ref(),
        b"Message 3".as_ref(),
    ];
    
    let mut encrypted = Vec::new();
    
    for msg in &messages {
        let (header, ciphertext) = ratchet.encrypt(msg)
            .await.expect("Failed to encrypt");
        encrypted.push((header, ciphertext));
    }
    
    // Verify all ciphertexts are different even for same plaintext
    let (_, ct1) = ratchet.encrypt(b"Same message")
        .await.expect("Failed to encrypt");
    let (_, ct2) = ratchet.encrypt(b"Same message")
        .await.expect("Failed to encrypt");
    
    assert_ne!(ct1, ct2, "Ciphertexts should be different due to ratcheting");
}

#[cfg(test)]
mod vault_integration_tests {
    use super::*;
    
    #[tokio::test]
    async fn test_vault_with_ratchet() {
        let config = VaultConfig {
            enable_qrng: false,
            enable_lamport: true,
            enable_ratchet: true,
            storage_path: None,
        };
        
        let vault = QuantumVault::new(config).await.expect("Failed to create vault");
        
        // Verify ratchet is initialized
        assert!(vault.get_ratchet().await.is_some());
    }
    
    #[tokio::test]
    async fn test_vault_rotation() {
        let config = VaultConfig {
            enable_qrng: false,
            enable_lamport: true,
            enable_ratchet: true,
            storage_path: None,
        };
        
        let mut vault = QuantumVault::new(config).await.expect("Failed to create vault");
        
        let original_keypair = vault.get_current_lamport_keypair()
            .await.expect("No Lamport keypair");
        
        vault.rotate_keys().await.expect("Failed to rotate keys");
        
        let new_keypair = vault.get_current_lamport_keypair()
            .await.expect("No Lamport keypair after rotation");
        
        // Public keys should be different after rotation
        assert_ne!(
            original_keypair.public_key().verification_keys[0],
            new_keypair.public_key().verification_keys[0]
        );
    }
}