SECURITY: make EPT routing independent of signature well-formedness
All checks were successful
check / gate (push) Successful in 20s
All checks were successful
check / gate (push) Successful in 20s
looks_like_ept used jwt.get_unverified_header, which validates the WHOLE token and therefore rejects anything with a malformed signature segment. Routing consequently depended on signature well-formedness: an EPT-shaped token with a bad signature fell through to the HUMAN path, where it was refused for the wrong reason and — with require_verified_jwt off (dev) — could have been read as a human identity via its `sub` claim. Now the header segment is decoded directly, so routing depends only on what the token CLAIMS to be; whether it is authentic remains verify_ept's job. Also routes alg:none to the EPT verifier regardless of typ, since a `none` token is never valid for any caller. Both forged shapes now return 401 ept_invalid — the honest code — instead of 503 "feature not ready". Found by noticing a forged EPT returned 503 where the verifier should have answered 401, rather than accepting "it was refused, close enough". 80 tests green. Co-Authored-By: Claude (Fable 5) <noreply@anthropic.com>
This commit is contained in:
@@ -260,3 +260,26 @@ async def test_resolve_passport_returns_band_on_active_wiring(monkeypatch):
|
||||
settings = Settings(eternitas_platform_api_key="x")
|
||||
band, actions = await resolve_passport(settings, "ET26-1EF9-VJAN")
|
||||
assert band == "gold" and actions == ("read",)
|
||||
|
||||
|
||||
def test_routing_does_not_depend_on_signature_wellformedness():
|
||||
"""An EPT-shaped token must route to the EPT verifier even when its
|
||||
signature is malformed. jwt.get_unverified_header() validates the whole
|
||||
token and rejects bad signature padding, which used to push such tokens to
|
||||
the human path — refused for the wrong reason, and readable as a human
|
||||
identity via `sub` whenever require_verified_jwt was off."""
|
||||
import base64
|
||||
import json as _j
|
||||
|
||||
from api.app.ept import looks_like_ept
|
||||
|
||||
def seg(d):
|
||||
return base64.urlsafe_b64encode(_j.dumps(d).encode()).rstrip(b"=").decode()
|
||||
|
||||
for hdr in ({"alg": "none", "typ": "EPT"}, {"alg": "ES256", "typ": "EPT"},
|
||||
{"alg": "ES256"}):
|
||||
tok = f"{seg(hdr)}.{seg({'sub': 'ET26-X'})}.x" # deliberately bad signature
|
||||
assert looks_like_ept(tok), f"{hdr} did not route to the EPT verifier"
|
||||
|
||||
assert not looks_like_ept("not-a-token")
|
||||
assert not looks_like_ept("")
|
||||
|
||||
Reference in New Issue
Block a user