libsoliton/soliton/fuzz/fuzz_targets/fuzz_kex_verify_bundle.rs
Kamal Tufekcic 1d99048c95
Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled
initial commit
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
2026-04-02 23:48:10 +03:00

89 lines
2.9 KiB
Rust

#![no_main]
use libfuzzer_sys::fuzz_target;
use soliton::{
identity::{generate_identity, HybridSignature, IdentityPublicKey},
kex::{verify_bundle, PreKeyBundle},
primitives::xwing,
};
use std::sync::LazyLock;
// KNOWN_IK is used as BOTH the "trusted" key AND the bundle's ik_pub.
// verify_bundle's first check is `bundle.ik_pub == known_ik`; a mismatch
// returns BundleVerificationFailed immediately without reaching the SPK
// signature check. Supplying the same value for both ensures the IK check
// always passes, so every input exercises the OPK co-presence guard,
// crypto_version validation, and hybrid_verify — the SPK sig-forge surface.
static KNOWN_IK: LazyLock<IdentityPublicKey> =
LazyLock::new(|| generate_identity().unwrap().public_key);
const SPK: usize = 1216;
const SIG: usize = 3373;
// Wire layout (ik_pub is always KNOWN_IK — not part of fuzz input):
// spk_pub (1216) | spk_sig (3373) | spk_id (4 BE)
// | has_opk (1) | [opk_pub (1216) | opk_id (4 BE)]
// | crypto_version (rest, strict UTF-8; bail if invalid)
const MIN: usize = SPK + SIG + 4 + 1;
fuzz_target!(|data: &[u8]| {
if data.len() < MIN {
return;
}
let mut off = 0;
let Ok(spk_pub) = xwing::PublicKey::from_bytes(data[off..off + SPK].to_vec()) else { return; };
off += SPK;
let Ok(spk_sig) = HybridSignature::from_bytes(data[off..off + SIG].to_vec()) else { return; };
off += SIG;
let spk_id = u32::from_be_bytes(data[off..off + 4].try_into().unwrap());
off += 4;
// Two independent bits control opk_pub and opk_id presence, allowing
// the fuzzer to reach the co-presence guard in verify_bundle (one Some,
// the other None). Bit 0 = has_opk_pub, bit 1 = has_opk_id.
let opk_flags = data[off];
let has_opk_pub = opk_flags & 0x01 != 0;
let has_opk_id = opk_flags & 0x02 != 0;
off += 1;
let opk_pub = if has_opk_pub {
if data.len() < off + SPK {
return;
}
let Ok(opk) = xwing::PublicKey::from_bytes(data[off..off + SPK].to_vec()) else { return; };
off += SPK;
Some(opk)
} else {
None
};
let opk_id = if has_opk_id {
if data.len() < off + 4 {
return;
}
let id = u32::from_be_bytes(data[off..off + 4].try_into().unwrap());
off += 4;
Some(id)
} else {
None
};
let Ok(crypto_version) = String::from_utf8(data[off..].to_vec()) else { return; };
let bundle = PreKeyBundle {
ik_pub: KNOWN_IK.clone(),
crypto_version,
spk_pub,
spk_id,
spk_sig,
opk_pub,
opk_id,
};
// verify_bundle must never panic regardless of input.
// Exercises: crypto_version validation, OPK co-presence guard (mismatched
// opk_pub/opk_id via independent flag bits), and SPK signature verification
// (hybrid_verify) — the main sig-forge attack surface.
let _ = verify_bundle(bundle, &KNOWN_IK);
});