jar_verify.rs 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. //! Fuzz JOSE assertion verification (`private_key_jwt`, RFC 7523).
  2. //!
  3. //! The assertion and the client JWKS document are both hostile input:
  4. //! [`validate_assertion`] parses the compact-JWT envelope, enforces the
  5. //! asymmetric `alg` allowlist, refuses `jku`/`x5u` fetch directives, pins a
  6. //! single `kid` candidate, and checks `iss`/`sub`/`aud`/`exp`/`jti` — all
  7. //! without I/O (the JWKS document is passed in, so no `jwks_uri` fetch and
  8. //! no replay store). [`unverified_iss`] reads only the envelope's `iss` for
  9. //! client lookup. Neither must ever panic; the `jti` replay claim itself
  10. //! needs Redis and stays under unit cover.
  11. #![no_main]
  12. use d_oauth::protocol::client_auth::assertion::{unverified_iss, validate_assertion};
  13. use libfuzzer_sys::fuzz_target;
  14. /// Pinned clock for the assertion window: a fixed instant, never the wall
  15. /// clock, so the harness is deterministic and hermetic.
  16. fn now() -> time::OffsetDateTime {
  17. match time::OffsetDateTime::from_unix_timestamp(1_700_000_000) {
  18. Ok(instant) => instant,
  19. Err(_) => time::OffsetDateTime::UNIX_EPOCH,
  20. }
  21. }
  22. fuzz_target!(|data: &[u8]| {
  23. let Ok(text) = core::str::from_utf8(data) else {
  24. return;
  25. };
  26. let mut lines = text.split('\n');
  27. let (Some(assertion), Some(jwks_text), Some(client_id), Some(token_endpoint)) =
  28. (lines.next(), lines.next(), lines.next(), lines.next())
  29. else {
  30. return;
  31. };
  32. let Ok(jwks_doc) = serde_json::from_str::<serde_json::Value>(jwks_text) else {
  33. return;
  34. };
  35. let _ = unverified_iss(assertion);
  36. let _ = validate_assertion(assertion, &jwks_doc, client_id, token_endpoint, now());
  37. });