Skip to content

security(jws): reject unrecognized 'crit' header extensions on ordinary verified decode #132

Description

@ZhuchkaTriplesix

Problem

RFC 7515 §4.1.11 requires a JWS recipient to reject the token if the header's crit list names any critical extension the recipient doesn't understand and process. OxyJWT already implements exactly this check — but only for the RFC 7797 detached-payload (b64: false) path, in jws::validate_rfc7797_header:

for param in crit {
    match param_str {
        "b64" => has_b64 = true,
        other => return Err(format!("Unsupported critical header parameter in 'crit': '{other}'")),
    }
}

The ordinary verified-decode path (api::verify_and_parse_impl, used by decode/decode_complete for every non-detached token) never looks at crit at all. A JWT with a signed header naming an unrecognized critical extension verifies and decodes successfully, contrary to the RFC:

header = {"alg": "HS256", "typ": "JWT", "crit": ["exp-strict-mode"], "exp-strict-mode": True}
# ... signed correctly with the real secret ...
oxyjwt.decode(token, secret, algorithms=["HS256"])  # succeeds; RFC 7515 says it must not

Confirmed by direct reproduction against the current dev branch.

This is not a signature-forgery vector (the header is inside the signed portion, so an attacker without the signing key can't add crit to someone else's token). The impact is protocol/interop-shaped: if an issuer signs tokens using crit to signal a security-relevant extension a relying party must enforce (some JOSE profiles do this), OxyJWT silently ignores the extension instead of refusing the token, and application code has no signal that something it doesn't understand was present.

Proposal

Generalize the existing crit-rejection logic (currently private to validate_rfc7797_header) into a small, reusable check — e.g. jws::reject_unknown_crit(header, understood: &[&str]) — and call it from verify_and_parse_impl for every verified decode, not just the detached-payload path. Since this library doesn't implement any JWS header extensions itself, the "understood" set is effectively just {"b64"} (only meaningful for the detached case); for an ordinary token, any crit entry at all should be rejected, matching the strictness the RFC recommends for implementations that don't support extensions.

Acceptance criteria

  • A signed, otherwise-valid JWT with an unrecognized crit entry is rejected by decode/decode_complete (the reproduction above must start raising)
  • The existing RFC 7797 crit handling (which additionally requires b64 to be listed) is preserved, sharing the underlying check rather than duplicating it
  • A token with no crit header, or an absent/empty one where already rejected today, is unaffected
  • Tests green; add a regression test reproducing the case above

Files

  • rust/src/jws.rs, rust/src/api.rs

Branch

issue/<N>-general-crit-rejection from dev

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    p1Should ship in 0.4.0securitySecurity hardening

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions