From 3140bc8615922fe5986b281e32f53be09a6f5f74 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Mon, 15 Jun 2026 15:53:45 -0400 Subject: [PATCH 01/16] ci: bump platform action SHA and enable dpop-nonce-challenge Update start-up-with-containers and start-additional-kas action SHAs to DSPX-3397-platform-service tip, and pass dpop-challenge-enabled: true so the DPoP nonce challenge tests are not skipped. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/xtest.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 48ed7fea..d22d582f 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,13 +297,14 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-up-with-containers@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true extra-keys: ${{ steps.load-extra-keys.outputs.EXTRA_KEYS }} log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} + dpop-challenge-enabled: true - name: Install uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 @@ -591,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: alpha @@ -603,7 +604,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: beta @@ -615,7 +616,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: gamma @@ -627,7 +628,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-port: 8484 @@ -639,7 +640,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -652,7 +653,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@11af44a5d4826ed281bf2e0e4e31d6ff6154b393 # pqc-enabled + uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: km2 From 61491dbcd5ccb3aa4650feee4ceb72cd6490efd7 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 09:09:23 -0400 Subject: [PATCH 02/16] fix(js-shim): wire CLIENTID, CLIENTSECRET, and DPoP into cli.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Capture caller env vars before sourcing test.env (which unconditionally resets them), then restore them — allows pytest monkeypatch to override CLIENTID=opentdf-dpop for DPoP-specific tests. - Replace hardcoded --auth opentdf:secret with ${CLIENTID:-opentdf}:${CLIENTSECRET:-secret}. - Add XT_WITH_DPOP (algorithm, e.g. ES256) and XT_WITH_DPOP_KEY (PEM path) support, wired to --dpop / --dpop-key CLI flags. - Update _dpop_client_env fixture to also export XT_WITH_DPOP=ES256 so DPoP proof generation is actually exercised in test_dpop.py. Co-Authored-By: Claude Sonnet 4.6 --- xtest/sdk/js/cli.sh | 21 ++++++++++++++++++++- xtest/test_dpop.py | 6 ++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/xtest/sdk/js/cli.sh b/xtest/sdk/js/cli.sh index d3160ca3..163d8af4 100755 --- a/xtest/sdk/js/cli.sh +++ b/xtest/sdk/js/cli.sh @@ -18,6 +18,10 @@ # XT_WITH_ATTRIBUTES [string] - Attributes to be used for encryption # XT_WITH_MIME_TYPE [string] - MIME type for the encrypted file # XT_WITH_TARGET_MODE [string] - Target spec mode for the encrypted file +# XT_WITH_DPOP [string] - Enable DPoP token binding; value selects algorithm (e.g. ES256) +# XT_WITH_DPOP_KEY [string] - Path to PEM-encoded PKCS8 private key for DPoP signing +# CLIENTID [string] - Override OIDC client ID (default: opentdf) +# CLIENTSECRET [string] - Override OIDC client secret (default: secret) # SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) @@ -124,9 +128,17 @@ if [ "$XTEST_DIR" = "/" ]; then exit 1 fi +# Capture any caller-set overrides before test.env unconditionally resets them. +_pre_clientid="${CLIENTID:-}" +_pre_clientsecret="${CLIENTSECRET:-}" + # shellcheck disable=SC1091 source "$XTEST_DIR"/test.env +# Restore caller overrides (e.g. from pytest monkeypatch for DPoP client). +[ -n "$_pre_clientid" ] && CLIENTID="$_pre_clientid" +[ -n "$_pre_clientsecret" ] && CLIENTSECRET="$_pre_clientsecret" + src_file=$(realpath "$2") dst_file=$(realpath "$(dirname "$3")")/$(basename "$3") @@ -134,9 +146,16 @@ args=( --output "$dst_file" --kasEndpoint "$KASURL" --oidcEndpoint "$KCFULLURL" - --auth opentdf:secret + --auth "${CLIENTID:-opentdf}:${CLIENTSECRET:-secret}" ) +if [ -n "$XT_WITH_DPOP" ]; then + args+=(--dpop "$XT_WITH_DPOP") +fi +if [ -n "$XT_WITH_DPOP_KEY" ]; then + args+=(--dpop-key "$XT_WITH_DPOP_KEY") +fi + args+=(--containerType tdf3) if [ -n "$XT_WITH_ATTRIBUTES" ]; then diff --git a/xtest/test_dpop.py b/xtest/test_dpop.py index 085f71fc..beacb737 100644 --- a/xtest/test_dpop.py +++ b/xtest/test_dpop.py @@ -323,9 +323,11 @@ def _skip_unless_dpop_enabled(encrypt_sdk: tdfs.SDK, in_focus: set[tdfs.SDK]) -> @pytest.fixture(autouse=True) def _dpop_client_env(monkeypatch: pytest.MonkeyPatch) -> None: - # SDK CLI shims read CLIENTID from the environment; tests in this module - # must use the DPoP-bound client provisioned by `service provision keycloak`. + # SDK CLI shims read CLIENTID/XT_WITH_DPOP from the environment; tests in + # this module must use the DPoP-bound client provisioned by + # `service provision keycloak` and enable DPoP proof generation. monkeypatch.setenv("CLIENTID", "opentdf-dpop") + monkeypatch.setenv("XT_WITH_DPOP", "ES256") def test_dpop_happy_path_roundtrip( From 286e52a8a3a4104744c558f52f535ccf57f18db9 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 09:16:41 -0400 Subject: [PATCH 03/16] fix(ci): pass OTDFCTL_HEADS to all pytest steps so otdfctl.sh uses dist binary load_otdfctl() in conftest.py reads OTDFCTL_HEADS to resolve sdk/go/dist/{tag}/otdfctl.sh. Without it, every test step fell through to sdk/go/dist/main/otdfctl.sh or the non-dist sdk/go/otdfctl.sh, which falls back to go run github.com/opentdf/otdfctl@latest instead of the built branch binary. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/xtest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index d22d582f..bfdb406b 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -687,6 +687,7 @@ jobs: KAS_DELTA_LOG_FILE: "../../${{ steps.kas-delta.outputs.log-file }}" KAS_KM1_LOG_FILE: "../../${{ steps.kas-km1.outputs.log-file }}" KAS_KM2_LOG_FILE: "../../${{ steps.kas-km2.outputs.log-file }}" + OTDFCTL_HEADS: ${{ steps.configure-go.outputs.heads }} - name: Sanitize sdk-version for artifact name id: artifact-name From 7f8a7a43010a052a7ef05234dd32619807441aa0 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 09:55:49 -0400 Subject: [PATCH 04/16] ci: bump platform action SHA to pick up DPoP htu fix Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/xtest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index bfdb406b..942e6895 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-up-with-containers@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: alpha @@ -604,7 +604,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: beta @@ -616,7 +616,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: gamma @@ -628,7 +628,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-port: 8484 @@ -640,7 +640,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -653,7 +653,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d0155a614e37de35bec0f47fe8bad18f346b9a60 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: km2 From 91748fdd44ab0cc7fe3a54e2ddb1c281851166ec Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 10:17:46 -0400 Subject: [PATCH 05/16] ci: bump platform action SHA to pick up strict_htu feature flag Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/xtest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 942e6895..9ed3381b 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-up-with-containers@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: alpha @@ -604,7 +604,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: beta @@ -616,7 +616,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: gamma @@ -628,7 +628,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-port: 8484 @@ -640,7 +640,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -653,7 +653,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@d55171e229915da2d43dbdc96057916d31be2710 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: km2 From 3a27100e9f0f3f4c2437745d0abe1d5528f40e25 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 11:57:15 -0400 Subject: [PATCH 06/16] feat(java-sdk): delegate dpop_nonce_challenge detection to binary --- xtest/sdk/java/cli.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtest/sdk/java/cli.sh b/xtest/sdk/java/cli.sh index cece9630..3b9fe545 100755 --- a/xtest/sdk/java/cli.sh +++ b/xtest/sdk/java/cli.sh @@ -118,8 +118,8 @@ if [ "$1" == "supports" ]; then exit $? ;; dpop_nonce_challenge) - echo "dpop_nonce_challenge not supported" - exit 1 + java -jar "$SCRIPT_DIR"/cmdline.jar supports dpop_nonce_challenge + exit $? ;; *) echo "Unknown feature: $2" From 3f39abfd18f081c2519ecd632edc04e1a85408e3 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 12:15:44 -0400 Subject: [PATCH 07/16] fix(ci): correct SHA pin for platform action (24d7101c094b) --- .github/workflows/xtest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 9ed3381b..0fbce5ea 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-up-with-containers@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: alpha @@ -604,7 +604,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: beta @@ -616,7 +616,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: gamma @@ -628,7 +628,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true kas-port: 8484 @@ -640,7 +640,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -653,7 +653,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c5c08867b1d74a8c86f49bd4aaef31f22 # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: km2 From 1afe14be79a996a7c5f5f5c2787792f0ae10383e Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 15:28:26 -0400 Subject: [PATCH 08/16] fix(java-cli): enable --verbose when available for silent failure diagnosis --- xtest/sdk/java/cli.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xtest/sdk/java/cli.sh b/xtest/sdk/java/cli.sh index 3b9fe545..6e281197 100755 --- a/xtest/sdk/java/cli.sh +++ b/xtest/sdk/java/cli.sh @@ -197,5 +197,9 @@ if [ -n "$XT_WITH_TARGET_MODE" ]; then args+=(--with-target-mode "$XT_WITH_TARGET_MODE") fi +if java -jar "$SCRIPT_DIR"/cmdline.jar help decrypt | grep -q -- '--verbose'; then + args+=(--verbose) +fi + echo java -jar "$SCRIPT_DIR"/cmdline.jar "${args[@]}" --file="$2" ">" "$3" java -jar "$SCRIPT_DIR"/cmdline.jar "${args[@]}" --file="$2" >"$3" From d71aa9dcaa0ef9985ff3be8a128c6be48bb644bf Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 17:36:42 -0400 Subject: [PATCH 09/16] fix(java-cli): check root help for --verbose (it is ScopeType.INHERIT on the parent command) --- xtest/sdk/java/cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtest/sdk/java/cli.sh b/xtest/sdk/java/cli.sh index 6e281197..da47de40 100755 --- a/xtest/sdk/java/cli.sh +++ b/xtest/sdk/java/cli.sh @@ -197,7 +197,7 @@ if [ -n "$XT_WITH_TARGET_MODE" ]; then args+=(--with-target-mode "$XT_WITH_TARGET_MODE") fi -if java -jar "$SCRIPT_DIR"/cmdline.jar help decrypt | grep -q -- '--verbose'; then +if java -jar "$SCRIPT_DIR"/cmdline.jar help | grep -q -- '--verbose'; then args+=(--verbose) fi From 00b8908a98850319ebc6532782c1a8ff98768948 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 16 Jun 2026 20:40:42 -0400 Subject: [PATCH 10/16] fix(ci): update platform action SHA pins to 70cb173a (fix DPoP htm validation) --- .github/workflows/xtest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 0fbce5ea..63782ead 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-up-with-containers@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: alpha @@ -604,7 +604,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: beta @@ -616,7 +616,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: gamma @@ -628,7 +628,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true kas-port: 8484 @@ -640,7 +640,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -653,7 +653,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@24d7101c094baff0c2ac31b852f929786942fded # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge with: ec-tdf-enabled: true kas-name: km2 From d5a33709a092fb331a525b63e5db07a9b42d77fc Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 18 Jun 2026 10:22:16 -0400 Subject: [PATCH 11/16] Cache java cli.sh help probes to cut JVM startup overhead Running 'java -jar cmdline.jar help' on every encrypt/decrypt paid 150-500ms of JVM startup per probe (kas-allowlist and --verbose checks). Add a jar_help() helper that caches help output to a tmpfile keyed by the jar's mtime, and discards stderr to keep JVM warnings out of test logs. --- xtest/sdk/java/cli.sh | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/xtest/sdk/java/cli.sh b/xtest/sdk/java/cli.sh index da47de40..a9a25f10 100755 --- a/xtest/sdk/java/cli.sh +++ b/xtest/sdk/java/cli.sh @@ -42,6 +42,25 @@ else exit 1 fi +# Cache `java -jar cmdline.jar help [...]` output to avoid paying JVM startup +# (typically 150-500ms) for the capability probes on every encrypt/decrypt. +# Keyed by the jar's mtime so a reinstall invalidates the cache. stderr is +# discarded to keep JVM warnings (reflective-access, agent notices) out of logs. +jar_help() { + local jar="$SCRIPT_DIR/cmdline.jar" + local mtime + mtime=$(stat -c %Y "$jar" 2>/dev/null || stat -f %m "$jar" 2>/dev/null || echo 0) + local key + key=$(printf '%s' "$*" | tr -c 'a-zA-Z0-9' '_') + local uid + uid=$(id -u 2>/dev/null || echo default) + local cache="${TMPDIR:-/tmp}/xtest-java-help-${uid}-${mtime}-${key}" + if [ ! -f "$cache" ]; then + java -jar "$jar" help "$@" >"$cache" 2>/dev/null + fi + cat "$cache" +} + if [ "$1" == "supports" ]; then case "$2" in autoconfigure | ns_grants) @@ -135,7 +154,7 @@ args=( ) # when we added support for KAS allowlist, we changed the platform endpoint format to require scheme -if java -jar "$SCRIPT_DIR"/cmdline.jar help decrypt | grep kas-allowlist; then +if jar_help decrypt | grep -q kas-allowlist; then args+=("--platform-endpoint=$PLATFORMURL") else args+=("--platform-endpoint=$PLATFORMENDPOINT") @@ -197,7 +216,7 @@ if [ -n "$XT_WITH_TARGET_MODE" ]; then args+=(--with-target-mode "$XT_WITH_TARGET_MODE") fi -if java -jar "$SCRIPT_DIR"/cmdline.jar help | grep -q -- '--verbose'; then +if jar_help | grep -q -- '--verbose'; then args+=(--verbose) fi From 7746ba2f611e092743c0ba55493fb39dd0ce5d60 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 18 Jun 2026 12:37:20 -0400 Subject: [PATCH 12/16] Address PR review: atomic cache write, [[ ]] tests, redact auth secret - java cli.sh: write jar_help cache to a temp file then mv, so concurrent xdist workers never read a partially written cache (gemini/coderabbit). - java/js cli.sh: use [[ ]] for the new conditionals (SonarCloud SC2292). - js cli.sh: mask the --auth secret in echoed commands so CI logs don't capture client credentials (coderabbit). --- xtest/sdk/java/cli.sh | 8 ++++++-- xtest/sdk/js/cli.sh | 26 ++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/xtest/sdk/java/cli.sh b/xtest/sdk/java/cli.sh index a9a25f10..d8013a08 100755 --- a/xtest/sdk/java/cli.sh +++ b/xtest/sdk/java/cli.sh @@ -55,8 +55,12 @@ jar_help() { local uid uid=$(id -u 2>/dev/null || echo default) local cache="${TMPDIR:-/tmp}/xtest-java-help-${uid}-${mtime}-${key}" - if [ ! -f "$cache" ]; then - java -jar "$jar" help "$@" >"$cache" 2>/dev/null + if [[ ! -f "$cache" ]]; then + # Write to a process-unique temp file, then rename: concurrent xdist + # workers see either no cache or the complete file, never a partial read. + local tmp="${cache}.$$" + java -jar "$jar" help "$@" >"$tmp" 2>/dev/null + mv -f "$tmp" "$cache" fi cat "$cache" } diff --git a/xtest/sdk/js/cli.sh b/xtest/sdk/js/cli.sh index 163d8af4..11c1dfe1 100755 --- a/xtest/sdk/js/cli.sh +++ b/xtest/sdk/js/cli.sh @@ -136,8 +136,8 @@ _pre_clientsecret="${CLIENTSECRET:-}" source "$XTEST_DIR"/test.env # Restore caller overrides (e.g. from pytest monkeypatch for DPoP client). -[ -n "$_pre_clientid" ] && CLIENTID="$_pre_clientid" -[ -n "$_pre_clientsecret" ] && CLIENTSECRET="$_pre_clientsecret" +[[ -n "$_pre_clientid" ]] && CLIENTID="$_pre_clientid" +[[ -n "$_pre_clientsecret" ]] && CLIENTSECRET="$_pre_clientsecret" src_file=$(realpath "$2") dst_file=$(realpath "$(dirname "$3")")/$(basename "$3") @@ -204,6 +204,24 @@ if ! cd "$SCRIPT_DIR"; then exit 1 fi +# Echo a CLI invocation with the --auth secret masked, so CI logs never capture +# client credentials. The real (unmasked) args are still used for execution. +echo_redacted() { + local out=() a mask_next=0 + for a in "$@"; do + if [[ "$mask_next" == 1 ]]; then + out+=("${a%%:*}:***") + mask_next=0 + elif [[ "$a" == "--auth" ]]; then + out+=("$a") + mask_next=1 + else + out+=("$a") + fi + done + echo "${out[@]}" +} + if [ "$1" == "encrypt" ]; then if npx $CTL help | grep autoconfigure; then args+=(--policyEndpoint "$PLATFORMURL" --autoconfigure true) @@ -224,7 +242,7 @@ if [ "$1" == "encrypt" ]; then args+=(--tdfSpecVersion "$XT_WITH_TARGET_MODE") fi - echo npx $CTL encrypt "$src_file" "${args[@]}" + echo_redacted npx $CTL encrypt "$src_file" "${args[@]}" npx $CTL encrypt "$src_file" "${args[@]}" elif [ "$1" == "decrypt" ]; then if [ "$XT_WITH_VERIFY_ASSERTIONS" == 'false' ]; then @@ -246,7 +264,7 @@ elif [ "$1" == "decrypt" ]; then args+=(--ignoreAllowList) fi - echo npx $CTL decrypt "$src_file" "${args[@]}" + echo_redacted npx $CTL decrypt "$src_file" "${args[@]}" npx $CTL decrypt "$src_file" "${args[@]}" else echo "Incorrect argument provided" From 6f4d8342fb522d042dd88e86f886aaac5c1146b8 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Wed, 24 Jun 2026 10:01:33 -0400 Subject: [PATCH 13/16] fix(ci): remove duplicate OTDFCTL_HEADS env key in xtest.yml --- .github/workflows/xtest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 63782ead..064fa4cb 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -687,7 +687,6 @@ jobs: KAS_DELTA_LOG_FILE: "../../${{ steps.kas-delta.outputs.log-file }}" KAS_KM1_LOG_FILE: "../../${{ steps.kas-km1.outputs.log-file }}" KAS_KM2_LOG_FILE: "../../${{ steps.kas-km2.outputs.log-file }}" - OTDFCTL_HEADS: ${{ steps.configure-go.outputs.heads }} - name: Sanitize sdk-version for artifact name id: artifact-name From 363786a5fe3f4d5ee389d9afb348dfc02b5b6c34 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Wed, 24 Jun 2026 17:39:33 -0400 Subject: [PATCH 14/16] ci(dpop): enforce require_nonce on additional KAS instances Pass dpop-challenge-enabled:true to the alpha/beta/gamma/delta/km1/km2 start-additional-kas steps and re-pin that action to the go-branch commit (2305c4ab) that adds the input. Fixes test_dpop_rejects_tampered_nonce, which targets the alpha KAS that previously never set require_nonce. --- .github/workflows/xtest.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 064fa4cb..6be843db 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: alpha @@ -600,11 +600,12 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: beta @@ -612,11 +613,12 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: gamma @@ -624,11 +626,12 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-port: 8484 @@ -636,11 +639,12 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -649,11 +653,12 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: km2 @@ -662,6 +667,7 @@ jobs: log-type: json pqc-enabled: ${{ steps.pqc-check.outputs.supported == 'true' }} root-key: ${{ steps.km-check.outputs.root_key }} + dpop-challenge-enabled: true - name: Run attribute based configuration tests if: ${{ steps.multikas.outputs.supported == 'true' }} From 3c796cfd49b84ade651badc5a11dd6b90c2b00ff Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 25 Jun 2026 06:28:22 -0400 Subject: [PATCH 15/16] ci(dpop): repin platform test actions to 4a19b297; make bearer test nonce-agnostic - Repin start-up-with-containers and all start-additional-kas steps to opentdf/platform DSPX-3397-platform-go-sdk @4a19b297 (adds dpop enforce alongside require_nonce on additional KAS). - test_dpop_bearer_scheme: route both rewrap calls through a new _post_rewrap_with_nonce_retry helper so the test passes whether or not the target KAS enforces require_nonce (satisfies the use_dpop_nonce challenge before asserting lenient 200 + WARN). --- .github/workflows/xtest.yml | 14 +++++----- xtest/test_dpop.py | 53 ++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 6be843db..32ebcc77 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@70cb173a4858f80ac9e8ac9b4949ceac6ac72b4b # dpop-nonce-challenge + uses: opentdf/platform/test/start-up-with-containers@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: alpha @@ -605,7 +605,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: beta @@ -618,7 +618,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: gamma @@ -631,7 +631,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-port: 8484 @@ -644,7 +644,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -658,7 +658,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@2305c4ab986b49774b5b4f50f19dce6e30c8bec4 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: km2 diff --git a/xtest/test_dpop.py b/xtest/test_dpop.py index beacb737..18844774 100644 --- a/xtest/test_dpop.py +++ b/xtest/test_dpop.py @@ -305,6 +305,37 @@ def _post_rewrap( ) +def _post_rewrap_with_nonce_retry( + call: RewrapCall, + key: DPoPKey, + *, + access_token: str, + auth_scheme: str = "DPoP", +) -> requests.Response: + """POST a rewrap, transparently satisfying a `require_nonce` challenge. + + Mints a fresh DPoP proof (no nonce) and sends it. If the KAS replies with a + `401` carrying a `DPoP-Nonce` header (the `use_dpop_nonce` challenge), the + proof is re-minted with that nonce and the request is retried once, keeping + the same `auth_scheme`. This makes callers agnostic to whether + `require_nonce` is enabled on the target KAS: with it off the first request + is returned as-is; with it on the second (nonce-bearing) request is. + """ + proof = key.sign_dpop_proof(htm="POST", htu=call.url, access_token=access_token) + response = _post_rewrap( + call, access_token=access_token, dpop_proof=proof, auth_scheme=auth_scheme + ) + nonce = response.headers.get("DPoP-Nonce") + if response.status_code == 401 and nonce: + proof = key.sign_dpop_proof( + htm="POST", htu=call.url, access_token=access_token, nonce=nonce + ) + response = _post_rewrap( + call, access_token=access_token, dpop_proof=proof, auth_scheme=auth_scheme + ) + return response + + def _assert_unauthorized(response: requests.Response) -> None: assert response.status_code == 401, response.text # Confirm the rejection is actually a DPoP-related challenge so a 401 @@ -428,16 +459,14 @@ def test_dpop_bearer_scheme_warns_but_accepted_for_dpop_token( dpop_access = _get_dpop_access_token() rewrap_call = _signed_rewrap_request(ct_file, dpop_access.key) - bearer_proof = dpop_access.key.sign_dpop_proof( - htm="POST", - htu=rewrap_call.url, - access_token=dpop_access.token, - ) + # Both calls go through the nonce-retry helper so the test passes whether or + # not the target KAS has `require_nonce` enabled: when it is, the lenient + # accept (and the WARN) only happen after the nonce challenge is satisfied. mark = audit_logs.mark("before_bearer_scheme_request") - bearer_response = _post_rewrap( + bearer_response = _post_rewrap_with_nonce_retry( rewrap_call, + dpop_access.key, access_token=dpop_access.token, - dpop_proof=bearer_proof, auth_scheme="Bearer", ) @@ -449,16 +478,10 @@ def test_dpop_bearer_scheme_warns_but_accepted_for_dpop_token( ) # Compliant path control: same proof key, same token, just the right scheme. - # Distinct jti via fresh proof so the server's replay cache doesn't reject it. - dpop_proof = dpop_access.key.sign_dpop_proof( - htm="POST", - htu=rewrap_call.url, - access_token=dpop_access.token, - ) - dpop_response = _post_rewrap( + dpop_response = _post_rewrap_with_nonce_retry( rewrap_call, + dpop_access.key, access_token=dpop_access.token, - dpop_proof=dpop_proof, auth_scheme="DPoP", ) assert dpop_response.status_code == 200, dpop_response.text From 9da1005eb2de96f7f2aa241e3c066f348ab349b9 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 25 Jun 2026 07:39:01 -0400 Subject: [PATCH 16/16] ci(dpop): repin platform test actions to 8ccc608d (enforce now opt-in) Platform action split DPoP enforcement into a separate dpop-enforce-required input (default false); xtest only passes dpop-challenge-enabled, so require_nonce stays on and global enforce stays off, restoring the non-DPoP suite. --- .github/workflows/xtest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml index 32ebcc77..1f5b484b 100644 --- a/.github/workflows/xtest.yml +++ b/.github/workflows/xtest.yml @@ -297,7 +297,7 @@ jobs: ######## SPIN UP PLATFORM BACKEND ############# - name: Check out and start up platform with deps/containers id: run-platform - uses: opentdf/platform/test/start-up-with-containers@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk + uses: opentdf/platform/test/start-up-with-containers@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk with: platform-ref: ${{ fromJSON(needs.resolve-versions.outputs.platform-tag-to-sha)[matrix.platform-tag] }} ec-tdf-enabled: true @@ -592,7 +592,7 @@ jobs: - name: Start additional kas id: kas-alpha if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: alpha @@ -605,7 +605,7 @@ jobs: - name: Start additional kas id: kas-beta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: beta @@ -618,7 +618,7 @@ jobs: - name: Start additional kas id: kas-gamma if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: gamma @@ -631,7 +631,7 @@ jobs: - name: Start additional kas id: kas-delta if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-port: 8484 @@ -644,7 +644,7 @@ jobs: - name: Start additional KM kas (km1) id: kas-km1 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true key-management: ${{ steps.km-check.outputs.supported }} @@ -658,7 +658,7 @@ jobs: - name: Start additional KM kas (km2) id: kas-km2 if: ${{ steps.multikas.outputs.supported == 'true' }} - uses: opentdf/platform/test/start-additional-kas@4a19b297a8962e4c58d542929fa9a3d0c9824e08 # DSPX-3397-platform-go-sdk: require_nonce on additional KAS + uses: opentdf/platform/test/start-additional-kas@8ccc608d2947f7f868d63e3741d3e78dc0ee88ce # DSPX-3397-platform-go-sdk: require_nonce on additional KAS with: ec-tdf-enabled: true kas-name: km2