Skip to content

feat(sdk)!: remove the hardcoded BouncyCastle provider#364

Draft
mkleene wants to merge 6 commits intomainfrom
remove-hardcoded-provider
Draft

feat(sdk)!: remove the hardcoded BouncyCastle provider#364
mkleene wants to merge 6 commits intomainfrom
remove-hardcoded-provider

Conversation

@mkleene
Copy link
Copy Markdown
Contributor

@mkleene mkleene commented May 7, 2026

Summary by CodeRabbit

  • Refactor

    • Migrated cryptographic operations to use standard Java security interfaces instead of third-party provider-specific implementations, improving compatibility and reducing external dependencies.
    • Updated EC key handling and validation logic to align with standard cryptographic interfaces.
  • Tests

    • Enhanced test infrastructure for cryptographic provider initialization and improved test reliability with corrected assertions and variable naming.

mkleene and others added 3 commits June 11, 2025 17:57
Merges main (v0.15.0) into the fips branch.

Conflict resolution:
- ECKeyPair.java: Combined main's structure (ECCurve enum, KeyPair field,
  new methods like getPEMPublicKeyFromX509Cert) with fips's FIPS-compatible
  crypto changes (no explicit BouncyCastle provider, standard Java interfaces
  for ECPublicKey/ECPrivateKey, KeyFactory.getInstance("EC")).
- ECKeyPairTest.java: Kept fips's removal of extractPemPubKeyFromX509 test
  (which used BC-specific .getQ()), incorporated main's new
  createSymmetricKeysWithOtherCurves test, and enabled publicKeyFromECPoint
  assertion using ECCurve.getCurveName().
- NanoTDF.java: Accepted main's deletion since all NanoTDF dependencies
  (ECCMode, NanoTDFType, etc.) were also removed in main.

Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: mkleene <262667+mkleene@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Warning

Rate limit exceeded

@mkleene has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 40 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 28fad334-967d-439d-b8a6-afbe2996594f

📥 Commits

Reviewing files that changed from the base of the PR and between ed364c5 and 3a2c684.

📒 Files selected for processing (1)
  • sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java
📝 Walkthrough

Walkthrough

The PR migrates OpenTDF SDK cryptographic operations from explicit BouncyCastle provider qualification to provider-agnostic JCA/JCE lookups. BouncyCastle provider registration is centralized in the CLI command and a JUnit test extension, while all key generation, ECDH, ECDSA, and PEM conversion operations now use unqualified JCA APIs.

Changes

Provider-Agnostic Cryptography Migration

Layer / File(s) Summary
Provider Registration Infrastructure
cmdline/src/main/java/io/opentdf/platform/Command.java, sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java
Static initializer registers BouncyCastle globally in Command; new JUnit 5 extension lazily registers provider once per test suite via JUnit BeforeAllCallback.
Core Cryptographic Operations
sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java
Key-pair generation, ECDH key agreement, ECDSA signing/verification, and PEM conversion refactored to use provider-agnostic KeyPairGenerator.getInstance(...), KeyAgreement.getInstance("ECDH"), Signature.getInstance(...), and JcaPEMKeyConverter without explicit provider names. Public key compression delegates to new getCompressedECPublicKey(PublicKey) helper via provider-agnostic KeyFactory.
Public Type Alignment
sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
ECPublicKey import replaced with JDK standard java.security.interfaces.ECPublicKey.
Test Implementation Updates
sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java
Test imports switch to JDK EC key interfaces; variable naming typo corrected; assertion ordering normalized.
Test Infrastructure
sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension, sdk/src/test/resources/junit-platform.properties
Extension service descriptor registers CryptoProviderSetupExtension; JUnit platform auto-detection enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A crypto refactor hops through the code,
Casting off BouncyCastle's rigid road,
JCA calls now provider-agnostic and free,
The keys still encrypt, but unshackled they be!
One provider to bind them, now global and light—
Cryptography's future hops cleaner and bright! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title 'feat(sdk)!: remove the hardcoded BouncyCastle provider' clearly and concisely describes the main objective: removing hardcoded BouncyCastle provider usage across multiple files in the SDK.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch remove-hardcoded-provider

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@mkleene mkleene force-pushed the remove-hardcoded-provider branch from fb346eb to df4682d Compare May 7, 2026 19:05
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

X-Test Failure Report

Copy link
Copy Markdown
Contributor

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

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 refactors the platform's security handling by removing external SSL utility dependencies and introducing a custom TrustProvider based on standard JCA APIs. ECKeyPair is updated to use standard Java security interfaces, reducing direct reliance on Bouncy Castle provider-specific classes. Key feedback includes a critical missing class (CompositeX509ExtendedTrustManager) that will cause compilation errors, the use of internal Bouncy Castle SPI classes instead of standard KeyPairGenerator APIs, and a deprecated OkHttp method usage in SDKBuilder.

I am having trouble creating individual review comments. Click here to see my feedback.

sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java (245)

critical

The class CompositeX509ExtendedTrustManager is referenced here but does not appear to be defined in this file or provided in the PR. This will cause a compilation error. Please ensure all necessary utility classes are included in the changes.

sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java (12)

high

This import of an internal Bouncy Castle SPI class should be removed. The goal of this PR is to move towards provider-agnostic JCA APIs. Using implementation classes directly defeats this purpose and can lead to runtime issues if the provider is not registered or if internal APIs change.

sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java (50-54)

high

Instead of using the internal Bouncy Castle SPI class and provider-specific algorithm names like "ECDH" or "ECDSA" for key pair generation, use the standard JCA algorithm name "EC" with the KeyPairGenerator API. This ensures better compatibility across different security providers.

            generator = KeyPairGenerator.getInstance("EC");

sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java (130)

medium

The iteration over keystore types contains potential duplicates. KeyStore.getDefaultType() is often "PKCS12" on modern JVMs, leading to redundant attempts. Consider using a LinkedHashSet to maintain order while ensuring uniqueness.

        for (String type : new java.util.LinkedHashSet<>(java.util.Arrays.asList(KeyStore.getDefaultType(), "JKS", "PKCS12"))) {

sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java (413)

medium

Calling sslSocketFactory(SSLSocketFactory) without an accompanying X509TrustManager is deprecated in OkHttp 4.x. It relies on reflection to find a trust manager, which is fragile on modern JDKs. Since TrustProvider can provide a trust manager, it should be used here if available, or a default one should be explicitly provided.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

X-Test Failure Report

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

X-Test Results

✅ java-main
✅ js-main
✅ go-main

@mkleene mkleene changed the title Remove hardcoded provider feat!(sdk): remove the hardcoded BouncyCastle provider May 7, 2026
@mkleene mkleene marked this pull request as ready for review May 7, 2026 20:04
@mkleene mkleene requested review from a team as code owners May 7, 2026 20:04
@mkleene mkleene changed the title feat!(sdk): remove the hardcoded BouncyCastle provider feat(sdk)!: remove the hardcoded BouncyCastle provider May 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

X-Test Failure Report

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java (1)

46-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

KeyPairGenerator.getInstance("ECDH"/"ECDSA") are BouncyCastle-specific aliases; use standard JCA algorithm "EC" instead

The standard JCA specification defines KeyPairGenerator and KeyFactory for algorithm "EC", not "ECDH" or "ECDSA". Using "ECDH" or "ECDSA" as a KeyPairGenerator algorithm name is a BouncyCastle-specific alias that will throw NoSuchAlgorithmException whenever the security provider is not explicitly registered (via Security.addProvider()).

The SDK instantiates ECKeyPair from core API classes (KASClient.java:124, TDF.java:246), not exclusively from the CLI. SDK consumers using this library without the CLI bootstrap will encounter runtime failures. Additionally, CryptoUtils.java:49 in the same codebase already demonstrates the correct pattern: KeyPairGenerator.getInstance("EC"). The ECGenParameterSpec on line 57 already encodes the target curve, making the algorithm distinction in getInstance redundant.

🐛 Proposed fix
-            if (algorithm == ECAlgorithm.ECDH) {
-                generator = KeyPairGenerator.getInstance(ECAlgorithm.ECDH.name());
-            } else {
-                generator = KeyPairGenerator.getInstance(ECAlgorithm.ECDSA.name());
-            }
+            generator = KeyPairGenerator.getInstance("EC");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java` around lines 46 -
56, In ECKeyPair, the conditional
KeyPairGenerator.getInstance(ECAlgorithm.ECDH.name()/ECDSA.name()) is using
BouncyCastle-specific aliases and can fail; change it to always call
KeyPairGenerator.getInstance("EC") (remove the algorithm==ECAlgorithm.ECDH
check) and continue to set the curve via ECGenParameterSpec as already done;
update any related exception handling in the ECKeyPair constructor so a
NoSuchAlgorithmException still wraps/propagates as before.
🧹 Nitpick comments (2)
sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java (1)

10-16: ⚡ Quick win

Instance-level synchronized + null-check does not guarantee global once-only BC registration

JUnit 5's service-loader auto-detection can create a fresh CryptoProviderSetupExtension instance per test class. Each new instance starts with securityProvider == null, so Security.addProvider() is invoked once per test class, not once per JVM. The synchronized keyword guards concurrent access to this particular instance, which provides no protection across separate instances.

This is functionally harmless because Security.addProvider() is idempotent (it returns -1 when the provider is already registered and does not throw), but the guard logic does not achieve its apparent intent.

If a true once-per-JVM guarantee is desired, use a static field or the ExtensionContext root store:

♻️ Option A – static flag (simplest)
 public class CryptoProviderSetupExtension implements BeforeAllCallback {
-    private BouncyCastleProvider securityProvider;
+    private static final AtomicBoolean registered = new AtomicBoolean(false);
 
     `@Override`
-    public synchronized void beforeAll(ExtensionContext extensionContext) throws Exception {
-        if (this.securityProvider == null) {
-            Security.addProvider(this.securityProvider = new BouncyCastleProvider());
-        }
+    public void beforeAll(ExtensionContext extensionContext) {
+        if (registered.compareAndSet(false, true)) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java`
around lines 10 - 16, The beforeAll method in CryptoProviderSetupExtension uses
an instance-level synchronized and null-check on the instance field
securityProvider, which does not guarantee a once-per-JVM BouncyCastle
registration because JUnit may create multiple extension instances; change the
guard to a JVM-wide one by making the provider flag static (e.g., a static
volatile BouncyCastleProvider or static boolean registered) or store/check a
marker in the ExtensionContext root store, then call Security.addProvider(...)
only when that static/root-store marker indicates it has not yet been
registered; update references in beforeAll and the securityProvider field
accordingly.
sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java (1)

56-56: 💤 Low value

Remove debug System.out.println statements from tests.

Lines 56, 57, 70, and 71 print key material to stdout. This adds noise to test output and leaks key bytes to logs; none of these prints are assertions.

🧹 Proposed cleanup
-        System.out.println(keypairAPublicKey);
         System.out.println(keypairAPrivateKey);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java` at line 56,
Remove the debug System.out.println calls that print key material in the
ECKeyPairTest; specifically delete the printlns referencing variables
keypairAPublicKey, keypairAPrivateKey, keypairBPublicKey, and keypairBPrivateKey
in the ECKeyPairTest class, or replace them with meaningful assertions if you
intended to verify values—do not print key bytes to stdout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java`:
- Around line 46-56: In ECKeyPair, the conditional
KeyPairGenerator.getInstance(ECAlgorithm.ECDH.name()/ECDSA.name()) is using
BouncyCastle-specific aliases and can fail; change it to always call
KeyPairGenerator.getInstance("EC") (remove the algorithm==ECAlgorithm.ECDH
check) and continue to set the curve via ECGenParameterSpec as already done;
update any related exception handling in the ECKeyPair constructor so a
NoSuchAlgorithmException still wraps/propagates as before.

---

Nitpick comments:
In `@sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java`:
- Around line 10-16: The beforeAll method in CryptoProviderSetupExtension uses
an instance-level synchronized and null-check on the instance field
securityProvider, which does not guarantee a once-per-JVM BouncyCastle
registration because JUnit may create multiple extension instances; change the
guard to a JVM-wide one by making the provider flag static (e.g., a static
volatile BouncyCastleProvider or static boolean registered) or store/check a
marker in the ExtensionContext root store, then call Security.addProvider(...)
only when that static/root-store marker indicates it has not yet been
registered; update references in beforeAll and the securityProvider field
accordingly.

In `@sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java`:
- Line 56: Remove the debug System.out.println calls that print key material in
the ECKeyPairTest; specifically delete the printlns referencing variables
keypairAPublicKey, keypairAPrivateKey, keypairBPublicKey, and keypairBPrivateKey
in the ECKeyPairTest class, or replace them with meaningful assertions if you
intended to verify values—do not print key bytes to stdout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7af437f-f1ea-4293-8a22-70b1ab4dc371

📥 Commits

Reviewing files that changed from the base of the PR and between 9991b07 and ed364c5.

📒 Files selected for processing (7)
  • cmdline/src/main/java/io/opentdf/platform/Command.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
  • sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java
  • sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java
  • sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
  • sdk/src/test/resources/junit-platform.properties

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

X-Test Results

✅ java-main
✅ js-main
✅ go-main

Copy link
Copy Markdown
Contributor

@marythought marythought left a comment

Choose a reason for hiding this comment

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

DX / docs review

Thanks for taking this on — letting consumers bring their own provider is the right call for FIPS. A few things I'd like to see addressed before merge, mostly around making the breaking change discoverable for downstream users.

1. PR description needs a migration note

Right now the body is just CodeRabbit's auto-summary. Because Release Please auto-generates the CHANGELOG from the PR title and links back to this PR, the description is the migration doc downstream consumers will land on when they hit the ⚠ BREAKING CHANGES entry in release notes. It needs a hand-written "What breaks / How to migrate" section. Suggested structure:

  • What's changing: SDK no longer auto-registers BouncyCastleProvider.
  • Why: FIPS compatibility — consumers can now plug in their own provider.
  • Action required: Register BC (or your preferred provider) in your application's bootstrap, e.g.:
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    See cmdline/src/main/java/io/opentdf/platform/Command.java for the reference pattern.

2. There are public API removals beyond provider registration

A few changes go further than the title suggests, and I think they deserve explicit callouts in the migration note:

  • ECKeyPair.getPEMPublicKeyFromX509Cert(String) is deleted entirely. Is this intentional? If anything external depends on it, we should either restore it (using the new BC-classes-without-provider pattern) or document the removal + replacement.
  • publicKeyFromPem / privateKeyFromPem now return java.security.interfaces.ECPublicKey / ECPrivateKey instead of the BC interfaces. Any caller invoking BC-specific methods like .getQ() will fail to compile — the deleted extractPemPubKeyFromX509 test is the canonical example of this pattern.
  • compressECPublickey(String) no longer throws NoSuchProviderException. Minor, but a checked-exception signature change.

3. README "Cryptography Library" section needs updating

The current wording hedges:

Note: When using this SDK, it may be necessary to register the Bouncy Castle Provider as follows...

That was accurate when the SDK auto-registered. After this PR it's misleading. I'd update it to:

  • State clearly that the SDK no longer auto-registers a provider.
  • Spell out when registration is required vs. optional, and what the JDK default supports.
  • Mention the FIPS motivation so FIPS users know they can plug in their own provider.
  • Point at Command.java as a reference pattern.

I'd also verify the Quick Start example in the README still runs end-to-end on a stock JDK after this change. If it doesn't, the example needs the registration block added so first-time users aren't met with a runtime failure.

4. opentdf.io docs follow-up

Not in scope for this PR, but I'll audit these for samples that import org.bouncycastle.jce.interfaces.ECPublicKey or call getPEMPublicKeyFromX509Cert, and update them once this lands:

What I liked

The test infrastructure (CryptoProviderSetupExtension + META-INF/services + junit.jupiter.extensions.autodetection.enabled) is a clean way to keep BC available for tests without coupling the SDK to it. Nice contributor DX.

@mkleene
Copy link
Copy Markdown
Contributor Author

mkleene commented May 8, 2026

DX / docs review

Thanks for taking this on — letting consumers bring their own provider is the right call for FIPS. A few things I'd like to see addressed before merge, mostly around making the breaking change discoverable for downstream users.

1. PR description needs a migration note

Right now the body is just CodeRabbit's auto-summary. Because Release Please auto-generates the CHANGELOG from the PR title and links back to this PR, the description is the migration doc downstream consumers will land on when they hit the ⚠ BREAKING CHANGES entry in release notes. It needs a hand-written "What breaks / How to migrate" section. Suggested structure:

* **What's changing:** SDK no longer auto-registers `BouncyCastleProvider`.

* **Why:** FIPS compatibility — consumers can now plug in their own provider.

* **Action required:** Register BC (or your preferred provider) in your application's bootstrap, e.g.:
  ```java
  static {
      Security.addProvider(new BouncyCastleProvider());
  }
  ```
  
  
      
        
      
  
        
      
  
      
    
  See `cmdline/src/main/java/io/opentdf/platform/Command.java` for the reference pattern.

2. There are public API removals beyond provider registration

A few changes go further than the title suggests, and I think they deserve explicit callouts in the migration note:

* **`ECKeyPair.getPEMPublicKeyFromX509Cert(String)` is deleted entirely.** Is this intentional? If anything external depends on it, we should either restore it (using the new BC-classes-without-provider pattern) or document the removal + replacement.

* **`publicKeyFromPem` / `privateKeyFromPem`** now return `java.security.interfaces.ECPublicKey` / `ECPrivateKey` instead of the BC interfaces. Any caller invoking BC-specific methods like `.getQ()` will fail to compile — the deleted `extractPemPubKeyFromX509` test is the canonical example of this pattern.

* **`compressECPublickey(String)` no longer throws `NoSuchProviderException`.** Minor, but a checked-exception signature change.

3. README "Cryptography Library" section needs updating

The current wording hedges:

Note: When using this SDK, it may be necessary to register the Bouncy Castle Provider as follows...

That was accurate when the SDK auto-registered. After this PR it's misleading. I'd update it to:

* State clearly that the SDK no longer auto-registers a provider.

* Spell out when registration is required vs. optional, and what the JDK default supports.

* Mention the FIPS motivation so FIPS users know they can plug in their own provider.

* Point at `Command.java` as a reference pattern.

I'd also verify the Quick Start example in the README still runs end-to-end on a stock JDK after this change. If it doesn't, the example needs the registration block added so first-time users aren't met with a runtime failure.

4. opentdf.io docs follow-up

Not in scope for this PR, but I'll audit these for samples that import org.bouncycastle.jce.interfaces.ECPublicKey or call getPEMPublicKeyFromX509Cert, and update them once this lands:

* https://opentdf.io/sdks/tdf (Java tab)

* https://opentdf.io/sdks/policy (Java tab)

* https://opentdf.io/quickstart (Java sections)

* https://opentdf.io/llms.txt

What I liked

The test infrastructure (CryptoProviderSetupExtension + META-INF/services + junit.jupiter.extensions.autodetection.enabled) is a clean way to keep BC available for tests without coupling the SDK to it. Nice contributor DX.

I'm not sure what the right pattern is for the provider. It seems like telling them they can bring their own provider is wrong because we use bouncycastle internally, e.g. this. We need a static binding to bouncycastle but at that point we should just load the provider for them, I think, since if we are statically bound to one provider and they pass us the other then we are in trouble. Maybe it's better to just depend on the bouncycastle classes explicitly and not go through the provider interfaces? I wonder if the right thing to do is just list bouncycastle as provided and then make sure that a BC provider is registered. Not great since forgetting to do that would result in a ClassNotFoundException, which is pretty ugly.

Ideally there would be a way to parse the PEM stuff in a way that is unrelated to FIPS. We can't use https://openjdk.org/jeps/524 until java 26, which kind of seems too much to me.

I also think I made a mistake in opening this without removing the ayza dependencies, since they explicitly load a non-FIPS bouncycastle provider, which kind of precludes loading the FIPS provider.

@mkleene mkleene marked this pull request as draft May 8, 2026 02:59
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.

3 participants