#![no_main] use libfuzzer_sys::fuzz_target; use soliton::{ identity::{generate_identity, GeneratedIdentity, HybridSignature, IdentityPublicKey, IdentitySecretKey}, kex::{receive_session, SessionInit}, primitives::xwing, }; use std::sync::LazyLock; struct BobKeys { ik_pk: IdentityPublicKey, ik_sk: IdentitySecretKey, spk_sk: xwing::SecretKey, } // Fixed Bob identity + SPK keypair — keygen is expensive, amortise across runs. static BOB: LazyLock = LazyLock::new(|| { let GeneratedIdentity { public_key: ik_pk, secret_key: ik_sk, .. } = generate_identity().unwrap(); let (_spk_pk, spk_sk) = xwing::keygen().unwrap(); BobKeys { ik_pk, ik_sk, spk_sk } }); // Fixed Alice identity (the "known sender" Bob trusts). static ALICE_PK: LazyLock = LazyLock::new(|| generate_identity().unwrap().public_key); const SIG: usize = 3373; const FP: usize = 32; const XPK: usize = 1216; const XCT: usize = 1120; // Wire layout: // sig (3373) | sender_fp (32) | recipient_fp (32) | sender_ek (1216) // | ct_ik (1120) | ct_spk (1120) | spk_id (4 BE) | has_opk (1) // | [ct_opk (1120) | opk_id (4 BE)] ← only if has_opk & 0x01 // | crypto_version (rest, strict UTF-8; bail if invalid) const MIN: usize = SIG + FP + FP + XPK + XCT + XCT + 4 + 1; fuzz_target!(|data: &[u8]| { if data.len() < MIN { return; } let mut off = 0; let Ok(sig) = HybridSignature::from_bytes(data[off..off + SIG].to_vec()) else { return; }; off += SIG; let sender_ik_fingerprint: [u8; FP] = data[off..off + FP].try_into().unwrap(); off += FP; let recipient_ik_fingerprint: [u8; FP] = data[off..off + FP].try_into().unwrap(); off += FP; let Ok(sender_ek) = xwing::PublicKey::from_bytes(data[off..off + XPK].to_vec()) else { return; }; off += XPK; let Ok(ct_ik) = xwing::Ciphertext::from_bytes(data[off..off + XCT].to_vec()) else { return; }; off += XCT; let Ok(ct_spk) = xwing::Ciphertext::from_bytes(data[off..off + XCT].to_vec()) else { return; }; off += XCT; let spk_id = u32::from_be_bytes(data[off..off + 4].try_into().unwrap()); off += 4; let has_opk = data[off] & 0x01 != 0; off += 1; let (ct_opk, opk_id) = if has_opk { if data.len() < off + XCT + 4 { return; } let Ok(ct) = xwing::Ciphertext::from_bytes(data[off..off + XCT].to_vec()) else { return; }; off += XCT; let id = u32::from_be_bytes(data[off..off + 4].try_into().unwrap()); off += 4; (Some(ct), Some(id)) } else { (None, None) }; // Use strict from_utf8 to match decode_session_init, which rejects // non-UTF-8 bytes with Error::InvalidData. Lossy decoding would allow // replacement-character strings that can never reach receive_session in // real protocol flow (the wire parser rejects them first). let Ok(crypto_version) = String::from_utf8(data[off..].to_vec()) else { return; }; let si = SessionInit { crypto_version, sender_ik_fingerprint, recipient_ik_fingerprint, sender_ek, ct_ik, ct_spk, spk_id, ct_opk, opk_id, }; // receive_session must never panic regardless of input. // Exercises: crypto_version check, fingerprint checks, hybrid signature // verification, KEM decapsulation, HKDF, and rollback on failure. let _ = receive_session(&BOB.ik_pk, &BOB.ik_sk, &ALICE_PK, &si, &sig, &BOB.spk_sk, None); });