Skip to content

test(xtest): DSPX-1561 add shared assertion conformance vectors and cross-SDK cases - #702

Draft
dmihalcik-virtru wants to merge 8 commits into
mainfrom
DSPX-1561-go-sdk-pluggable-assertion-validation-and-trust-an
Draft

dmihalcik-virtru wants to merge 8 commits into
mainfrom
DSPX-1561-go-sdk-pluggable-assertion-validation-and-trust-an

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Summary

This adds shared assertion conformance vectors and cross-SDK xtest coverage for the matching Go, Java, and JS changes.

  • Shared vectors. xtest/vectors/assertions-phase0.json holds 288 vectors covering parsing, hash input, binding forms, and single-assertion verification. Each gives an expected assertion hash or failure reason.
    • The vectors come from a deterministic generator with fixed test keys.
    • The SDKs vendor byte-identical copies.
    • xtest/assertions.py contains a reference reader, and assertion_hash now uses RFC 8785 through the rfc8785 package.
    • A unit test regenerates the file and compares it.
  • New xtest modules:
    • test_assertion_verification.py: verification key selection, defaultKey, hash input, binding forms, and the JSON profile, over real TDFs from every writer;
    • test_empty_key_access.py: manifests with no key access objects;
    • test_assertion_gmac_limitation.py: a fixture documenting segment-integrity behaviour under GMAC.
  • Feature gates. assertion_header_key_pinning, assertion_hash_raw, and reject_empty_key_access. They are force-only until the SDK releases ship; use XT_FORCE_SUPPORTS locally or the force-supports workflow input.
  • CI. xtest.yml runs the new modules.
  • Fixture fix. Corrects a Markdown-mangled @base key in the STANAG assertion fixture.

Testing

  • cd xtest && uv run ruff check . && uv run ruff format --check . && uv run pyright, plus the offline unit tests.
  • Forced run, with all three gates forced against the SDK branches, and released/branch interop run. Both pass, and the new modules ran in every SDK job.
  • Branch builds use short refs (dspx-1561). xtest puts the version name into temporary filenames twice, so long branch names can exceed filename limits.

Related pull requests

assertion_hash now hashes the assertion as received, minus only its
top-level binding, with RFC 8785 JCS from the rfc8785 package. The old
json.dumps(sort_keys=True) formats numbers differently and sorts member
names by code point instead of by UTF-16 code unit.

assertions.py also gains a reference reader for Sections 3 to 8 of the
ZTDF assertions contract (DSPX-5519): strict parsing (duplicate members,
I-JSON, case variants of modelled member names), the exact id, the
binding form, and binding verification for one assertion with an
already-selected key. Each check fails with its Section 9 reason.

Also restores the "@base" context key in the STANAG 5636 fixture, which
had been mangled into a Markdown link.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
xtest/vectors/assertions-phase0.json is the shared vector set for Phase 0
of the ZTDF assertions contract (DSPX-5519). The Go, Java, and JS SDKs
vendor it byte-for-byte and run every vector in their unit tests. It
covers parsing, the hash input (starting with the spec Section 4.4 worked
examples), binding forms, and single-assertion verification.

vectors/generate_assertion_vectors.py produces it deterministically from
two committed, test-only RSA-2048 keys. The offline
test_assertion_vectors_units.py, now run by the check workflow,
regenerates the file and requires a byte-identical match, checks the
Section 4.4 anchor hashes, and runs every vector against the reference
reader in assertions.py.

Every expected assertionHash was also cross-checked against Go
github.com/gowebpki/jcs and JS json-canonicalize.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Moves the vector generator's JSON-text builder into xtest/jsontext.py
and its JWS, JWK, certificate, and binding-claims helpers into
assertions.py, so the xtest cases that put assertions into real TDFs
build them the same way the DSPX-5520 vectors do. The generated vector
file is unchanged byte for byte.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… binding forms

Adds the Phase 0 feature gates assertion_header_key_pinning and
assertion_hash_raw. Every cli.sh answers no until a release ships;
branch builds run the cases with XT_FORCE_SUPPORTS.

test_assertion_verification.py builds assertions into TDFs that each SDK
wrote and checks how each SDK reads them:
- key selection from configuration only: per-ID key, default key, and
  payload key, including embedded jwk and x5c and the default key
  through each CLI's verification-keys file;
- the hash over the assertion as received;
- strict manifest parsing, including assertions from the DSPX-5520
  vectors;
- spec Section 5.1 binding forms.

Every rejection is matched by its reported reason and has a positive
control under the same configuration. The reference reader in
assertions.py checks each built assertion before an SDK sees it.

test_assertion_gmac_limitation.py documents a known, unfixed limitation
of GMAC segment integrity for holders of the payload key (spec Section
10.7). It needs the local KAS key and skips without it.

tdfs.update_manifest_text rewrites a manifest's raw JSON text, for
changes the Manifest model cannot represent.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…est in the shared vectors

Extend the shared assertion vectors and the reference reader to the
revised parsing rule: a member is rejected when it is a case variant of a
name listed for its level, or when two members of a listed object are case
variants of each other. The file now carries the level table
(modelledNames) and the IsStreamable exception, so SDK tests can check
their own tables against it.

Also add vectors for strict base64url and canonical x5c base64, minimal
RSA integers, well-formed EC and OKP embedded keys, x5c leaf structure,
duplicate JWS header and claim members, ignored registered claims, invalid
UTF-8 and BOMs anywhere in the manifest, and unusable configured keys.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…l TDFs

Add profile cases that inject Payload alongside payload, URL in a key
access object, and SchemaVersion into real TDFs, plus a positive control
for the IsStreamable spelling written by older Java SDKs.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… default keys

Adds the force-only feature gate reject_empty_key_access. Every cli.sh
answers no until a release ships; branch builds run the case with
XT_FORCE_SUPPORTS.

test_empty_key_access.py builds a TDF whose encryptionInformation.keyAccess
is empty. Apart from that it is internally consistent: payload, segment
hash, root signature, and an HS256 payload-key assertion all under the
all-zero key, checked in Python before any reader sees it. Each reader
must refuse it, with assertion verification on or off, reporting
"no key access objects" and writing no plaintext.

test_assertion_verification.py pins documented default-key behaviour
(spec 6.1): a Java-written TDF read with only a default RS256 key fails
with algorithm-mismatch on its payload-key system-metadata assertion.
The control is the same file read with an empty keys file.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add test_assertion_verification.py, test_assertion_gmac_limitation.py and
test_empty_key_access.py to the standard and focused xtest steps, so their
cases run on branch builds. They are gated on force-only features and skip
unless a run forces them.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@sonarqubecloud

sonarqubecloud Bot commented Nov 9, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Reliability Rating on New Code (required ≥ A)
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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