#[cfg(feature = "alloc")]
use alloc::{
boxed::Box,
format,
string::String,
vec,
vec::Vec,
};
use crate::security::CryptoRng;
use crate::types::*;
#[derive(Debug, Clone)]
pub struct FuzzingConfig {
pub max_input_size: usize,
pub min_input_size: usize,
pub include_invalid_inputs: bool,
pub test_edge_cases: bool,
pub iterations: usize,
}
impl Default for FuzzingConfig {
fn default() -> Self {
Self {
max_input_size: 4096,
min_input_size: 0,
include_invalid_inputs: true,
test_edge_cases: true,
iterations: 1000,
}
}
}
pub struct FuzzingInputGenerator {
config: FuzzingConfig,
rng: Box<dyn CryptoRng>,
}
impl FuzzingInputGenerator {
pub fn new(config: FuzzingConfig, rng: Box<dyn CryptoRng>) -> Self {
Self { config, rng }
}
pub fn generate_random_bytes(&mut self, len: usize) -> Vec<u8> {
let mut bytes = vec![0u8; len];
let _ = self.rng.fill_bytes(&mut bytes); bytes
}
pub fn generate_random_key(&mut self, key_len: usize) -> Vec<u8> {
self.generate_random_bytes(key_len)
}
pub fn generate_random_nonce(&mut self, nonce_len: usize) -> Vec<u8> {
self.generate_random_bytes(nonce_len)
}
pub fn generate_random_plaintext(&mut self) -> Vec<u8> {
let len = self.random_length();
self.generate_random_bytes(len)
}
pub fn generate_random_aad(&mut self) -> Vec<u8> {
let len = self.random_length();
self.generate_random_bytes(len)
}
pub fn generate_random_ciphertext(&mut self) -> Vec<u8> {
let len = self.random_length();
self.generate_random_bytes(len)
}
pub fn generate_edge_case_inputs(&mut self) -> Vec<Vec<u8>> {
let mut inputs = Vec::new();
if self.config.test_edge_cases {
inputs.push(Vec::new());
for i in 0..=255 {
inputs.push(vec![i]);
}
inputs.push(vec![0u8; 32]);
inputs.push(vec![0u8; 64]);
inputs.push(vec![0xFFu8; 32]);
inputs.push(vec![0xFFu8; 64]);
inputs.push(
(0..32)
.map(|i| if i % 2 == 0 { 0x00 } else { 0xFF })
.collect(),
);
inputs.push((0..64).map(|i| i as u8).collect());
inputs.push(vec![0x42u8; self.config.max_input_size]);
}
inputs
}
pub fn generate_invalid_inputs(&mut self) -> Vec<Vec<u8>> {
let mut inputs = Vec::new();
if self.config.include_invalid_inputs {
inputs.push(vec![0u8; self.config.max_input_size + 1]);
inputs.push(vec![0u8; self.config.max_input_size * 2]);
inputs.push(vec![0xDEu8, 0xAD, 0xBE, 0xEF]); inputs.push(vec![0xFF; 1000]); }
inputs
}
fn random_length(&mut self) -> usize {
if self.config.max_input_size == self.config.min_input_size {
return self.config.min_input_size;
}
let mut bytes = [0u8; 4];
let _ = self.rng.fill_bytes(&mut bytes); let random_u32 = u32::from_le_bytes(bytes);
let range = self.config.max_input_size - self.config.min_input_size;
self.config.min_input_size + (random_u32 as usize % (range + 1))
}
}
pub struct HpkeFuzzingHarness {
generator: FuzzingInputGenerator,
results: FuzzingResults,
}
impl HpkeFuzzingHarness {
pub fn new(config: FuzzingConfig, rng: Box<dyn CryptoRng>) -> Self {
Self {
generator: FuzzingInputGenerator::new(config, rng),
results: FuzzingResults::new(),
}
}
pub fn fuzz_aead_seal(&mut self, _aead: HpkeAead) -> FuzzingResults {
self.results = FuzzingResults::new();
for _ in 0..self.generator.config.iterations {
let key = self.generator.generate_random_key(32); let nonce = self.generator.generate_random_nonce(16); let plaintext = self.generator.generate_random_plaintext();
let aad = self.generator.generate_random_aad();
let result = self.simulate_aead_operation(&key, &nonce, &plaintext, &aad);
self.results.record_result(result);
}
for input in self.generator.generate_edge_case_inputs() {
let result = self.simulate_aead_operation(&input, &input, &input, &input);
self.results.record_result(result);
}
for input in self.generator.generate_invalid_inputs() {
let result = self.simulate_aead_operation(&input, &input, &input, &input);
self.results.record_result(result);
}
self.results.clone()
}
pub fn fuzz_kem_operations(&mut self, kem: HpkeKem) -> FuzzingResults {
let mut results = FuzzingResults::new();
for _ in 0..self.generator.config.iterations {
let public_key = self.generator.generate_random_key(kem.public_key_len());
let secret_key = self.generator.generate_random_key(kem.secret_key_len());
let encap_result = self.simulate_kem_encapsulate(&public_key);
let decap_result = self.simulate_kem_decapsulate(&secret_key, &public_key);
results.record_result(encap_result);
results.record_result(decap_result);
}
results
}
fn simulate_aead_operation(
&self,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
_aad: &[u8],
) -> FuzzingResult {
if key.is_empty() || nonce.is_empty() {
return FuzzingResult::Error("Empty key or nonce".into());
}
if key.len() != 32 {
return FuzzingResult::Error("Invalid key length".into());
}
if nonce.len() != 16 {
return FuzzingResult::Error("Invalid nonce length".into());
}
if plaintext.len() > 1024 * 1024 {
return FuzzingResult::Error("Plaintext too large".into());
}
FuzzingResult::Success
}
fn simulate_kem_encapsulate(&self, public_key: &[u8]) -> FuzzingResult {
if public_key.is_empty() {
return FuzzingResult::Error("Empty public key".into());
}
if public_key.iter().all(|&b| b == 0) {
return FuzzingResult::Error("All-zero public key".into());
}
FuzzingResult::Success
}
fn simulate_kem_decapsulate(&self, secret_key: &[u8], ciphertext: &[u8]) -> FuzzingResult {
if secret_key.is_empty() || ciphertext.is_empty() {
return FuzzingResult::Error("Empty secret key or ciphertext".into());
}
FuzzingResult::Success
}
}
#[derive(Debug, Clone)]
pub enum FuzzingResult {
Success,
Error(String),
Panic(String),
}
#[derive(Debug, Clone)]
pub struct FuzzingResults {
pub total_tests: usize,
pub successes: usize,
pub errors: usize,
pub panics: usize,
pub error_messages: Vec<String>,
}
impl FuzzingResults {
pub fn new() -> Self {
Self {
total_tests: 0,
successes: 0,
errors: 0,
panics: 0,
error_messages: Vec::new(),
}
}
pub fn record_result(&mut self, result: FuzzingResult) {
self.total_tests += 1;
match result {
FuzzingResult::Success => self.successes += 1,
FuzzingResult::Error(msg) => {
self.errors += 1;
self.error_messages.push(msg);
}
FuzzingResult::Panic(msg) => {
self.panics += 1;
self.error_messages.push(format!("PANIC: {}", msg));
}
}
}
pub fn success_rate(&self) -> f64 {
if self.total_tests == 0 {
return 0.0;
}
self.successes as f64 / self.total_tests as f64
}
pub fn has_panics(&self) -> bool {
self.panics > 0
}
pub fn unique_errors(&self) -> Vec<String> {
let mut unique = self.error_messages.clone();
unique.sort();
unique.dedup();
unique
}
}
impl Default for FuzzingResults {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::test_rng::TestRng;
#[test]
fn test_fuzzing_config_default() {
let config = FuzzingConfig::default();
assert_eq!(config.max_input_size, 4096);
assert_eq!(config.min_input_size, 0);
assert!(config.include_invalid_inputs);
assert!(config.test_edge_cases);
assert_eq!(config.iterations, 1000);
}
#[test]
fn test_fuzzing_input_generator() {
let config = FuzzingConfig::default();
let rng = Box::new(TestRng::new());
let mut generator = FuzzingInputGenerator::new(config, rng);
let key = generator.generate_random_key(32);
assert_eq!(key.len(), 32);
let nonce = generator.generate_random_nonce(16);
assert_eq!(nonce.len(), 16);
let plaintext = generator.generate_random_plaintext();
assert!(plaintext.len() <= 4096);
}
#[test]
fn test_edge_case_generation() {
let config = FuzzingConfig::default();
let rng = Box::new(TestRng::new());
let mut generator = FuzzingInputGenerator::new(config, rng);
let edge_cases = generator.generate_edge_case_inputs();
assert!(!edge_cases.is_empty());
assert!(edge_cases.iter().any(|input| input.is_empty()));
assert!(edge_cases.iter().any(|input| input.iter().all(|&b| b == 0)));
}
#[test]
fn test_fuzzing_results() {
let mut results = FuzzingResults::new();
results.record_result(FuzzingResult::Success);
results.record_result(FuzzingResult::Error("Test error".into()));
results.record_result(FuzzingResult::Success);
assert_eq!(results.total_tests, 3);
assert_eq!(results.successes, 2);
assert_eq!(results.errors, 1);
assert_eq!(results.panics, 0);
assert_eq!(results.success_rate(), 2.0 / 3.0);
assert!(!results.has_panics());
}
#[test]
fn test_hpke_fuzzing_harness() {
let config = FuzzingConfig {
iterations: 10, ..Default::default()
};
let rng = Box::new(TestRng::new());
let mut harness = HpkeFuzzingHarness::new(config, rng);
let results = harness.fuzz_aead_seal(HpkeAead::Saturnin256);
assert!(results.total_tests > 0);
}
}