Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions xtest/sdk/js/cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
npx $CTL encrypt --help | grep -i 'mlkem:768'
exit $?
;;
multikao)
# Tolerate a KAO array where the same KAS wraps the same split more than
# once (DSPX-3379). Shipped in web-sdk >= 0.20.0.
set -o pipefail
npx $CTL --version | jq -re '.["@opentdf/sdk"]' | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 >= 20)) exit 0; else exit 1; }'

Check warning on line 107 in xtest/sdk/js/cli.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal '.[\"@opentdf/sdk\"]' 5 times.

See more on https://sonarcloud.io/project/issues?id=opentdf_tests&issues=AZ9ISE2vBMDwF-kQIhW5&open=AZ9ISE2vBMDwF-kQIhW5&pullRequest=555
exit $?
;;
mechanism-xwing)
set -o pipefail
npx $CTL help | grep -i xwing
Expand Down
8 changes: 8 additions & 0 deletions xtest/tdfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ def is_sdk_type(val: str) -> TypeIs[sdk_type]:
"mechanism-secpmlkem",
# Support for pure (non-hybrid) ML-KEM key wrapping: mlkem:768 and mlkem:1024.
"mechanism-mlkem",
# Decrypt a KAO array that has two entries with the same split id and KAS
# URI (the same KAS wrapping the same split more than once). go/java have
# always tolerated this; web-sdk gained it in 0.20.0 (DSPX-3379).
"multikao",
"ns_grants",
"obligations",
]
Expand Down Expand Up @@ -625,6 +629,10 @@ def _uncached_supports(self, feature: feature_type) -> bool:
return False
case ("autoconfigure", ("go" | "java")):
return True
case ("multikao", ("go" | "java")):
# go/java reconstruct by skipping already-satisfied splits, so a
# duplicate KAS on the same split has always decrypted.
return True
case ("better-messages-2024", ("js" | "java")):
return True
case ("ns_grants", ("go" | "java")):
Expand Down
56 changes: 56 additions & 0 deletions xtest/test_tdfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@ def malicious_kao(manifest: tdfs.Manifest) -> tdfs.Manifest:
return manifest


def duplicate_first_kao(manifest: tdfs.Manifest) -> tdfs.Manifest:
# DSPX-3379: append a byte-identical copy of the first KAO so the same KAS
# holds two copies of the same split (same sid + url + wrappedKey). This is
# a valid, decryptable duplicate (either copy unwraps the split).
assert manifest.encryptionInformation.keyAccess
first = manifest.encryptionInformation.keyAccess[0]
manifest.encryptionInformation.keyAccess.append(first.model_copy(deep=True))
return manifest


### TAMPER TESTS


Expand Down Expand Up @@ -831,3 +841,49 @@ def test_tdf_with_malicious_kao(

# Note: We don't assert on audit logs here because the SDK should reject
# the malicious KAO client-side before making a rewrap request to the KAS


def test_tdf_with_duplicate_kao_same_kas(
encrypt_sdk: tdfs.SDK,
decrypt_sdk: tdfs.SDK,
pt_file: Path,
in_focus: set[tdfs.SDK],
attribute_default_rsa: Attribute,
encrypted_tdf: EncryptFactory,
) -> None:
"""DSPX-3379: when a KAO array has two elements with the same split id and
the same KAS URI, the file must still decrypt (the same KAS is allowed to
wrap the same split more than once). It should behave exactly as it would if
the copies lived on distinct URIs.
"""
if not in_focus & {encrypt_sdk, decrypt_sdk}:
pytest.skip("Not in focus")
pfs = tdfs.get_platform_features()
tdfs.skip_connectrpc_skew(encrypt_sdk, decrypt_sdk, pfs)
tdfs.skip_hexless_skew(encrypt_sdk, decrypt_sdk)
# Only the decrypt side needs to tolerate the duplicate KAO; the encrypt
# side just produces a normal TDF that we mutate below.
if not decrypt_sdk.supports("multikao"):
pytest.skip(
f"{decrypt_sdk} sdk doesn't support duplicate KAOs on the same KAS+split"
)
ct_file = encrypted_tdf(
encrypt_sdk,
target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk),
attr_values=attribute_default_rsa.value_fqns,
)
original = tdfs.manifest(ct_file).encryptionInformation.keyAccess
assert original, "expected at least one KAO to duplicate"

b_file = tdfs.update_manifest(
"duplicate_kao_same_kas", ct_file, duplicate_first_kao
)
# Confirm the mutation produced a genuine same-(sid, url) duplicate.
kaos = tdfs.manifest(b_file).encryptionInformation.keyAccess
assert len(kaos) == len(original) + 1
assert kaos[0].url == kaos[-1].url
assert kaos[0].sid == kaos[-1].sid

rt_file = encrypted_tdf.rt_file(b_file, decrypt_sdk)
decrypt_sdk.decrypt(b_file, rt_file, "ztdf")
assert filecmp.cmp(pt_file, rt_file)
Loading