jar_claims.rs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. //! Fuzz the JAR (request object) claims envelope plus the PAR/JAR
  2. //! mandatory-rule dispatch.
  3. //!
  4. //! The target splits the input into a faux `token_endpoint_auth_method` and a
  5. //! compact-JWT-shaped claims envelope. The envelope parser (three
  6. //! base64url segments, JSON header/payload objects) must never panic;
  7. //! `require_par_or_jar` pins the confidential-client dispatch. Full
  8. //! signature verification needs keys and stays under unit/proptest cover.
  9. #![no_main]
  10. use d_oauth::db::Client;
  11. use d_oauth::jose::jar::require_par_or_jar;
  12. use libfuzzer_sys::fuzz_target;
  13. fn envelope_invariants(compact: &str) {
  14. let mut parts = compact.split('.');
  15. let (Some(header_b64), Some(payload_b64), Some(sig_b64), None) =
  16. (parts.next(), parts.next(), parts.next(), parts.next())
  17. else {
  18. return;
  19. };
  20. if sig_b64.is_empty() {
  21. return;
  22. }
  23. use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
  24. let Ok(header_raw) = URL_SAFE_NO_PAD.decode(header_b64) else {
  25. return;
  26. };
  27. let Ok(payload_raw) = URL_SAFE_NO_PAD.decode(payload_b64) else {
  28. return;
  29. };
  30. let Ok(header) = serde_json::from_slice::<serde_json::Value>(&header_raw) else {
  31. return;
  32. };
  33. let Ok(payload) = serde_json::from_slice::<serde_json::Value>(&payload_raw) else {
  34. return;
  35. };
  36. // Header and payload are JSON objects; a request object names its
  37. // algorithm and carries claims. Parsing robustness (never panicking on
  38. // hostile input) is the property under test; validity is decided by the
  39. // verifier, not the envelope shape, so nothing is asserted here.
  40. let _ = (header, payload);
  41. }
  42. fn client_with_method(method: &str) -> Client {
  43. Client {
  44. id: uuid::Uuid::nil(),
  45. client_id: "fuzz-client".to_owned(),
  46. client_secret_expires_at: None,
  47. client_id_issued_at: time::OffsetDateTime::UNIX_EPOCH,
  48. client_name: "fuzz".to_owned(),
  49. grant_types: vec!["authorization_code".to_owned()],
  50. response_types: vec!["code".to_owned()],
  51. scope: String::new(),
  52. token_endpoint_auth_method: method.to_owned(),
  53. jwks: None,
  54. jwks_uri: None,
  55. tls_client_auth_subject_dn: None,
  56. tls_client_auth_cert_thumbprint: None,
  57. request_uris: None,
  58. contacts: None,
  59. software_id: None,
  60. dpop_bound_access_tokens: false,
  61. active: true,
  62. created_at: time::OffsetDateTime::UNIX_EPOCH,
  63. updated_at: time::OffsetDateTime::UNIX_EPOCH,
  64. }
  65. }
  66. fuzz_target!(|data: &[u8]| {
  67. let Ok(text) = core::str::from_utf8(data) else {
  68. return;
  69. };
  70. let (method, compact) = match text.split_once('\n') {
  71. Some((method, compact)) => (method, compact),
  72. None => ("none", text),
  73. };
  74. envelope_invariants(compact);
  75. let client = client_with_method(method);
  76. let _ = require_par_or_jar(&client);
  77. });