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
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
89 lines
2.9 KiB
Rust
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);
|
|
});
|