syntarq-cli 0.1.0

Command-line interface for Syntarq identity management
//! Test crypto command - Tests cryptographic functions

use crate::ui;
use anyhow::{Context, Result};
use colored::Colorize;
use syntarq_core::crypto::{aes_gcm::AesGcmCipher, argon2, hashing::HashAlgorithm, traits::Cipher};

/// Run cryptographic tests
pub async fn run() -> Result<()> {
    ui::header("๐Ÿงช Testing Cryptographic Functions");

    let mut passed = 0;
    let mut failed = 0;

    // Test 1: Password Hashing with Argon2id
    ui::section("Test 1: Password Hashing (Argon2id)");
    match test_password_hashing().await {
        Ok(_) => {
            ui::success("Password hashing works correctly");
            passed += 1;
        }
        Err(e) => {
            ui::error(&format!("Password hashing failed: {}", e));
            failed += 1;
        }
    }

    // Test 2: AES-256-GCM Encryption
    ui::section("Test 2: AES-256-GCM Encryption");
    match test_aes_encryption().await {
        Ok(_) => {
            ui::success("AES-256-GCM encryption works correctly");
            passed += 1;
        }
        Err(e) => {
            ui::error(&format!("AES encryption failed: {}", e));
            failed += 1;
        }
    }

    // Test 3: Hashing (SHA-256 and BLAKE3)
    ui::section("Test 3: Cryptographic Hashing");
    match test_hashing().await {
        Ok(_) => {
            ui::success("Hashing algorithms work correctly");
            passed += 1;
        }
        Err(e) => {
            ui::error(&format!("Hashing failed: {}", e));
            failed += 1;
        }
    }

    // Test 4: Identity Creation
    ui::section("Test 4: Identity Creation");
    match test_identity_creation().await {
        Ok(_) => {
            ui::success("Identity creation works correctly");
            passed += 1;
        }
        Err(e) => {
            ui::error(&format!("Identity creation failed: {}", e));
            failed += 1;
        }
    }

    // Test 5: Service Key Derivation
    ui::section("Test 5: Service Key Derivation");
    match test_service_keys().await {
        Ok(_) => {
            ui::success("Service key derivation works correctly");
            passed += 1;
        }
        Err(e) => {
            ui::error(&format!("Service key derivation failed: {}", e));
            failed += 1;
        }
    }

    // Test 6: Post-Quantum Crypto (if enabled)
    #[cfg(feature = "post-quantum")]
    {
        ui::section("Test 6: Post-Quantum Cryptography");
        match test_post_quantum().await {
            Ok(_) => {
                ui::success("Post-quantum crypto works correctly");
                passed += 1;
            }
            Err(e) => {
                ui::error(&format!("Post-quantum crypto failed: {}", e));
                failed += 1;
            }
        }
    }

    // Summary
    ui::separator();
    ui::section("Test Summary");
    ui::kv_pair("Passed", &format!("{}", passed).green().to_string());
    if failed > 0 {
        ui::kv_pair("Failed", &format!("{}", failed).red().to_string());
    } else {
        ui::kv_pair("Failed", &format!("{}", failed));
    }

    if failed == 0 {
        ui::separator();
        ui::success("All cryptographic tests passed! โœ…");
    } else {
        ui::separator();
        ui::error(&format!("{} test(s) failed", failed));
    }

    Ok(())
}

async fn test_password_hashing() -> Result<()> {
    let password = b"test_password_123";

    // Hash password
    let hash = argon2::hash_password(password).context("Failed to hash password")?;
    ui::info(&format!("  Hash length: {} bytes", hash.len()));

    // Verify correct password
    let valid = argon2::verify_password(password, &hash).context("Failed to verify password")?;
    if !valid {
        anyhow::bail!("Password verification failed for correct password");
    }
    ui::info("  Correct password verified โœ“");

    // Verify wrong password
    let wrong_password = b"wrong_password";
    let valid = argon2::verify_password(wrong_password, &hash)?;
    if valid {
        anyhow::bail!("Wrong password was incorrectly verified as valid");
    }
    ui::info("  Wrong password rejected โœ“");

    Ok(())
}

async fn test_aes_encryption() -> Result<()> {
    let data = b"Hello, Syntarq! This is a secret message.";

    // Generate cipher
    let cipher = AesGcmCipher::generate();
    ui::info("  Cipher generated");

    // Encrypt
    let encrypted = cipher.encrypt(data).context("Failed to encrypt data")?;
    ui::info(&format!(
        "  Encrypted: {} bytes -> {} bytes",
        data.len(),
        encrypted.total_size()
    ));

    // Decrypt
    let decrypted = cipher
        .decrypt(&encrypted)
        .context("Failed to decrypt data")?;
    ui::info(&format!("  Decrypted: {} bytes", decrypted.len()));

    // Verify
    if data.as_slice() != decrypted.as_slice() {
        anyhow::bail!("Decrypted data doesn't match original");
    }
    ui::info("  Data integrity verified โœ“");

    // Test with AAD
    let aad = b"metadata";
    let encrypted_aad = cipher
        .encrypt_with_ad(data, aad)
        .context("Failed to encrypt with AAD")?;
    let decrypted_aad = cipher
        .decrypt_with_ad(&encrypted_aad, aad)
        .context("Failed to decrypt with AAD")?;

    if data.as_slice() != decrypted_aad.as_slice() {
        anyhow::bail!("AAD decrypted data doesn't match original");
    }
    ui::info("  AAD encryption verified โœ“");

    Ok(())
}

async fn test_hashing() -> Result<()> {
    let data = b"Test data for hashing";

    // SHA-256
    let sha256_hash = syntarq_core::crypto::hashing::hash_data(data, HashAlgorithm::Sha256)?;
    ui::info(&format!("  SHA-256: {} bytes", sha256_hash.len()));
    if sha256_hash.len() != 32 {
        anyhow::bail!("SHA-256 hash has incorrect length");
    }

    // BLAKE3
    let blake3_hash = syntarq_core::crypto::hashing::hash_data(data, HashAlgorithm::Blake3)?;
    ui::info(&format!("  BLAKE3: {} bytes", blake3_hash.len()));
    if blake3_hash.len() != 32 {
        anyhow::bail!("BLAKE3 hash has incorrect length");
    }

    // Verify determinism
    let sha256_hash2 = syntarq_core::crypto::hashing::hash_data(data, HashAlgorithm::Sha256)?;
    if sha256_hash != sha256_hash2 {
        anyhow::bail!("Hashing is not deterministic");
    }
    ui::info("  Deterministic hashing verified โœ“");

    Ok(())
}

async fn test_identity_creation() -> Result<()> {
    use syntarq_core::identity::SyntarqIdentity;

    let password = "test_identity_password_123";

    // Create identity
    let (identity, _hash) =
        SyntarqIdentity::create(password).context("Failed to create identity")?;
    ui::info(&format!("  Identity created: {}", identity.id()));

    // Unlock identity
    let unlocked = SyntarqIdentity::unlock(identity.id(), password, &_hash, None)
        .context("Failed to unlock identity")?;
    ui::info("  Identity unlocked โœ“");

    // Verify IDs match
    if identity.id() != unlocked.id() {
        anyhow::bail!("Identity IDs don't match after unlock");
    }
    ui::info("  Identity persistence verified โœ“");

    Ok(())
}

async fn test_service_keys() -> Result<()> {
    use syntarq_core::identity::{ServiceType, SyntarqIdentity};

    let password = "service_key_test_123";
    let (identity, _) = SyntarqIdentity::create(password).context("Failed to create identity")?;

    // Derive keys for all services
    for service_type in ServiceType::all() {
        let key = identity
            .derive_service_key(*service_type)
            .context("Failed to derive service key")?;
        ui::info(&format!(
            "  {} key: {} bytes",
            service_type,
            key.as_bytes().len()
        ));

        if key.as_bytes().len() != 32 {
            anyhow::bail!("Service key has incorrect length");
        }
    }

    // Verify determinism
    let key1 = identity.derive_service_key(ServiceType::Pass)?;
    let key2 = identity.derive_service_key(ServiceType::Pass)?;
    if key1.as_bytes() != key2.as_bytes() {
        anyhow::bail!("Service key derivation is not deterministic");
    }
    ui::info("  Deterministic key derivation verified โœ“");

    Ok(())
}

#[cfg(feature = "post-quantum")]
async fn test_post_quantum() -> Result<()> {
    use syntarq_core::crypto::pq::{
        dilithium::DilithiumSigner, hybrid::HybridCipher, kyber::KyberCipher,
    };

    // Test Kyber
    ui::info("  Testing Kyber-1024...");
    let kyber = KyberCipher::new().context("Failed to create Kyber cipher")?;
    let (pk, sk) = kyber
        .generate_keypair()
        .context("Failed to generate Kyber keypair")?;
    let (ct, ss1) = kyber
        .encapsulate(&pk)
        .context("Failed to encapsulate with Kyber")?;
    let ss2 = kyber
        .decapsulate(&sk, &ct)
        .context("Failed to decapsulate with Kyber")?;

    if ss1.as_bytes() != ss2.as_bytes() {
        anyhow::bail!("Kyber shared secrets don't match");
    }
    ui::info("    Kyber KEM working โœ“");

    // Test Dilithium
    ui::info("  Testing Dilithium5...");
    let signer = DilithiumSigner::new().context("Failed to create Dilithium signer")?;
    let (pk, sk) = signer
        .generate_keypair()
        .context("Failed to generate Dilithium keypair")?;
    let message = b"Test message for signing";
    let signature = signer
        .sign(message, &sk)
        .context("Failed to sign message")?;
    let valid = signer
        .verify(message, &signature, &pk)
        .context("Failed to verify signature")?;

    if !valid {
        anyhow::bail!("Dilithium signature verification failed");
    }
    ui::info("    Dilithium signatures working โœ“");

    // Test Hybrid Encryption
    ui::info("  Testing Hybrid Encryption...");
    let cipher = HybridCipher::new().context("Failed to create hybrid cipher")?;
    let (pk, sk) = cipher
        .generate_keypair()
        .context("Failed to generate hybrid keypair")?;
    let data = b"Quantum-resistant secret data";
    let ciphertext = cipher
        .hybrid_encrypt(data, &pk)
        .context("Failed to hybrid encrypt")?;
    let plaintext = cipher
        .hybrid_decrypt(&ciphertext, &sk)
        .context("Failed to hybrid decrypt")?;

    if data.as_slice() != plaintext.as_slice() {
        anyhow::bail!("Hybrid decrypted data doesn't match original");
    }
    ui::info("    Hybrid encryption working โœ“");

    Ok(())
}