secretumvault/tests/pqc_end_to_end.rs
Jesús Pérez 91eefc86fa
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
chore: upgrade README and add CHANGELOG with production PQC status
- Add badges, competitive comparison, and 30-sec demo to README
  - Add Production Status section showing OQS backend is production-ready
  - Mark PQC KEM/signing operations complete in roadmap
  - Fix GitHub URL
  - Create CHANGELOG.md documenting all recent changes

  Positions SecretumVault as first Rust vault with production PQC.
2026-01-21 10:45:44 +00:00

417 lines
12 KiB
Rust

//! End-to-end integration tests for post-quantum cryptography
//!
//! Tests cover:
//! - ML-KEM-768 key encapsulation with Transit engine
//! - ML-DSA-65 certificate generation with PKI engine
//! - Hybrid mode (classical + PQC) cryptography
//! - Backward compatibility with classical-only mode
#![cfg(all(test, feature = "pqc"))]
use secretumvault::config::OqsCryptoConfig;
use secretumvault::crypto::hybrid::{HybridKem, HybridSignature};
use secretumvault::crypto::oqs_backend::OqsBackend;
use secretumvault::crypto::{CryptoBackend, HybridKeyPair, KeyAlgorithm};
#[tokio::test]
async fn test_oqs_backend_ml_kem_768_full_cycle() {
// Initialize OQS backend
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
// Generate ML-KEM-768 keypair
let keypair = backend
.generate_keypair(KeyAlgorithm::MlKem768)
.await
.expect("ML-KEM-768 key generation failed");
// Verify NIST-compliant key sizes
assert_eq!(
keypair.public_key.key_data.len(),
1184,
"ML-KEM-768 public key must be 1184 bytes"
);
assert_eq!(
keypair.private_key.key_data.len(),
2400,
"ML-KEM-768 private key must be 2400 bytes"
);
// KEM encapsulation
let (ciphertext, shared_secret_1) = backend
.kem_encapsulate(&keypair.public_key)
.await
.expect("ML-KEM-768 encapsulation failed");
assert_eq!(
ciphertext.len(),
1088,
"ML-KEM-768 ciphertext must be 1088 bytes"
);
assert_eq!(shared_secret_1.len(), 32, "Shared secret must be 32 bytes");
// KEM decapsulation
let shared_secret_2 = backend
.kem_decapsulate(&keypair.private_key, &ciphertext)
.await
.expect("ML-KEM-768 decapsulation failed");
// Shared secrets must match
assert_eq!(
shared_secret_1, shared_secret_2,
"KEM shared secrets must match"
);
}
#[tokio::test]
async fn test_oqs_backend_ml_dsa_65_full_cycle() {
// Initialize OQS backend
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
// Generate ML-DSA-65 keypair
let keypair = backend
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.expect("ML-DSA-65 key generation failed");
// Verify NIST-compliant key sizes
assert_eq!(
keypair.public_key.key_data.len(),
1952,
"ML-DSA-65 public key must be 1952 bytes"
);
assert_eq!(
keypair.private_key.key_data.len(),
4032,
"ML-DSA-65 private key must be 4032 bytes"
);
let message = b"Test message for ML-DSA-65 signature verification";
// Sign
let signature = backend
.sign(&keypair.private_key, message)
.await
.expect("ML-DSA-65 signing failed");
assert!(!signature.is_empty(), "Signature must not be empty");
// Verify valid signature
let is_valid = backend
.verify(&keypair.public_key, message, &signature)
.await
.expect("ML-DSA-65 verification failed");
assert!(is_valid, "Valid signature must verify");
// Tamper signature and verify it fails
let mut tampered_sig = signature.clone();
tampered_sig[0] ^= 0xFF;
let is_valid = backend
.verify(&keypair.public_key, message, &tampered_sig)
.await
.unwrap_or(false);
assert!(!is_valid, "Tampered signature must not verify");
}
#[tokio::test]
async fn test_hybrid_signature_end_to_end() {
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
// Generate keypairs (using ML-DSA for both classical and PQC for simplicity)
let classical_keypair = backend
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.expect("Classical key generation failed");
let pqc_keypair = backend
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.expect("PQC key generation failed");
let message = b"Hybrid signature test message";
// Sign with hybrid mode
let hybrid_sig = HybridSignature::sign(
&backend,
&classical_keypair.private_key,
&pqc_keypair.private_key,
message,
)
.await
.expect("Hybrid signing failed");
// Verify wire format
assert!(hybrid_sig.len() > 5, "Hybrid signature must include header");
assert_eq!(hybrid_sig[0], 1, "Version byte must be 1");
// Verify valid hybrid signature
let is_valid = HybridSignature::verify(
&backend,
&classical_keypair.public_key,
&pqc_keypair.public_key,
message,
&hybrid_sig,
)
.await
.expect("Hybrid verification failed");
assert!(is_valid, "Valid hybrid signature must verify");
// Tamper signature and verify it fails
let mut tampered = hybrid_sig.clone();
tampered[20] ^= 0xFF;
let is_valid = HybridSignature::verify(
&backend,
&classical_keypair.public_key,
&pqc_keypair.public_key,
message,
&tampered,
)
.await
.unwrap_or(false);
assert!(!is_valid, "Tampered hybrid signature must not verify");
}
#[tokio::test]
async fn test_hybrid_kem_end_to_end() {
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
// Generate keypairs
let pqc_keypair = backend
.generate_keypair(KeyAlgorithm::MlKem768)
.await
.expect("PQC key generation failed");
// Use ML-DSA as placeholder for classical (in real scenario: RSA/ECDSA)
let classical_keypair = backend
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.expect("Classical key generation failed");
// Hybrid KEM encapsulation
let (ciphertext, shared_secret_1) = HybridKem::encapsulate(
&backend,
&classical_keypair.public_key,
&pqc_keypair.public_key,
)
.await
.expect("Hybrid KEM encapsulation failed");
// Verify wire format
assert!(
ciphertext.len() > 5,
"Hybrid KEM ciphertext must include header"
);
assert_eq!(ciphertext[0], 1, "Version byte must be 1");
assert_eq!(shared_secret_1.len(), 32, "Shared secret must be 32 bytes");
// Hybrid KEM decapsulation
let shared_secret_2 = HybridKem::decapsulate(
&backend,
&classical_keypair.private_key,
&pqc_keypair.private_key,
&ciphertext,
)
.await
.expect("Hybrid KEM decapsulation failed");
// Shared secrets must match
assert_eq!(
shared_secret_1, shared_secret_2,
"Hybrid KEM shared secrets must match"
);
}
#[tokio::test]
async fn test_pqc_no_fake_crypto() {
// This test verifies that we're NOT using rand::fill_bytes() for cryptographic
// operations
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
// Generate two ML-KEM-768 keypairs
let keypair1 = backend
.generate_keypair(KeyAlgorithm::MlKem768)
.await
.expect("First key generation failed");
let keypair2 = backend
.generate_keypair(KeyAlgorithm::MlKem768)
.await
.expect("Second key generation failed");
// Keys must be different (not filled with random bytes deterministically)
assert_ne!(
keypair1.public_key.key_data, keypair2.public_key.key_data,
"Public keys must be different"
);
assert_ne!(
keypair1.private_key.key_data, keypair2.private_key.key_data,
"Private keys must be different"
);
// Encapsulate with keypair1, try to decapsulate with keypair2 - must fail
let (ciphertext, _shared_secret_1) = backend
.kem_encapsulate(&keypair1.public_key)
.await
.expect("Encapsulation failed");
let result = backend
.kem_decapsulate(&keypair2.private_key, &ciphertext)
.await;
// Should succeed but produce different shared secret (or fail if OQS validates
// keys) The important part is that it's real crypto, not fake random bytes
match result {
Ok(shared_secret_2) => {
// If decapsulation succeeds with wrong key, it's still real crypto
// (some KEM implementations always succeed but produce wrong secret)
assert_eq!(shared_secret_2.len(), 32, "Shared secret must be 32 bytes");
}
Err(_) => {
// If decapsulation fails, that's also acceptable (stricter
// validation)
}
}
}
#[tokio::test]
async fn test_hybrid_keypair_structure() {
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
let classical = backend
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.expect("Classical key generation failed");
let pqc = backend
.generate_keypair(KeyAlgorithm::MlKem768)
.await
.expect("PQC key generation failed");
let hybrid = HybridKeyPair {
classical: classical.clone(),
pqc: pqc.clone(),
};
// Verify structure
assert_eq!(hybrid.classical.algorithm, classical.algorithm);
assert_eq!(hybrid.pqc.algorithm, pqc.algorithm);
assert_eq!(
hybrid.classical.public_key.key_data,
classical.public_key.key_data
);
assert_eq!(hybrid.pqc.public_key.key_data, pqc.public_key.key_data);
// Verify serialization/deserialization
let serialized = serde_json::to_string(&hybrid).expect("Serialization failed");
let deserialized: HybridKeyPair =
serde_json::from_str(&serialized).expect("Deserialization failed");
assert_eq!(
hybrid.classical.public_key.key_data,
deserialized.classical.public_key.key_data
);
assert_eq!(
hybrid.pqc.public_key.key_data,
deserialized.pqc.public_key.key_data
);
}
#[tokio::test]
async fn test_config_validation() {
use secretumvault::config::{AwsLcCryptoConfig, OqsCryptoConfig};
// AWS-LC config: hybrid_mode requires enable_pqc
let invalid_config = AwsLcCryptoConfig {
enable_pqc: false,
hybrid_mode: true,
};
assert!(
invalid_config.validate().is_err(),
"hybrid_mode without enable_pqc must fail validation"
);
let valid_config = AwsLcCryptoConfig {
enable_pqc: true,
hybrid_mode: true,
};
// This will fail if pqc feature not enabled, but that's expected
let _ = valid_config.validate();
// OQS config validation
let oqs_config = OqsCryptoConfig { enable_pqc: true };
// This will fail if pqc feature not enabled, but that's expected
let _ = oqs_config.validate();
}
#[tokio::test]
async fn test_symmetric_encryption_compatibility() {
// Verify that symmetric encryption (AES-256-GCM, ChaCha20-Poly1305) still works
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
let key = backend
.random_bytes(32)
.await
.expect("Random bytes generation failed");
let plaintext = b"Symmetric encryption test";
// Test AES-256-GCM
let ciphertext = backend
.encrypt_symmetric(
&key,
plaintext,
secretumvault::crypto::SymmetricAlgorithm::Aes256Gcm,
)
.await
.expect("AES-256-GCM encryption failed");
let decrypted = backend
.decrypt_symmetric(
&key,
&ciphertext,
secretumvault::crypto::SymmetricAlgorithm::Aes256Gcm,
)
.await
.expect("AES-256-GCM decryption failed");
assert_eq!(plaintext.as_slice(), decrypted.as_slice());
// Test ChaCha20-Poly1305
let ciphertext = backend
.encrypt_symmetric(
&key,
plaintext,
secretumvault::crypto::SymmetricAlgorithm::ChaCha20Poly1305,
)
.await
.expect("ChaCha20-Poly1305 encryption failed");
let decrypted = backend
.decrypt_symmetric(
&key,
&ciphertext,
secretumvault::crypto::SymmetricAlgorithm::ChaCha20Poly1305,
)
.await
.expect("ChaCha20-Poly1305 decryption failed");
assert_eq!(plaintext.as_slice(), decrypted.as_slice());
}
#[tokio::test]
async fn test_health_check() {
let config = OqsCryptoConfig { enable_pqc: true };
let backend = OqsBackend::new(&config).expect("OQS backend creation failed");
backend.health_check().await.expect("Health check failed");
}