Skip to content

CAMEL-25795: camel-pqc - stopping an endpoint no longer removes the Bouncy Castle providers from the JVM - #28089

Merged
davsclaus merged 5 commits into
apache:mainfrom
oscerd:fix/CAMEL-25795-pqc-bc-provider-lifecycle
Oct 20, 2026
Merged

davsclaus merged 5 commits into
apache:mainfrom
oscerd:fix/CAMEL-25795-pqc-bc-provider-lifecycle

Conversation

@oscerd

@oscerd oscerd commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Fixes CAMEL-25795.

Problem

PQCEndpoint.doStop() unconditionally removed the BC and BCPQC providers from java.security.Security:

if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) {
    Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
}

doStart() only adds a provider when it is absent, so an endpoint routinely removed one it had not installed.
java.security.Security is process-global, so this tears the provider out from under:

  • the sibling pqc endpoint of the same route — the canonical pqc:sign + pqc:verify pair in this component's own
    documentation breaks itself, since stopping one de-registers BC for the other;
  • other CamelContext instances in the same JVM;
  • camel-crypto, camel-crypto-pgp, camel-as2, camel-ssh, camel-xmlsecurity and the JSSE stack;
  • all non-Camel code in the JVM.

After the removal, every Signature.getInstance(..., "BC") fails with NoSuchProviderException. The removal can also
race with another thread that is mid-operation.

CAMEL-22016 added the null guards but did not address provider ownership.

Fix

The providers are now left registered. Per-endpoint ownership tracking does not solve this — endpoint A adds the
provider, endpoint B sees it present and skips, then A stops and removes it, breaking B. Security records no
ownership and there is no reference counting across endpoints or across the JVM.

Leaving them registered is also what the rest of the codebase already does: camel-as2, camel-crypto-pgp and this
component's own 21 statically-initialised PQCDefault*Material classes all register BouncyCastle and never remove it.
PQCEndpoint was the only place in the entire repository that removed a BouncyCastle provider.

The now-empty doStop() override is removed, and the rationale is recorded on doStart().

Tests

New PQCEndpointProviderLifecycleTest:

  • stoppingTheContextKeepsTheProvidersRegistered — both providers are still registered after context.stop();
  • stoppingOneEndpointLeavesTheOtherUsable — a sibling pqc endpoint still works after the first one is removed.

Both tests fail on main and pass with this change. Full camel-pqc module build is green: 241 tests, 0 failures,
no regenerated-file drift.

Note for reviewers

Nearly every existing test in this module carries a Security.addProvider(new BouncyCastleProvider()) in @BeforeAll.
That workaround exists precisely because of this bug. Those blocks are now redundant, but removing ~90 of them is out
of scope here — it is folded into CAMEL-25802.

Upgrade guide

Added a 4.24 entry: an application that relied on the providers being de-registered on shutdown must now remove them
itself.


Reported, analysed and fixed by Claude Code (AI agent) on behalf of @oscerd.

🤖 Generated with Claude Code

@oscerd
oscerd requested review from davsclaus and gnodet October 19, 2026 09:12
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@gnodet-bot gnodet-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.

Minor test resilience note (low severity).

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet gnodet added the lts label Oct 19, 2026
@github-actions github-actions Bot removed the lts label Oct 19, 2026

@gnodet-bot gnodet-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.

Re-review after new commits: the fix is correct. Removing the doStop() override that was tearing JVM-global Bouncy Castle providers out from under sibling endpoints and other code is the right approach — providers registered by other components or by static initializers should not be evicted on endpoint stop. The test exercises the exact race path described in the bug. One minor Javadoc nit below.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus davsclaus 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.

Thanks @oscerd. Removing doStop() is the right call: Security is JVM-global, and the endpoint removed providers it had not installed, so the pqc:sign + pqc:verify pair of the docs could break itself. The camel-as2 and camel-crypto-pgp precedent and the upgrade-guide entry cover the behaviour change.

One small note, non-blocking: in stoppingOneEndpointLeavesTheOtherUsable the sendBody("direct:b", ...) most likely also succeeds on main, as the signer of endpoint b already holds its Signature instance. The test fails on main through the getProvider assertion, so it still guards the fix, the name just promises a bit more than the send checks.

The gnodet-bot <p/> nit is still open; fine either way, <p/> is common in the code base.

Claude Code on behalf of Claus Ibsen

@gnodet-bot gnodet-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.

Following up on my earlier review — the route-ID fix (✅) and the test rename (✅) are both in. One nit remains open: the <p/> in the doStart() Javadoc.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@oscerd

oscerd commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @davsclaus. You are right that the send succeeds on main too — the surviving endpoint already holds its own Signature, so what the test actually pins is the provider still being registered. Renamed to stoppingOneEndpointLeavesTheProviderForTheOther with a comment saying so, in 9555d23.

On the <p/> nit from gnodet-bot: left as is, since it is the prevailing style in this code base.

Claude Code on behalf of @oscerd

@oscerd
oscerd requested a review from davsclaus October 19, 2026 13:40

@davsclaus davsclaus 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.

Re-reviewed the new commit: the second test is now named for what it pins (the provider stays registered for the other endpoint), and the comment says why. LGTM.

The two gnodet-bot threads on the <p/> in the PQCEndpoint javadoc are still open; <p> would settle them.

Claude Code on behalf of Claus Ibsen

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-pqc
  • docs

🔬 Scalpel shadow comparison — Scalpel: 8 of 707 tested, 26 compile-only — current: 8 all tested

Maveniverse Scalpel detected 8 affected modules (current approach: 8).

Skip-tests mode would test 8 modules (2 direct + 7 downstream), skip tests for 26 (generated code, meta-modules)

Modules Scalpel would test (8)
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-pqc ← components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCEndpoint.java, components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndpointProviderLifecycleTest.java
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (26)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (35 modules, 5m 25s total)

Total reactor time: 5m 25s

Module Duration Status
Camel :: Launcher 53.5s SUCCESS
Camel :: JBang :: MCP 43.3s SUCCESS
Camel :: JBang :: Plugin :: Validate 28.8s SUCCESS
Camel :: Catalog :: Camel Catalog 23.3s SUCCESS
Camel :: YAML DSL :: Validator 23.0s SUCCESS
Camel :: YAML DSL 22.1s SUCCESS
Camel :: Component DSL 18.2s SUCCESS
Camel :: Kamelet Main 15.3s SUCCESS
Camel :: Docs 14.7s SUCCESS
Camel :: JBang :: Plugin :: Testing 13.9s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 13.3s SUCCESS
Camel :: YAML DSL :: Deserializers 8.0s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 7.6s SUCCESS
Camel :: Catalog :: Camel Route Parser 7.4s SUCCESS
Camel :: All Components Sync point 5.2s SUCCESS
Camel :: Assembly 4.0s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 4.0s SUCCESS
Camel :: YAML DSL :: Maven Plugins 3.4s SUCCESS
Camel :: Catalog :: Maven 3.1s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 3.1s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.9s SUCCESS
Camel :: Coverage 1.3s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.3s SUCCESS
Camel :: Catalog :: Dummy Component 1.1s SUCCESS
Camel :: JBang :: Main 1.0s SUCCESS
Camel :: Catalog :: Console 0.8s SUCCESS
Camel :: JBang :: Integration tests 0.8s SUCCESS
Camel :: Launcher :: Container 0.7s SUCCESS
Camel :: Endpoint DSL :: Support 0.7s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.6s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a
Camel :: PQC n/a

Top 20 slowest modules:

  • Camel :: Launcher (53.5s)
  • Camel :: JBang :: MCP (43.3s)
  • Camel :: JBang :: Plugin :: Validate (28.8s)
  • Camel :: Catalog :: Camel Catalog (23.3s)
  • Camel :: YAML DSL :: Validator (23.0s)
  • Camel :: YAML DSL (22.1s)
  • Camel :: Component DSL (18.2s)
  • Camel :: Kamelet Main (15.3s)
  • Camel :: Docs (14.7s)
  • Camel :: JBang :: Plugin :: Testing (13.9s)
  • Camel :: JBang :: Plugin :: Kubernetes (13.3s)
  • Camel :: YAML DSL :: Deserializers (8.0s)
  • Camel :: Catalog :: Camel Report Maven Plugin (7.6s)
  • Camel :: Catalog :: Camel Route Parser (7.4s)
  • Camel :: All Components Sync point (5.2s)
  • Camel :: Assembly (4.0s)
  • Camel :: YAML DSL :: Validator Maven Plugin (4.0s)
  • Camel :: YAML DSL :: Maven Plugins (3.4s)
  • Camel :: Catalog :: Maven (3.1s)
  • Camel :: Catalog :: Suggest (deprecated) (3.1s)

⚙️ View full build and test results

oscerd and others added 5 commits October 20, 2026 12:52
…ouncy Castle providers from the JVM

PQCEndpoint.doStop() unconditionally removed the BC and BCPQC providers from
java.security.Security, which is JVM-global. doStart() only adds a provider when
it is absent, so an endpoint routinely removed one it had not installed - tearing
it out from under the sibling pqc endpoint of the same route, other CamelContexts,
camel-crypto/camel-ssh/camel-xmlsecurity, and all non-Camel code in the same JVM.

Per-endpoint ownership tracking does not fix this: endpoint A adds the provider,
endpoint B sees it present and skips, then A stops and removes it, breaking B.
Since Security records no ownership, the providers are now simply left registered,
which is what camel-as2, camel-crypto-pgp and this component's own statically
initialised PQCDefault*Material classes already do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
…ycle test

Relying on the auto-generated "route1" id would break silently if Camel's default
naming scheme ever changed. The routes now carry explicit ids, which also reads
better at the stopRoute call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
…it pins

The send to the surviving route succeeds on main too, since that endpoint already holds
its own Signature. What the test actually guards is the provider still being registered,
so the name and a comment now say that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
…g <p/> in the PQCEndpoint javadoc

javadoc doclint rejects the self-closing form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd

oscerd commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main and pushed one more commit.

  • Rebase. The only conflict was the 4.24 upgrade guide, where several camel-pqc PRs had added their note at the same spot. This note now sits right after the existing camel-pqc - an unknown hybridKdfAlgorithm is rejected section, at a different place from the other open camel-pqc PRs, so they stop conflicting with each other.
  • Review feedback. The commit "CAMEL-25795: Address review feedback - <p> instead of the self-closing <p/> in the PQCEndpoint javadoc" settles the two <p/> notes on the doStart() javadoc that were still open when the review was written.

Verification: camel-pqc on JDK 21 (mvn clean install) and JDK 25 (mvn test: 276 tests, 0 failures); reactor from camel-pqc through the catalog and DSL modules BUILD SUCCESS, no regenerated-file drift.

Claude Code on behalf of oscerd

@oscerd
oscerd force-pushed the fix/CAMEL-25795-pqc-bc-provider-lifecycle branch from 9555d23 to 333577d Compare October 20, 2026 12:01

@gnodet-bot gnodet-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.

✅ LGTM — The fix is correct and well-tested.

The doStop() removal is the right minimal change: since Security.addProvider() is JVM-global, removing providers on endpoint stop would break sibling endpoints sharing the same Bouncy Castle providers. Both test scenarios (stoppingTheContextKeepsTheProvidersRegistered and stoppingOneEndpointLeavesTheProviderForTheOther) correctly validate this behavior. The upgrade guide entry accurately documents the behavioral change.

Minor polish already in place: explicit routeId("a")/routeId("b") pinning prevents test brittleness, and the <p/> → <p> javadoc fix is correct.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus davsclaus 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.

LGTM, thanks. The rebase only moves the upgrade-guide note, and 333577d changes <p/> to <p>.

One thing about merge order: this branch and #28094 (and #28099, which contains #28094's commits) still conflict in the 4.24 upgrade guide, whichever is merged first. The cause is on the #28094 side: two of its paragraphs were left at the end of the camel-pqc - an unknown hybridKdfAlgorithm is rejected section, which is exactly where this note goes. Details are on #28094. Once they are moved there, all of these merge cleanly in any order.

Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@davsclaus davsclaus added this to the 4.24.0 milestone Oct 20, 2026
@davsclaus davsclaus added the bug Something isn't working label Oct 20, 2026
@davsclaus
davsclaus merged commit 4c41724 into apache:main Oct 20, 2026
7 checks passed
@oscerd
oscerd deleted the fix/CAMEL-25795-pqc-bc-provider-lifecycle branch October 21, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working components docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants