//! Comprehensive RSA timing vulnerability assessment for RUSTSEC-2023-0071
//!
//! This test suite investigates the Marvin Attack timing vulnerability in the `rsa` crate
//! and determines if a bug report with new findings is warranted.
//!
//! Key questions:
//! 1. Is there a padding oracle (Bleichenbacher-style)?
//! 2. How does it compare to ring's RSA signing?
//! 3. How many measurements are needed to exploit?

// Use rsa crate's re-exported rand to avoid version conflicts
use rsa::rand_core::OsRng;
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
use std::cell::Cell;
use std::hint::black_box;
use std::time::{Duration, Instant};
use tacet::{helpers::InputPair, output::format_outcome, AttackerModel, Outcome, TimingOracle};

// Helper for random byte generation using rsa's rand
fn random_bytes<const N: usize>() -> [u8; N] {
    use rsa::rand_core::RngCore;
    let mut bytes = [0u8; N];
    OsRng.fill_bytes(&mut bytes);
    bytes
}

fn random_u8() -> u8 {
    use rsa::rand_core::RngCore;
    let mut byte = [0u8; 1];
    OsRng.fill_bytes(&mut byte);
    byte[0]
}

fn random_usize() -> usize {
    use rsa::rand_core::RngCore;
    let mut bytes = [0u8; 8];
    OsRng.fill_bytes(&mut bytes);
    usize::from_le_bytes(bytes)
}

// =============================================================================
// PHASE 1: PADDING ORACLE ASSESSMENT
// =============================================================================

/// P1: Test if valid vs invalid ciphertexts have different decryption timing
///
/// This is the most critical test. If there's a timing difference between
/// valid and invalid ciphertexts, it enables Bleichenbacher-style attacks.
#[test]
#[ignore] // Requires sudo for kperf
fn exp_p1_padding_oracle_basic() {
    println!("\n=== P1: Padding Oracle Basic Test ===");
    println!("Testing: Valid ciphertexts vs Random bytes (invalid)");
    println!("rsa crate version: 0.9.9 (num-bigint-dig, VULNERABLE to Marvin)");
    println!();

    // Generate RSA-1024 key (faster for testing)
    let private_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("key generation failed");
    let public_key = RsaPublicKey::from(&private_key);

    // Generate pool of VALID ciphertexts (will decrypt successfully)
    let valid_pool: Vec<Vec<u8>> = (0..100)
        .map(|_| {
            let msg: [u8; 32] = random_bytes::<32>();
            public_key
                .encrypt(&mut OsRng, Pkcs1v15Encrypt, &msg)
                .expect("encryption failed")
        })
        .collect();

    // Generate pool of INVALID ciphertexts (random bytes, will fail to decrypt)
    // These are modulus-length random bytes - guaranteed invalid PKCS#1 padding
    let key_len = 128; // 1024 bits = 128 bytes
    let invalid_pool: Vec<Vec<u8>> = (0..100)
        .map(|_| {
            let mut ct: Vec<u8> = (0..key_len).map(|_| random_u8()).collect();
            // Ensure it's less than modulus (set MSB to ensure valid range)
            ct[0] &= 0x7F;
            ct
        })
        .collect();

    let valid_idx = Cell::new(0usize);
    let invalid_idx = Cell::new(0usize);

    let valid_pool_clone = valid_pool.clone();
    let invalid_pool_clone = invalid_pool.clone();

    let inputs = InputPair::new(
        move || {
            let i = valid_idx.get();
            valid_idx.set((i + 1) % 100);
            valid_pool_clone[i].clone()
        },
        move || {
            let i = invalid_idx.get();
            invalid_idx.set((i + 1) % 100);
            invalid_pool_clone[i].clone()
        },
    );

    // Use SharedHardware model for maximum sensitivity
    let outcome = TimingOracle::for_attacker(AttackerModel::SharedHardware)
        .time_budget(Duration::from_secs(60))
        .max_samples(50_000)
        .test(inputs, |ct| {
            // Decrypt and ignore the result (we're measuring timing of both success and failure)
            let result = private_key.decrypt(Pkcs1v15Encrypt, ct);
            black_box(result.is_ok());
        });

    println!("{}", format_outcome(&outcome));

    // Analyze the result
    match &outcome {
        Outcome::Fail {
            leak_probability,
            effect,
            ..
        } => {
            println!("\n🚨 PADDING ORACLE DETECTED!");
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
            println!("Effect size: {:.1} ns", effect.max_effect_ns);
            println!("This indicates Bleichenbacher-style attack may be possible.");
        }
        Outcome::Pass {
            leak_probability, ..
        } => {
            println!("\n✅ No padding oracle timing difference detected");
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
        }
        Outcome::Inconclusive {
            reason,
            leak_probability,
            ..
        } => {
            println!("\n⚠️ Inconclusive: {:?}", reason);
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
        }
        Outcome::Unmeasurable { recommendation, .. } => {
            println!("\n⚠️ Unmeasurable: {}", recommendation);
        }
        &Outcome::Research(_) => {}
    }
}

/// P1b: More controlled padding oracle test - measure decryption only (not error handling)
///
/// This variant times only the modular exponentiation part by catching the error
/// at a known point.
#[test]
#[ignore]
fn exp_p1b_padding_oracle_controlled() {
    println!("\n=== P1b: Padding Oracle Controlled Test ===");
    println!("Testing: Bit-flipped valid ciphertexts (subtle invalidity)");
    println!();

    let private_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("key generation failed");
    let public_key = RsaPublicKey::from(&private_key);

    // Generate pool of VALID ciphertexts
    let valid_pool: Vec<Vec<u8>> = (0..100)
        .map(|_| {
            let msg: [u8; 32] = random_bytes::<32>();
            public_key
                .encrypt(&mut OsRng, Pkcs1v15Encrypt, &msg)
                .expect("encryption failed")
        })
        .collect();

    // Generate pool of INVALID ciphertexts by bit-flipping valid ones
    // This creates ciphertexts that look "real" but have invalid padding
    let invalid_pool: Vec<Vec<u8>> = valid_pool
        .iter()
        .map(|ct| {
            let mut flipped = ct.clone();
            // Flip a random bit in the middle of the ciphertext
            let pos = 64 + (random_usize() % 32);
            flipped[pos] ^= 1 << (random_u8() % 8);
            flipped
        })
        .collect();

    let valid_idx = Cell::new(0usize);
    let invalid_idx = Cell::new(0usize);

    let valid_pool_clone = valid_pool.clone();
    let invalid_pool_clone = invalid_pool.clone();

    let inputs = InputPair::new(
        move || {
            let i = valid_idx.get();
            valid_idx.set((i + 1) % 100);
            valid_pool_clone[i].clone()
        },
        move || {
            let i = invalid_idx.get();
            invalid_idx.set((i + 1) % 100);
            invalid_pool_clone[i].clone()
        },
    );

    let outcome = TimingOracle::for_attacker(AttackerModel::SharedHardware)
        .time_budget(Duration::from_secs(60))
        .max_samples(50_000)
        .test(inputs, |ct| {
            let result = private_key.decrypt(Pkcs1v15Encrypt, ct);
            black_box(result.is_ok());
        });

    println!("{}", format_outcome(&outcome));

    match &outcome {
        Outcome::Fail {
            leak_probability,
            effect,
            ..
        } => {
            println!("\n🚨 PADDING ORACLE DETECTED (bit-flip variant)!");
            println!("Effect size: {:.1} ns", effect.max_effect_ns);
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
        }
        Outcome::Pass { .. } => {
            println!("\n✅ No padding oracle (bit-flip variant)");
        }
        _ => {}
    }
}

// =============================================================================
// PHASE 2: SIGNING COMPARISON
// =============================================================================

/// R1: Test RSA signing timing with the rsa crate
#[test]
#[ignore]
fn exp_r1_rsa_crate_signing() {
    use rsa::pkcs1v15::SigningKey;
    use rsa::signature::{RandomizedSigner, SignatureEncoding};

    println!("\n=== R1: RSA Signing Timing (rsa crate) ===");
    println!("Testing: Different messages -> different signing times?");
    println!();

    let private_key = RsaPrivateKey::new(&mut OsRng, 2048).expect("key generation failed");
    let signing_key = SigningKey::<sha2::Sha256>::new_unprefixed(private_key);

    // Generate pools of different messages
    let pool_a: Vec<[u8; 32]> = (0..100).map(|_| random_bytes::<32>()).collect();
    let pool_b: Vec<[u8; 32]> = (0..100).map(|_| random_bytes::<32>()).collect();

    let idx_a = Cell::new(0usize);
    let idx_b = Cell::new(0usize);

    let pool_a_clone = pool_a.clone();
    let pool_b_clone = pool_b.clone();

    let inputs = InputPair::new(
        move || {
            let i = idx_a.get();
            idx_a.set((i + 1) % 100);
            pool_a_clone[i]
        },
        move || {
            let i = idx_b.get();
            idx_b.set((i + 1) % 100);
            pool_b_clone[i]
        },
    );

    let outcome = TimingOracle::for_attacker(AttackerModel::SharedHardware)
        .time_budget(Duration::from_secs(60))
        .max_samples(20_000)
        .test(inputs, |msg| {
            let signature = signing_key.sign_with_rng(&mut OsRng, msg);
            black_box(signature.to_bytes());
        });

    println!("{}", format_outcome(&outcome));

    match &outcome {
        Outcome::Fail {
            leak_probability,
            effect,
            ..
        } => {
            println!("\n🚨 Signing timing leak detected (rsa crate)!");
            println!("Effect size: {:.1} ns", effect.max_effect_ns);
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
        }
        Outcome::Pass { .. } => {
            println!("\n✅ No signing timing leak (rsa crate)");
        }
        _ => {}
    }
}

/// R1b: Single message signing variance (is blinding working?)
#[test]
#[ignore]
fn exp_r1b_signing_variance() {
    use rsa::pkcs1v15::SigningKey;
    use rsa::signature::{RandomizedSigner, SignatureEncoding};

    println!("\n=== R1b: Signing Variance Test ===");
    println!("Measuring variance of signing the same message repeatedly");
    println!();

    let private_key = RsaPrivateKey::new(&mut OsRng, 2048).expect("key generation failed");
    let signing_key = SigningKey::<sha2::Sha256>::new_unprefixed(private_key);

    let message: [u8; 32] = random_bytes::<32>();

    // Warmup
    for _ in 0..100 {
        let _ = signing_key.sign_with_rng(&mut OsRng, &message);
    }

    // Measure
    let mut timings: Vec<f64> = Vec::with_capacity(1000);
    for _ in 0..1000 {
        let start = Instant::now();
        let signature = signing_key.sign_with_rng(&mut OsRng, &message);
        let elapsed = start.elapsed().as_nanos() as f64;
        black_box(signature.to_bytes());
        timings.push(elapsed);
    }

    let mean = timings.iter().sum::<f64>() / timings.len() as f64;
    let variance = timings.iter().map(|t| (t - mean).powi(2)).sum::<f64>() / timings.len() as f64;
    let std_dev = variance.sqrt();
    let cv = std_dev / mean * 100.0;

    timings.sort_by(|a, b| a.partial_cmp(b).unwrap());
    let min = timings[0];
    let max = timings[timings.len() - 1];
    let median = timings[timings.len() / 2];

    println!("Results (1000 signatures of same message):");
    println!("  Mean:   {:.0} ns ({:.2} ms)", mean, mean / 1_000_000.0);
    println!("  Median: {:.0} ns", median);
    println!("  StdDev: {:.0} ns", std_dev);
    println!("  CV:     {:.2}%", cv);
    println!("  Range:  {:.0} - {:.0} ns", min, max);
    println!();

    if cv > 0.5 {
        println!("✅ High variance suggests blinding is active");
    } else {
        println!("⚠️ Low variance - blinding may not be effective");
    }
}

/// R2: Test RSA signing with ring crate (BoringSSL-based)
#[test]
#[ignore]
fn exp_r2_ring_signing() {
    use ring::rand::SystemRandom;
    use ring::signature::{self, RsaKeyPair};

    println!("\n=== R2: RSA Signing Timing (ring crate) ===");
    println!("Testing: Different messages -> different signing times?");
    println!();

    // Generate RSA key pair using ring
    // ring requires PKCS#8 DER format, so we generate with rsa crate and convert
    let rsa_key = RsaPrivateKey::new(&mut OsRng, 2048).expect("key generation failed");
    let pkcs8_der =
        rsa::pkcs8::EncodePrivateKey::to_pkcs8_der(&rsa_key).expect("pkcs8 encode failed");

    let key_pair = RsaKeyPair::from_pkcs8(pkcs8_der.as_bytes()).expect("ring key parse failed");
    let rng = SystemRandom::new();

    // Generate pools of different messages
    let pool_a: Vec<[u8; 32]> = (0..100).map(|_| random_bytes::<32>()).collect();
    let pool_b: Vec<[u8; 32]> = (0..100).map(|_| random_bytes::<32>()).collect();

    let idx_a = Cell::new(0usize);
    let idx_b = Cell::new(0usize);

    let pool_a_clone = pool_a.clone();
    let pool_b_clone = pool_b.clone();

    let inputs = InputPair::new(
        move || {
            let i = idx_a.get();
            idx_a.set((i + 1) % 100);
            pool_a_clone[i]
        },
        move || {
            let i = idx_b.get();
            idx_b.set((i + 1) % 100);
            pool_b_clone[i]
        },
    );

    let outcome = TimingOracle::for_attacker(AttackerModel::SharedHardware)
        .time_budget(Duration::from_secs(60))
        .max_samples(20_000)
        .test(inputs, |msg| {
            let mut signature = vec![0u8; key_pair.public().modulus_len()];
            key_pair
                .sign(&signature::RSA_PKCS1_SHA256, &rng, msg, &mut signature)
                .expect("signing failed");
            black_box(signature);
        });

    println!("{}", format_outcome(&outcome));

    match &outcome {
        Outcome::Fail {
            leak_probability,
            effect,
            ..
        } => {
            println!("\n🚨 Signing timing leak detected (ring crate)!");
            println!("Effect size: {:.1} ns", effect.max_effect_ns);
            println!("Leak probability: {:.1}%", leak_probability * 100.0);
        }
        Outcome::Pass { .. } => {
            println!("\n✅ No signing timing leak (ring crate) - BoringSSL is constant-time");
        }
        _ => {}
    }
}

// =============================================================================
// PHASE 3: ATTACK FEASIBILITY
// =============================================================================

/// A1: Determine how many measurements are needed to distinguish ciphertexts
#[test]
#[ignore]
fn exp_a1_measurements_to_distinguish() {
    println!("\n=== A1: Measurements Required to Distinguish Ciphertexts ===");
    println!();

    let private_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("key generation failed");
    let public_key = RsaPublicKey::from(&private_key);

    // Generate two specific ciphertexts
    let msg_a: [u8; 32] = random_bytes::<32>();
    let msg_b: [u8; 32] = random_bytes::<32>();

    let ct_a = public_key
        .encrypt(&mut OsRng, Pkcs1v15Encrypt, &msg_a)
        .unwrap();
    let ct_b = public_key
        .encrypt(&mut OsRng, Pkcs1v15Encrypt, &msg_b)
        .unwrap();

    println!("Collecting timing samples for two ciphertexts...");

    // Warmup
    for _ in 0..100 {
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_a);
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_b);
    }

    // Collect many samples
    let n_samples = 2000;
    let mut times_a: Vec<f64> = Vec::with_capacity(n_samples);
    let mut times_b: Vec<f64> = Vec::with_capacity(n_samples);

    for _ in 0..n_samples {
        // Interleave measurements
        let start = Instant::now();
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_a);
        times_a.push(start.elapsed().as_nanos() as f64);

        let start = Instant::now();
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_b);
        times_b.push(start.elapsed().as_nanos() as f64);
    }

    let mean_a = times_a.iter().sum::<f64>() / times_a.len() as f64;
    let mean_b = times_b.iter().sum::<f64>() / times_b.len() as f64;

    let var_a = times_a.iter().map(|t| (t - mean_a).powi(2)).sum::<f64>() / times_a.len() as f64;
    let var_b = times_b.iter().map(|t| (t - mean_b).powi(2)).sum::<f64>() / times_b.len() as f64;

    let std_a = var_a.sqrt();
    let std_b = var_b.sqrt();
    let pooled_std = ((var_a + var_b) / 2.0).sqrt();

    let effect = (mean_a - mean_b).abs();
    let effect_sign = if mean_a > mean_b {
        "CT_A slower"
    } else {
        "CT_B slower"
    };

    println!("\nRaw Statistics:");
    println!("  CT_A: mean = {:.0} ns, std = {:.0} ns", mean_a, std_a);
    println!("  CT_B: mean = {:.0} ns, std = {:.0} ns", mean_b, std_b);
    println!("  Effect: {:.1} ns ({})", effect, effect_sign);
    println!("  Pooled StdDev: {:.1} ns", pooled_std);

    // Calculate measurements needed for various confidence levels
    // Using the formula: n = 2 * (z * σ / δ)²
    // where z is the z-score, σ is pooled std dev, δ is effect size
    println!("\nMeasurements needed to distinguish:");

    for (conf, z) in [(0.90, 1.645), (0.95, 1.96), (0.99, 2.576)] {
        if effect > 0.0 {
            let n = 2.0 * (z * pooled_std / effect).powi(2);
            println!(
                "  {}% confidence: {:.0} measurements per ciphertext",
                conf * 100.0,
                n
            );
        }
    }

    // Welch's t-test
    let se = (var_a / times_a.len() as f64 + var_b / times_b.len() as f64).sqrt();
    let t_stat = (mean_a - mean_b) / se;
    println!(
        "\nWelch's t-statistic: {:.2} (|t| > 1.96 → p < 0.05)",
        t_stat
    );

    if t_stat.abs() > 2.576 {
        println!("Result: HIGHLY SIGNIFICANT (p < 0.01)");
    } else if t_stat.abs() > 1.96 {
        println!("Result: SIGNIFICANT (p < 0.05)");
    } else {
        println!("Result: NOT SIGNIFICANT at α=0.05");
    }
}

/// A1b: Monte Carlo simulation of distinguishing attack
#[test]
#[ignore]
fn exp_a1b_monte_carlo_distinguishing() {
    println!("\n=== A1b: Monte Carlo Distinguishing Simulation ===");
    println!();

    let private_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("key generation failed");
    let public_key = RsaPublicKey::from(&private_key);

    // Generate two ciphertexts
    let ct_a = public_key
        .encrypt(&mut OsRng, Pkcs1v15Encrypt, &random_bytes::<32>())
        .unwrap();
    let ct_b = public_key
        .encrypt(&mut OsRng, Pkcs1v15Encrypt, &random_bytes::<32>())
        .unwrap();

    println!("Collecting 5000 samples per ciphertext...");

    // Collect large sample
    let n_samples = 5000;
    let mut times_a: Vec<f64> = Vec::with_capacity(n_samples);
    let mut times_b: Vec<f64> = Vec::with_capacity(n_samples);

    // Warmup
    for _ in 0..200 {
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_a);
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_b);
    }

    for _ in 0..n_samples {
        let start = Instant::now();
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_a);
        times_a.push(start.elapsed().as_nanos() as f64);

        let start = Instant::now();
        let _ = private_key.decrypt(Pkcs1v15Encrypt, &ct_b);
        times_b.push(start.elapsed().as_nanos() as f64);
    }

    // Determine which is faster on average
    let mean_a: f64 = times_a.iter().sum::<f64>() / n_samples as f64;
    let mean_b: f64 = times_b.iter().sum::<f64>() / n_samples as f64;
    let a_is_faster = mean_a < mean_b;

    println!(
        "Ground truth: CT_{} is faster by {:.1} ns",
        if a_is_faster { "A" } else { "B" },
        (mean_a - mean_b).abs()
    );

    // Simulate attacker with limited measurements
    println!("\nAttacker success rate with N measurements:");

    for n in [10, 25, 50, 100, 200, 500, 1000] {
        let trials = 1000;
        let mut successes = 0;

        for _ in 0..trials {
            // Randomly sample N measurements from each pool
            let sample_a: Vec<f64> = (0..n)
                .map(|_| times_a[random_usize() % n_samples])
                .collect();
            let sample_b: Vec<f64> = (0..n)
                .map(|_| times_b[random_usize() % n_samples])
                .collect();

            let est_mean_a = sample_a.iter().sum::<f64>() / n as f64;
            let est_mean_b = sample_b.iter().sum::<f64>() / n as f64;

            let guess_a_faster = est_mean_a < est_mean_b;
            if guess_a_faster == a_is_faster {
                successes += 1;
            }
        }

        let success_rate = successes as f64 / trials as f64 * 100.0;
        let marker = if success_rate >= 95.0 {
            "✅"
        } else if success_rate >= 80.0 {
            "⚠️"
        } else {
            "❌"
        };
        println!("  N={:4}: {:.1}% success rate {}", n, success_rate, marker);
    }
}

// =============================================================================
// COMPREHENSIVE ASSESSMENT
// =============================================================================

/// Run all assessment tests in sequence and produce summary
#[test]
#[ignore]
fn exp_comprehensive_assessment() {
    println!("\n");
    println!("╔══════════════════════════════════════════════════════════════╗");
    println!("║     RSA TIMING VULNERABILITY COMPREHENSIVE ASSESSMENT        ║");
    println!("║              Related to RUSTSEC-2023-0071                    ║");
    println!("╚══════════════════════════════════════════════════════════════╝");
    println!();
    println!("rsa crate version: 0.9.9 (uses num-bigint-dig)");
    println!("Advisory: CVE-2023-49092 (Marvin Attack)");
    println!("Platform: macOS ARM64");
    println!();

    // Run individual tests and collect results
    // Note: In practice, run these individually with sudo

    println!("To run the full assessment:");
    println!("  sudo -E cargo test --test rsa_vulnerability_assessment -- --ignored --nocapture");
    println!();
    println!("Individual tests:");
    println!("  exp_p1_padding_oracle_basic     - Valid vs invalid ciphertext timing");
    println!("  exp_p1b_padding_oracle_controlled - Bit-flipped ciphertext timing");
    println!("  exp_r1_rsa_crate_signing        - Signing timing (rsa crate)");
    println!("  exp_r1b_signing_variance        - Signing variance check");
    println!("  exp_r2_ring_signing             - Signing timing (ring/BoringSSL)");
    println!("  exp_a1_measurements_to_distinguish - Sample size requirements");
    println!("  exp_a1b_monte_carlo_distinguishing - Attack simulation");
}
