Skip to content

fix(verify): require a trusted key, enforce freshness, validate JWK type#73

Merged
imran-siddique merged 1 commit into
mainfrom
fix/verify-trust-anchor
Jun 30, 2026
Merged

fix(verify): require a trusted key, enforce freshness, validate JWK type#73
imran-siddique merged 1 commit into
mainfrom
fix/verify-trust-anchor

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Hardens verify_record (and the surrounding signing module) so that signature verification fails closed. Four changes:

  1. Require an explicit trusted key. verify_record previously fell back to the key embedded in record.cnf.jwk, which means a record could vouch for itself. The trusted key/JWK is now required. The embedded-key path is gated behind an explicit allow_embedded_key=True opt-in that emits a loud UserWarning (it proves only internal consistency, not authenticity). With no trusted key and the opt-in off, the function raises ValueError.

  2. Enforce freshness. Adds a max_age_seconds check (default 86400 = 24h, configurable; pass None to disable) against iat, and an optional expected_nonce compared in constant time against runtime.nonce. Stale records or nonce mismatches fail verification.

  3. Validate JWK type before key reconstruction. Asserts kty == "OKP" and crv == "Ed25519" before building an Ed25519PublicKey, so a mismatched key type (e.g. an EC key) is rejected explicitly rather than silently treated as Ed25519.

  4. Robust base64url decode. Factors the padding logic into a single helper that raises ValueError (not binascii.Error) on malformed input, applied to both the signature and the JWK x field.

API note: verify_record gains keyword-only params allow_embedded_key: bool = False, max_age_seconds: int | None = 86400, and expected_nonce: str | None = None. Callers that relied on the implicit embedded-key fallback must now pass a trusted key or set allow_embedded_key=True.

Tests: existing tests updated to pass a trusted key (or opt in). New tests cover: no trusted key raises; record signed by key A does not verify against key B; expired record fails; non-OKP JWK rejected; nonce match/mismatch; malformed signature. pytest -q (65 passed) and ruff check src/ tests/ --select E,F,W --ignore E501 both green.

RFC 8785/JCS canonicalization and revocation enforcement are out of scope here and tracked separately.

🤖 Generated with Claude Code

Harden verify_record so it fails closed:

- Require an explicit trusted key. verify_record no longer falls back to
  the key embedded in record.cnf.jwk by default, so a record can no longer
  vouch for itself. The embedded-key path is now gated behind an explicit
  allow_embedded_key=True opt-in that emits a loud UserWarning; with no
  trusted key and the opt-in off, it raises ValueError.
- Enforce freshness. Add a max_age_seconds check (default 86400 = 24h,
  configurable, None to disable) against iat, and an optional constant-time
  expected_nonce comparison against runtime.nonce. Stale or mismatched
  records fail verification.
- Validate JWK type before key reconstruction. Assert kty=="OKP" and
  crv=="Ed25519" before building an Ed25519PublicKey, so an EC key is never
  silently treated as Ed25519.
- Robust base64url decode. Factor padding into one helper that raises
  ValueError (not binascii.Error) on malformed input, applied to both the
  signature and the JWK x field.

Tests updated to pass a trusted key (or opt in), plus new tests for the
no-key, wrong-key, expired, non-OKP, nonce, and malformed-signature paths.
Doc code examples updated to verify against a trusted key.

RFC 8785/JCS canonicalization and revocation enforcement are out of scope
and tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@imran-siddique imran-siddique merged commit 177b57a into main Jun 30, 2026
6 checks passed
@imran-siddique imran-siddique deleted the fix/verify-trust-anchor branch June 30, 2026 19:17
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.

1 participant