Skip to content

Reject JWS tokens declaring unsupported crit header extensions#410

Open
arpitjain099 wants to merge 1 commit into
mpdavis:masterfrom
arpitjain099:fix/reject-unsupported-crit-header
Open

Reject JWS tokens declaring unsupported crit header extensions#410
arpitjain099 wants to merge 1 commit into
mpdavis:masterfrom
arpitjain099:fix/reject-unsupported-crit-header

Conversation

@arpitjain099
Copy link
Copy Markdown

Fixes #406

Summary

jws.verify (and therefore jwt.decode) accepts a token whose protected
header declares a critical extension that the library does not understand,
instead of rejecting it. This is a fail-open gap: a relying party that issues
or expects a crit policy marker gets no enforcement, and an attacker can
strip or ignore the semantics the issuer intended to make mandatory.

RFC 7515 4.1.11

The crit (Critical) Header Parameter "indicates that extensions to this
specification and/or [JWA] are being used that MUST be understood and
processed." The spec is explicit that:

If any of the listed extension Header Parameters are not understood and
supported by the recipient, then the JWS is invalid.

python-jose implements none of the optional crit-flagged extensions (for
example b64 from RFC 7797), so any well-formed, non-empty crit list names
an extension this library cannot honor and the token must be rejected. RFC 7515
also constrains the value itself: crit must be a non-empty array of non-empty
case-sensitive strings, so malformed values are rejected as well.

PyJWT parity

PyJWT already fails closed on the same input, raising
InvalidTokenError: Unsupported critical extension: .... After this change
python-jose matches that behavior and raises JWSError.

Change

  • Add _validate_crit(header) to jose/jws.py and call it from verify
    before signature checking.
  • It rejects a malformed crit (not a list, empty, or containing a non-string
    or empty entry) and any listed extension that is not in the understood set.
    The understood set is intentionally empty because the library supports none
    of the optional extensions.
  • Enforcement is gated on verify=True, so it mirrors how signature checking
    is skipped when verification is disabled.

Tests

Added TestCrit in tests/test_jws.py covering:

  • a correctly signed token with an unsupported crit extension is rejected
  • a token without crit still verifies
  • malformed crit (non-list, empty list, non-string entry) is rejected
  • crit is not enforced when verify=False

The unsupported-crit and malformed-crit tests fail on the current master
and pass with this change. The full tests/test_jws.py and tests/test_jwt.py
suites pass. flake8, isort, and black are clean on the changed files.

RFC 7515 section 4.1.11 requires a verifier to reject any JWS whose
protected header lists, via crit, an extension that the verifier does
not understand. python-jose implements none of the optional crit
extensions, so any well-formed non-empty crit list is unsupported and
must cause verification to fail closed. Previously jws.verify ignored
crit entirely and returned verified claims, which is a fail-open gap
relative to RFC 7515 and to PyJWT (which rejects the same tokens).

Add _validate_crit to the verification path: reject malformed crit
values (non-list, empty, or non-string entries) and any listed
extension that is not understood. Enforcement is gated on verify=True
so it mirrors signature checking.

Fixes mpdavis#406

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jwt.decode() accepts unsupported critical header extensions

1 participant