Skip to content

feat(sdk): add comprehensive DPoP (RFC 9449) support (DSPX-3397)#374

Draft
dmihalcik-virtru wants to merge 6 commits into
mainfrom
DSPX-3397-java-sdk
Draft

feat(sdk): add comprehensive DPoP (RFC 9449) support (DSPX-3397)#374
dmihalcik-virtru wants to merge 6 commits into
mainfrom
DSPX-3397-java-sdk

Conversation

@dmihalcik-virtru

Copy link
Copy Markdown
Member

Summary

Adds comprehensive DPoP (RFC 9449) support to the Java SDK as part of the Keycloak v26 upgrade and DPoP implementation effort.

Related Jira: https://virtru.atlassian.net/browse/DSPX-3397
Test Scenario: xtest/scenarios/DSPX-3397.yaml (in tests repo)

Changes

Core DPoP Infrastructure

  • TokenSource: Added server-issued nonce caching with per-origin storage (Map<String, String> nonceCache)
  • TokenSource: Extended getAuthHeaders() to accept optional nonce parameter for proof generation
  • TokenSource: Added cacheNonce() method to store nonces from server responses
  • AuthInterceptor: Updated to cache DPoP-Nonce headers from successful (200) responses

Configuration & Extensibility

  • SDKBuilder: Added dpopKey(RSAKey) method to allow caller-supplied DPoP keys
  • SDKBuilder: Auto-generates ephemeral RSA-2048 key if none provided
  • Command.java: Added supports dpop CLI subcommand for xtest feature detection (exits 0)

Implementation Notes

  • Uses Nimbus OAuth2 SDK's DefaultDPoPProofFactory for RFC 9449 compliant proof generation
  • DPoP proofs include required claims: jti, htm, htu, iat (plus ath for resource endpoints)
  • Current implementation uses RSA-2048/RS256 (spec suggests EC P-256/ES256 as alternative)
  • Nonce caching infrastructure is in place; full 401 retry with nonce challenges deferred (requires Connect RPC interceptor refactoring)

Feature Detection

The Java SDK now exposes DPoP support via:

tdf supports dpop  # exits 0 if supported, 1 otherwise

The xtest wrapper in the tests repo at xtest/sdk/java/cli.sh should call this command to detect support.

Testing

  • Existing DPoP-related tests in SDKBuilderTest and KASClientTest continue to pass
  • Tests verify DPoP headers are sent correctly (Authorization: DPoP <token> + DPoP header with proof)
  • New nonce caching methods are in place for future 401 retry testing

Related PRs

This PR is part of a multi-repo feature implementation. Coordinate with:

  • tests repo: DSPX-3397-kc26-dpop branch (xtest integration tests + otdf-local KC26 bump)
  • platform repo: DSPX-3397-platform-service branch (server-side DPoP validation)
  • platform repo: DSPX-3397-platform-go-sdk branch (Go SDK DPoP support)
  • web-sdk repo: DSPX-3397-web-sdk branch (TypeScript/JS DPoP support)

Checklist

  • DPoP proof generation (htm/htu/iat/jti/ath)
  • Nonce caching infrastructure
  • DpopKey configuration in SDKBuilder
  • Feature detection via CLI (supports dpop)
  • Existing tests pass
  • Full 401 retry with nonce (deferred - requires Connect RPC changes)
  • xtest integration tests (pending tests repo coordination)

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d0340ad-5dc8-49a4-91d1-d7525b58721c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-3397-java-sdk

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for custom DPoP keys in the SDKBuilder and implements DPoP nonce caching in TokenSource and AuthInterceptor to handle server-issued nonces. It also adds a new "supports" subcommand to the CLI to check for feature support (e.g., "dpop"). Regarding the feedback, the "supports" subcommand should avoid calling System.exit() directly, as this abruptly terminates the JVM and hinders testing; instead, it should implement Callable and return the exit code.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cmdline/src/main/java/io/opentdf/platform/Command.java
@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

Copy link
Copy Markdown
Contributor

@dmihalcik-virtru dmihalcik-virtru changed the title feat(java-sdk): add comprehensive DPoP (RFC 9449) support (DSPX-3397) feat(java-sdk): DSPX-3397 Comprehensive DPoP (RFC 9449) support Jun 12, 2026
@dmihalcik-virtru dmihalcik-virtru changed the title feat(java-sdk): DSPX-3397 Comprehensive DPoP (RFC 9449) support feat(sdk): DSPX-3397 Comprehensive DPoP (RFC 9449) support Jun 12, 2026
@dmihalcik-virtru dmihalcik-virtru changed the title feat(sdk): DSPX-3397 Comprehensive DPoP (RFC 9449) support feat(sdk): add comprehensive DPoP (RFC 9449) support (DSPX-3397) Jun 12, 2026
dmihalcik-virtru and others added 6 commits June 12, 2026 10:41
- Add server-issued nonce caching infrastructure in TokenSource with per-origin storage
- Add dpopKey() method to SDKBuilder for caller-supplied RSA keys (defaults to auto-generated ephemeral key)
- Update AuthInterceptor to cache DPoP-Nonce from successful responses
- Add 'supports dpop' CLI command for xtest feature detection
- Extend TokenSource.getAuthHeaders() to accept optional nonce parameter for proof generation

Implementation uses Nimbus OAuth2 SDK's DefaultDPoPProofFactory for RFC 9449 compliant
DPoP proof generation with htm/htu/iat/jti claims (plus ath for resource endpoints).

Current implementation uses RSA-2048/RS256 for DPoP keys. The SDK already had DPoP proof
generation via Nimbus OAuth2 SDK; this PR adds nonce support infrastructure and makes
the DPoP key configurable.

Note: Full 401 retry logic with nonce challenges requires Connect RPC interceptor changes
and is deferred to future work. Nonce caching infrastructure is in place for when retry
logic is added.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
- AuthInterceptor.kt: fix resp.code→resp.status and resp.message.request()
  compilation errors; use ThreadLocal<URL> to thread request URL into
  responseFunction for nonce caching; change private→internal so
  SDKBuilder.java can access dpopRetryInterceptor(); add dpopRetryInterceptor()
  OkHttp interceptor that caches DPoP-Nonce and retries 401 once
- TokenSource.java: wrap nonce String as new Nonce(nonce) to match
  DefaultDPoPProofFactory.createDPoPJWT signature; generalize RSAKey to
  JWK+JWSAlgorithm to support EC keys for ES256/ES384/ES512
- SDKBuilder.java: update to JWK+JWSAlgorithm, separate SRT key from DPoP
  key (EC DPoP key auto-generates RSA for SRT), wire dpopRetryInterceptor
  into OkHttpClient for KAS and all platform services; add dpopAlgorithm()
  builder method
- Add TokenSourceTest and DPoPRetryInterceptorTest (6 new tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Add DPoP configuration flags to the tdf cmdline tool (shared by encrypt
and decrypt via buildSDK()):
- --dpop[=<alg>]: enable DPoP; optional algorithm (RS256, RS384, RS512,
  ES256, ES384, ES512); defaults to RS256; generates ephemeral key
- --dpop-key <path>: use PEM-encoded private key from file; algorithm
  inferred from key type (EC or RSA); combinable with --dpop=<alg>

Both flags work for encrypt and decrypt subcommands. Help text contains
"dpop" so the grep probe matches: encrypt --help | grep -i dpop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…e.INHERIT

Add scope = CommandLine.ScopeType.INHERIT to --dpop and --dpop-key so they
appear in `help encrypt` and `help decrypt` (not just the parent `tdf` help),
allowing the tests-repo cli.sh probe to pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Gemini review: System.exit() in Supports.run() abruptly terminates
the JVM and prevents unit testing. Switch to Callable<Integer> so
picocli handles the exit code via CommandLine.execute().

Also remove `required = true` from --client-id, --client-secret, and
--platform-endpoint so `supports dpop` can run without auth credentials
(it performs a local capability check, not a platform call).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… imports

Adds EC key, origin-isolated nonce, and empty-nonce guard tests to
TokenSourceTest; removes three nl.altindag.ssl imports from SDKBuilder
that were added without a corresponding pom.xml dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

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