diff --git a/xtest/sdk/go/cli.sh b/xtest/sdk/go/cli.sh index 425582c5..50649e47 100755 --- a/xtest/sdk/go/cli.sh +++ b/xtest/sdk/go/cli.sh @@ -81,6 +81,14 @@ if [ "$1" == "supports" ]; then "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 > 3) || ($1 == 0 && $2 == 3 && $3 >= 18)) exit 0; else exit 1; }' exit $? ;; + assertion_schema_v2) + # V2 assertion schema (urn:opentdf:system:metadata:v2) support + # Uses root signature for binding instead of aggregate hash + # Introduced in SDK version 0.10.0 + set -o pipefail + "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 >= 10)) exit 0; else exit 1; }' + exit $? + ;; *) echo "Unknown feature: $2" exit 2 diff --git a/xtest/tdfs.py b/xtest/tdfs.py index 893949ff..7dfe548d 100644 --- a/xtest/tdfs.py +++ b/xtest/tdfs.py @@ -35,6 +35,9 @@ feature_type = Literal[ "assertions", "assertion_verification", + # Support for V2 assertion schema (urn:opentdf:system:metadata:v2) + # Go SDK supports V2, Java/JS only support V1 + "assertion_schema_v2", "autoconfigure", "better-messages-2024", "bulk_rewrap", @@ -494,6 +497,92 @@ def skip_connectrpc_skew(encrypt_sdk: SDK, decrypt_sdk: SDK, pfs: PlatformFeatur return False +def skip_assertion_schema_skew( + encrypt_sdk: SDK, decrypt_sdk: SDK, tdf_file: Path | None = None +): + """Check assertion compatibility and skip/fail tests appropriately. + + In xtest (testing environment): All assertion types and schemas MUST be explicitly + tested. Unknown assertions cause test FAILURE (not skip) to ensure comprehensive + test coverage and alert developers to new assertion types. + + In real-world SDK usage: Unknown assertions are gracefully logged and skipped + to maintain forward compatibility (see tdf.go:1572). + + This function inspects the TDF manifest to determine if the decrypt SDK can handle + the assertions created by the encrypt SDK, using feature detection via SDK.supports(). + + Known assertion schemas (identified by schema, not ID): + - "system-metadata-v1": System metadata V1 (all SDKs) + - "urn:opentdf:system:metadata:v2": System metadata V2 (Go SDK >= 0.10.0) + - "urn:nato:stanag:5636:A:1:elements:json": STANAG 5636 military standard (all SDKs) + - Key assertions: Identified by ID "assertion-key" (use custom schemas) + + Args: + encrypt_sdk: The SDK used for encryption + decrypt_sdk: The SDK used for decryption + tdf_file: Path to the encrypted TDF file to inspect for actual assertions. + Must not be None. + + Raises: + ValueError: If tdf_file is None (caller error). + pytest.fail: If TDF contains unknown assertion types or schemas. + + Behavior: + - No assertions: test continues + - All compatible: test continues + - V2 schema without SDK support: test skips (temporary incompatibility) + - Unknown assertion type/schema: test FAILS (requires explicit handling) + """ + if tdf_file is None: + # Caller must provide a TDF file for inspection + raise ValueError( + "tdf_file cannot be None - must provide encrypted TDF to inspect" + ) + + m = manifest(tdf_file) + if not m.assertions: + # No assertions in TDF - nothing to verify, test can proceed + return + + # Check all assertions - fail on unknown schemas to ensure comprehensive test coverage + # Assertions are identified by their schema, not their ID + for assertion in m.assertions: + schema = assertion.statement.schema_ + + # System metadata V2 schema - check if decrypt SDK supports it + if schema == "urn:opentdf:system:metadata:v2": + if not decrypt_sdk.supports("assertion_schema_v2"): + pytest.skip( + f"TDF uses V2 assertion schema that {decrypt_sdk} doesn't support yet. " + f"Payload decryption works, but assertion verification will fail." + ) + continue + + # System metadata V1 schema or empty (legacy) - all SDKs support this + elif schema in ["system-metadata-v1", ""]: + continue + + # STANAG 5636 assertions - all SDKs support this + elif schema == "urn:nato:stanag:5636:A:1:elements:json": + continue + + # Key-based assertions use wildcard schema - all SDKs support this + # These can have various custom schemas, so we check with a pattern + elif assertion.id == "assertion-key": + # Key assertions are identified by ID since they use custom schemas + continue + + # Unknown assertion schema - FAIL the test + else: + pytest.fail( + f"TDF uses unknown assertion schema: {schema!r} (id={assertion.id!r}). " + f"Known schemas: 'system-metadata-v1', 'urn:opentdf:system:metadata:v2', " + f"'urn:nato:stanag:5636:A:1:elements:json'. " + f"New assertion schemas must be explicitly tested and added to this function." + ) + + def select_target_version( encrypt_sdk: SDK, decrypt_sdk: SDK ) -> container_version | None: diff --git a/xtest/test_tdfs.py b/xtest/test_tdfs.py index ae93a580..195a2902 100644 --- a/xtest/test_tdfs.py +++ b/xtest/test_tdfs.py @@ -310,6 +310,7 @@ def test_tdf_assertions_unkeyed( pfs = tdfs.PlatformFeatureSet() if not in_focus & {encrypt_sdk, decrypt_sdk}: pytest.skip("Not in focus") + target_mode = tdfs.select_target_version(encrypt_sdk, decrypt_sdk) tdfs.skip_hexless_skew(encrypt_sdk, decrypt_sdk) tdfs.skip_connectrpc_skew(encrypt_sdk, decrypt_sdk, pfs) if not encrypt_sdk.supports("assertions"): @@ -323,8 +324,10 @@ def test_tdf_assertions_unkeyed( tmp_dir, scenario="assertions", az=assertion_file_no_keys, - target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk), + target_mode=target_mode, ) + # Check assertion schema compatibility after encryption + tdfs.skip_assertion_schema_skew(encrypt_sdk, decrypt_sdk, ct_file) fname = ct_file.stem rt_file = tmp_dir / f"{fname}.untdf" decrypt_sdk.decrypt(ct_file, rt_file, "ztdf") @@ -343,6 +346,7 @@ def test_tdf_assertions_with_keys( pfs = tdfs.PlatformFeatureSet() if not in_focus & {encrypt_sdk, decrypt_sdk}: pytest.skip("Not in focus") + target_mode = tdfs.select_target_version(encrypt_sdk, decrypt_sdk) tdfs.skip_hexless_skew(encrypt_sdk, decrypt_sdk) tdfs.skip_connectrpc_skew(encrypt_sdk, decrypt_sdk, pfs) if not encrypt_sdk.supports("assertions"): @@ -356,8 +360,10 @@ def test_tdf_assertions_with_keys( tmp_dir, scenario="assertions-keys-roundtrip", az=assertion_file_rs_and_hs_keys, - target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk), + target_mode=target_mode, ) + # Check assertion schema compatibility after encryption + tdfs.skip_assertion_schema_skew(encrypt_sdk, decrypt_sdk, ct_file) fname = ct_file.stem rt_file = tmp_dir / f"{fname}.untdf" @@ -400,6 +406,8 @@ def test_tdf_assertions_422_format( az=assertion_file_rs_and_hs_keys, target_mode="4.2.2", ) + # Check assertion schema compatibility after encryption + tdfs.skip_assertion_schema_skew(encrypt_sdk, decrypt_sdk, ct_file) fname = ct_file.stem rt_file = tmp_dir / f"{fname}.untdf"