feat: migrate refimpls from draft-00 to draft-01 claim names
- Rename `par` to `pred` (predecessor) in types, serialization, tests - Remove `pol`, `pol_decision` from core payload; move to `ect_ext` - Remove `sub` from payload (not part of ECT spec) - Update `typ` from `wimse-exec+jwt` to `exec+jwt` (accept both) - Rename MaxParLength to MaxPredLength everywhere - Update testdata, demos, READMEs with migration table - All Go tests pass, all 56 Python tests pass (90% coverage)
This commit is contained in:
@@ -10,7 +10,7 @@ from typing import Callable, Optional
|
||||
import jwt
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
|
||||
|
||||
from ect.types import ECT_TYPE, Payload, valid_pol_decision
|
||||
from ect.types import ECT_TYPE, ECT_TYPE_LEGACY, Payload
|
||||
from ect.dag import ECTStore, DAGConfig, validate_dag
|
||||
from ect.validate import validate_ext, validate_hash_format, valid_uuid
|
||||
|
||||
@@ -37,7 +37,7 @@ class VerifyOptions:
|
||||
jti_seen: Optional[Callable[[str], bool]] = None
|
||||
wit_subject: str = ""
|
||||
validate_uuids: bool = False
|
||||
max_par_length: int = 0 # 0 = no limit
|
||||
max_pred_length: int = 0 # 0 = no limit
|
||||
on_verify_attempt: Optional[Callable[[str, Optional[Exception]], None]] = None # (jti, err) for observability
|
||||
|
||||
|
||||
@@ -83,8 +83,8 @@ def verify(compact: str, opts: VerifyOptions) -> ParsedECT:
|
||||
def _verify_impl(compact: str, opts: VerifyOptions, set_log_jti: Callable[[str], None]) -> ParsedECT:
|
||||
header = jwt.get_unverified_header(compact)
|
||||
typ = header.get("typ") or ""
|
||||
# Constant-time comparison for typ
|
||||
if not hmac.compare_digest(typ, ECT_TYPE):
|
||||
# Constant-time comparison for typ; accept both preferred and legacy values
|
||||
if not hmac.compare_digest(typ, ECT_TYPE) and not hmac.compare_digest(typ, ECT_TYPE_LEGACY):
|
||||
raise ValueError("ect: invalid typ parameter")
|
||||
alg = header.get("alg")
|
||||
if alg in ("none", "HS256", "HS384", "HS512"):
|
||||
@@ -114,8 +114,8 @@ def _verify_impl(compact: str, opts: VerifyOptions, set_log_jti: Callable[[str],
|
||||
set_log_jti(payload.jti)
|
||||
|
||||
validate_ext(payload.ext)
|
||||
if opts.max_par_length > 0 and len(payload.par) > opts.max_par_length:
|
||||
raise ValueError("ect: par exceeds max length")
|
||||
if opts.max_pred_length > 0 and len(payload.pred) > opts.max_pred_length:
|
||||
raise ValueError("ect: pred exceeds max length")
|
||||
if opts.validate_uuids:
|
||||
if not valid_uuid(payload.jti):
|
||||
raise ValueError("ect: jti must be UUID format")
|
||||
@@ -139,17 +139,11 @@ def _verify_impl(compact: str, opts: VerifyOptions, set_log_jti: Callable[[str],
|
||||
if payload.iat > now + opts.iat_max_future_sec:
|
||||
raise ValueError("ect: iat in the future")
|
||||
|
||||
# Required claims per spec: jti, exec_act, par. par may be set to [] when missing (from_claims already uses []).
|
||||
# Required claims per spec: jti, exec_act, pred. pred may be set to [] when missing (from_claims already uses []).
|
||||
if not payload.jti or not payload.exec_act:
|
||||
raise ValueError("ect: missing required claims (jti, exec_act, par)")
|
||||
if payload.par is None:
|
||||
payload.par = []
|
||||
# If pol or pol_decision present, both must be present and valid
|
||||
if payload.pol or payload.pol_decision:
|
||||
if not payload.pol or not payload.pol_decision:
|
||||
raise ValueError("ect: pol and pol_decision must both be present when either is set")
|
||||
if not valid_pol_decision(payload.pol_decision):
|
||||
raise ValueError("ect: invalid pol_decision value")
|
||||
raise ValueError("ect: missing required claims (jti, exec_act, pred)")
|
||||
if payload.pred is None:
|
||||
payload.pred = []
|
||||
|
||||
if opts.store is not None and opts.dag is not None:
|
||||
validate_dag(payload, opts.store, opts.dag)
|
||||
|
||||
Reference in New Issue
Block a user