Repository navigation
Closed
Description
What is wrong
At main (4c83b1d), tools/acta_receipt_verifier.py (the reference verifier for draft-farley-acta-signed-receipts-03 used by tests/test_acta_fixtures.py and tests/test_acta_receipt_verifier.py) has three verification and input-hardening gaps:
-
issuer_keyaccepts JWKs containing private key material and non-canonical base64urlxvalues:- In
tools/acta_receipt_verifier.py:165-186,issuer_keychecksjwk.get("kty") != "OKP" or jwk.get("crv") != "Ed25519" or not isinstance(x, str)and then callsbase64.urlsafe_b64decode(x + "=" * (-len(x) % 4)). - It does not reject RFC 7517 / RFC 7518 / RFC 8037 private key parameters (
d,p,q,dp,dq,qi,oth,k), and it accepts padded (=), non-url-safe (+//), ignored-character, or non-zero-trailing-bit base64url strings forx.
- In
-
signature_verifiesaccepts uppercase hex and arbitrary whitespace insignature.sig:- In
tools/acta_receipt_verifier.py:86-92,signature_verifiescallsbytes.fromhex(envelope["signature"]["sig"])directly. - Python's
bytes.fromhexaccepts uppercase hex characters (A-F) and strips arbitrary ASCII whitespace (" " + sig.upper() + "\n"), whereas_link_is_well_formed(lines 114-119) holds hex fields to lowercase hex ([0-9a-f]) without whitespace.
- In
-
_read_jsonaccepts duplicate JSON keys and does not catchRecursionError:_read_json(lines 189-193) callsjson.loads(...)without anobject_pairs_hookto reject duplicate keys (even thoughsignature_verifiesandenvelope_hashcanonicalize via RFC 8785 JCS, which is defined over I-JSON RFC 7493 with no duplicate keys) and only catches(OSError, UnicodeDecodeError, ValueError), allowingRecursionErroron deeply nested JSON to crash the verifier instead of returning("undecidable", "malformed").
Reproduction on 4c83b1d:
from tools import acta_receipt_verifier as acta
from agentrust_trace import sign
k = sign.generate_key()
jwk = sign.key_to_jwk(k)
jwk["kid"] = "k1"
jwk["d"] = "private-key-material"
# 1. Accepts JWK with private key 'd':
ik = acta.issuer_key({"keys": [jwk]}, "k1")
# 2. Accepts uppercase hex with surrounding whitespace in signature.sig:
payload = {"issued_at": "2026-10-20T00:00:00Z", "action": "test"}
sig_hex = k.sign(acta.jcs(payload)).hex()
env = {"payload": payload, "signature": {"alg": "EdDSA", "kid": "k1", "sig": " " + sig_hex.upper() + "\n"}}
assert acta.verify_envelope(env, ik) == ("valid", None)Spec section or file
tools/acta_receipt_verifier.py(issuer_key,signature_verifies,_read_json,_instant)tests/test_acta_receipt_verifier.py
Expected behavior
issuer_keyrejects any JWK carrying private key parameters (d,p,q,dp,dq,qi,oth,k) or non-canonical unpadded base64urlxwithUndecidable("malformed_key_set").signature_verifiesrequiresenvelope["signature"]["sig"]to match^[0-9a-f]{128}\Zbefore verifying the Ed25519 signature, returningFalse(("invalid", "signature_invalid")) on uppercase hex or whitespace._read_jsonrejects duplicate JSON object keys and catchesRecursionErrorasUndecidable("malformed").
Impact
acta_receipt_verifier.py accepts unredacted private JWKs, non-canonical key encodings, duplicate JSON members, and non-canonical signature hex strings with whitespace.
Metadata
Metadata
Assignees
Labels
No labels
Activity
tools(acta_receipt_verifier): reject private JWKs, non-canonical base…
tools(acta_receipt_verifier): reject private JWKs, non-canonical base…
tools(acta_receipt_verifier): reject private JWKs, non-canonical base…