Skip to content

sovp 1.1.0

Choose a tag to compare

@litzki-systems litzki-systems released this 09 Oct 17:34
· 5 commits to main since this release
Immutable release. Only release title and notes can be modified.

Added

  • sovp/core.py — generate_identity_document() now signs a freshness object (created, nonce, expiresAt) as part of the Ed25519-signed scope; default context_version changed from "v1.4" to "v2.0". Previously these fields lived inside the unsigned integrity_proof and were forgeable without invalidating the signature — same weakness as the already-documented expiresAt forgeability in sovp-engine's scanner. Closes the replay-protection gap described in draft-litzki-sovp-04.
  • tests/test_vectors.py — Test Vector Set 2 (schema v2.0, real generated+verified signatures) covering the new freshness object, including tamper rejection for freshness.created/freshness.expiresAt. Test Vector Set 1 (v1.4, locked) is unchanged.
  • sovp/document_safety.py — parse_unverified_sovp_document(), a minimal JSON parser enforcing draft-litzki-sovp-04's "Resource Limits for Unverified Documents": rejects a document larger than 64 KiB, nested deeper than 16 levels, or containing a duplicate member name in any object, before the document is trusted with any further processing.

Changed

  • pyproject.toml — the cryptography bound stays at >=38.0.0,!=40.0.0,!=40.0.1,<42. It matches the Requires-Dist line of the OS-packaged pyOpenSSL 23.2.0 on Ubuntu 24.04, which certbot imports. A global pip install sovp with a higher bound shadows that version and breaks certificate renewal. The library itself runs on newer cryptography releases; the bound protects the system install.
  • CHANGELOG.md — the 1.0.1 entry referenced a parameter count. It now states that the parameter count references were aligned with the protocol specification.
  • README.md — the introduction names pip install sovp as the normal installation route and keeps the editable clone for development.
  • sovp/core.py — verify_identity()'s check_timestamp path now reads freshness.created first and falls back to the unsigned integrity_proof.created only for documents whose @context predates v2.0; this fallback affects timestamp freshness checking only, never signature verification.
  • README.md — schema example and "Replay protection" table row updated to the v2.0/freshness shape; added an explicit callout that the tamper-resistance guarantee is new as of v2.0 and did not hold under v1.4.
  • README.md — contentAddress example corrected from the stale prefixed "sha256:<hex>" single-field form to the shape generate_identity_document() actually ships: {"alg": "sha256", "digest": "<hex>"}.
  • README.md — removed the parameters (entropy_threshold/determinism_score) field from the schema example; it does not exist in any reference implementation (sovp-python, sovp-engine, or the Cloudflare Worker) and was never anything other than an illustrative artifact. Found while reconciling draft-litzki-sovp-04 against the running implementations — the draft's own schema example had the same field, removed there too.
  • sovp/core.py — Draft 04 host binding is enforced through expected_host; v2.0 freshness remains signed and expiry is always validated. Generated DNS key references use the URL hostname, excluding ports and normalizing trailing dots and case.
  • sovp/resolver.py — DNS TXT parsing now accepts only the exact v=SOVP1; k=<base64> record shape and requires a valid 32-byte Ed25519 public key. Legacy endpoint fallback remains restricted to HTTP 404.
  • examples/ — live validation, end-to-end verification, and the ARD trustManifest example now identify Draft 04 explicitly and demonstrate v2.0 host binding and the current contentAddress shape.
  • workers/sovp-identity/ — the reference worker now serves a publisher-supplied, pre-signed Draft 04 v2.0 document from a Cloudflare secret instead of embedding a stale v1.4 artifact.

Fixed

  • tests/test_vectors.py — contentAddress.digest in Test Vector Set 2 was wrong. The vector shipped 33465a2b…3ae2b5; the correct value over sha256(JCS(signed scope)) is d00c6db5…30b460. No test read the field, so the vector and the specification drifted apart unnoticed.
  • sovp/core.py — verify_identity() no longer fails open when integrity_proof.created is missing; with check_timestamp=True a missing created is now a rejection (Psi_core = 0), closing a replay bypass of draft Section 7.2
  • sovp/core.py — verify_identity() no longer raises AttributeError when integrity_proof is a non-dict value (attacker-controlled since it's excluded from the signed payload); now returns False per the documented bool-only contract
  • sovp/resolver.py — fetch_identity_document() used resp.json() (== json.loads()) to parse a remote, not-yet-signature-checked document, with no size, depth, or duplicate-key limit. Now parses via parse_unverified_sovp_document().

Tests

  • tests/test_vectors.py — two tests now recompute contentAddress.digest from Test Vector Set 2 itself and assert that a change inside the signed scope changes the recomputed digest. This keeps the vector and the specification from drifting apart again.
  • Added Draft 04 conformance coverage for freshness clock-skew boundaries, the 600-second issuance window, signed unknown top-level members, unsigned contentAddress and scan extensions, host normalization, URL ports, and exact DNS TXT syntax.

PyPI: https://pypi.org/project/sovp/1.1.0/