This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4c4172410627 CAMEL-25795: camel-pqc - stopping an endpoint no longer
removes the Bouncy Castle providers from the JVM (#28089)
4c4172410627 is described below
commit 4c417241062798d27ff1967ea841c37688ac9fad
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Oct 20 21:02:22 2026 +0200
CAMEL-25795: camel-pqc - stopping an endpoint no longer removes the Bouncy
Castle providers from the JVM (#28089)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
---
.../apache/camel/component/pqc/PQCEndpoint.java | 20 +++---
.../pqc/PQCEndpointProviderLifecycleTest.java | 81 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 ++++
3 files changed, 102 insertions(+), 11 deletions(-)
diff --git
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCEndpoint.java
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCEndpoint.java
index 42dc26432838..3a78b651d698 100644
---
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCEndpoint.java
+++
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCEndpoint.java
@@ -60,6 +60,15 @@ public class PQCEndpoint extends DefaultEndpoint {
return new PQCProducer(this);
}
+ /**
+ * Registers the BouncyCastle providers if they are not installed yet.
+ * <p>
+ * The providers are deliberately never removed again. {@link Security} is
JVM-global and shared with every other
+ * endpoint, every other {@code CamelContext} and all non-Camel code in
the same JVM, and nothing records which
+ * component installed a given provider. Removing one on stop therefore
tears it out from under unrelated users -
+ * including the sibling {@code pqc} endpoint of the same route and the
statically initialised
+ * {@code PQCDefault*Material} classes of this very component, which also
register BouncyCastle and never remove it.
+ */
@Override
public void doStart() throws Exception {
super.doStart();
@@ -71,17 +80,6 @@ public class PQCEndpoint extends DefaultEndpoint {
}
}
- @Override
- public void doStop() throws Exception {
- if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) {
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
- }
- if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) !=
null) {
- Security.removeProvider(BouncyCastlePQCProvider.PROVIDER_NAME);
- }
- super.doStop();
- }
-
public PQCConfiguration getConfiguration() {
return configuration;
}
diff --git
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndpointProviderLifecycleTest.java
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndpointProviderLifecycleTest.java
new file mode 100644
index 000000000000..bef20c0f91f1
--- /dev/null
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndpointProviderLifecycleTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.junit.jupiter.api.Test;
+
+import static java.security.Security.getProvider;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * The BouncyCastle providers live in the JVM-global {@link
java.security.Security} registry, so a PQC endpoint must
+ * never remove them on stop - other endpoints, other CamelContexts and
unrelated code in the same JVM rely on them.
+ */
+public class PQCEndpointProviderLifecycleTest {
+
+ @Test
+ void stoppingTheContextKeepsTheProvidersRegistered() throws Exception {
+ try (CamelContext context = new DefaultCamelContext()) {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:sign").to("pqc:sign?operation=sign&signatureAlgorithm=MLDSA");
+ }
+ });
+ context.start();
+
+ assertNotNull(getProvider(BouncyCastleProvider.PROVIDER_NAME), "BC
must be registered while running");
+ assertNotNull(getProvider(BouncyCastlePQCProvider.PROVIDER_NAME),
"BCPQC must be registered while running");
+
+ context.stop();
+
+ assertNotNull(getProvider(BouncyCastleProvider.PROVIDER_NAME),
+ "stopping a PQC endpoint must not de-register the
JVM-global BC provider");
+ assertNotNull(getProvider(BouncyCastlePQCProvider.PROVIDER_NAME),
+ "stopping a PQC endpoint must not de-register the
JVM-global BCPQC provider");
+ }
+ }
+
+ @Test
+ void stoppingOneEndpointLeavesTheProviderForTheOther() throws Exception {
+ try (CamelContext context = new DefaultCamelContext()) {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:a").routeId("a").to("pqc:a?operation=sign&signatureAlgorithm=MLDSA");
+
from("direct:b").routeId("b").to("pqc:b?operation=sign&signatureAlgorithm=MLDSA");
+ }
+ });
+ context.start();
+
+ // stopping the first route disposes its endpoint. The second
route keeps its own Signature either way,
+ // so what this pins is the provider: on main the stop
de-registers it for the whole JVM.
+ context.getRouteController().stopRoute("a");
+ context.removeEndpoints("pqc:a*");
+
+ context.createProducerTemplate().sendBody("direct:b", "Hello");
+
+ assertNotNull(getProvider(BouncyCastleProvider.PROVIDER_NAME),
+ "a sibling PQC endpoint must still see the BC provider");
+ }
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 56e5ff0a682a..9e7a7d47dc0a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -272,6 +272,18 @@ values such as `AES`, which are pure ASCII. A deployment
running on a JVM whose
name identically to UTF-8 would, however, derive a different key than the
previous version, and could not decrypt
material produced by it.
+=== camel-pqc - the BouncyCastle providers stay registered when an endpoint
stops
+
+Stopping a `pqc` endpoint no longer removes the `BC` and `BCPQC` providers
from `java.security.Security`.
+
+`java.security.Security` is JVM-global, and the endpoint only added a provider
when it was absent, so stopping one
+endpoint could remove a provider it had not installed - breaking the other
`pqc` endpoint of the same route, other
+`CamelContext` instances, components such as camel-crypto, camel-ssh and
camel-xmlsecurity, and any non-Camel code in
+the same JVM.
+
+An application that relied on the providers being de-registered on shutdown
must now remove them itself with
+`Security.removeProvider("BC")` and `Security.removeProvider("BCPQC")`.
+
=== camel-jbang - camel validate normalize writes one file per input file to
an output directory
`camel validate normalize <files> --output=<dir>` now writes one normalized
file per input file into the