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 5b46eb30f94a CAMEL-25798: camel-pqc - generateSecretKeyEncapsulation
stamps the JCE algorithm name on the secret key (#28093)
5b46eb30f94a is described below
commit 5b46eb30f94a4f8d501033932b37e79b37560845
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Oct 21 10:27:42 2026 +0200
CAMEL-25798: camel-pqc - generateSecretKeyEncapsulation stamps the JCE
algorithm name on the secret key (#28093)
Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../apache/camel/component/pqc/PQCProducer.java | 10 +-
.../PQCGenerateEncapsulationAlgorithmNameTest.java | 119 +++++++++++++++++++++
...rateEncapsulationCryptoRoundTripDESedeTest.java | 3 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 16 +++
4 files changed, 146 insertions(+), 2 deletions(-)
diff --git
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
index 53a42d5eb188..a3a696f8f965 100644
---
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
+++
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
@@ -713,6 +713,10 @@ public class PQCProducer extends DefaultProducer {
private void generateEncapsulation(Exchange exchange)
throws InvalidAlgorithmParameterException,
NoSuchAlgorithmException {
+ if
(ObjectHelper.isEmpty(getConfiguration().getSymmetricKeyAlgorithm())) {
+ throw new IllegalArgumentException("Symmetric Algorithm needs to
be specified");
+ }
+
KeyGenerator generator = keyGeneratorForExchange();
SecretKeyWithEncapsulation secEnc1;
synchronized (generator) {
@@ -720,7 +724,11 @@ public class PQCProducer extends DefaultProducer {
generator.init(
new KEMGenerateSpec(
keyPair.getPublic(),
-
getEndpoint().getConfiguration().getSymmetricKeyAlgorithm(),
+ // The mapped JCE algorithm name, as the extract
operations use: for the algorithms whose
+ // enum name differs from the JCE name
(GOST3412_2015 -> GOST3412-2015, DESEDE -> DESede)
+ // the raw name is not a resolvable cipher, and
the two ends of the exchange would disagree
+ // on the identity of the key
+
PQCSymmetricAlgorithms.valueOf(getConfiguration().getSymmetricKeyAlgorithm()).getAlgorithm(),
getEndpoint().getConfiguration().getSymmetricKeyLength()),
SecureRandomHelper.getSecureRandom());
// SecretKeyWithEncapsulation is the class to use as the secret
key, it has additional
diff --git
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCGenerateEncapsulationAlgorithmNameTest.java
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCGenerateEncapsulationAlgorithmNameTest.java
new file mode 100644
index 000000000000..b41fea0e150e
--- /dev/null
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCGenerateEncapsulationAlgorithmNameTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 java.security.Security;
+
+import javax.crypto.Cipher;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.bouncycastle.jcajce.SecretKeyWithEncapsulation;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The generate and extract KEM operations must agree on the algorithm name
stamped on the secret key, and that name
+ * must be the JCE one so the key can be handed straight to a Cipher.
+ */
+public class PQCGenerateEncapsulationAlgorithmNameTest extends
CamelTestSupport {
+
+ @EndpointInject("mock:encapsulate")
+ protected MockEndpoint resultEncapsulate;
+
+ @EndpointInject("mock:extract")
+ protected MockEndpoint resultExtract;
+
+ @Produce("direct:encapsulate")
+ protected ProducerTemplate templateEncapsulate;
+
+ @Produce("direct:noAlgorithm")
+ protected ProducerTemplate templateNoAlgorithm;
+
+ @BeforeEach
+ void addProvider() {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:encapsulate")
+
.to("pqc:keyenc?operation=generateSecretKeyEncapsulation&keyEncapsulationAlgorithm=MLKEM"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192")
+ .to("mock:encapsulate")
+
.to("pqc:keyext?operation=extractSecretKeyEncapsulation&keyEncapsulationAlgorithm=MLKEM"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192")
+ .to("mock:extract");
+
+ from("direct:noAlgorithm")
+
.to("pqc:nokey?operation=generateSecretKeyEncapsulation&keyEncapsulationAlgorithm=MLKEM");
+ }
+ };
+ }
+
+ @Test
+ void generateAndExtractStampTheSameJceName() throws Exception {
+ resultEncapsulate.expectedMessageCount(1);
+ resultExtract.expectedMessageCount(1);
+
+ templateEncapsulate.sendBody("Hello");
+
+ resultEncapsulate.assertIsSatisfied();
+ resultExtract.assertIsSatisfied();
+
+ String generated = resultEncapsulate.getExchanges().get(0).getMessage()
+ .getBody(SecretKeyWithEncapsulation.class).getAlgorithm();
+ String extracted = resultExtract.getExchanges().get(0).getMessage()
+ .getBody(SecretKeyWithEncapsulation.class).getAlgorithm();
+
+ assertEquals(PQCSymmetricAlgorithms.DESEDE.getAlgorithm(), generated,
+ "the generated key must carry the mapped JCE name");
+ assertEquals(generated, extracted, "both ends of the exchange must
agree on the key algorithm");
+
+ // the whole point of the JCE name: the key is directly usable
+ assertDoesNotThrow(() -> Cipher.getInstance(generated),
+ "the stamped name must resolve as a cipher algorithm");
+ }
+
+ @Test
+ void aMissingSymmetricAlgorithmIsRejected() {
+ CamelExecutionException e = assertThrows(CamelExecutionException.class,
+ () -> templateNoAlgorithm.sendBody("Hello"));
+
+ Throwable cause = e.getCause();
+ assertInstanceOf(IllegalArgumentException.class, cause, "expected a
configuration error but got " + cause);
+ assertTrue(cause.getMessage().contains("Symmetric Algorithm"),
+ "unexpected message: " + cause.getMessage());
+ }
+}
diff --git
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMLKEMGenerateEncapsulationCryptoRoundTripDESedeTest.java
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMLKEMGenerateEncapsulationCryptoRoundTripDESedeTest.java
index 48695e116f87..d388ad5cd831 100644
---
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMLKEMGenerateEncapsulationCryptoRoundTripDESedeTest.java
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMLKEMGenerateEncapsulationCryptoRoundTripDESedeTest.java
@@ -93,7 +93,8 @@ public class
PQCMLKEMGenerateEncapsulationCryptoRoundTripDESedeTest extends Came
templateEncapsulate.sendBody("Hello");
resultEncapsulate.assertIsSatisfied();
assertNotNull(resultEncapsulate.getExchanges().get(0).getMessage().getBody(SecretKeyWithEncapsulation.class));
- assertEquals("DESEDE",
+ // the mapped JCE name ("DESede"), not the raw enum name: the raw name
is not a resolvable cipher algorithm
+ assertEquals(PQCSymmetricAlgorithms.DESEDE.getAlgorithm(),
resultEncapsulate.getExchanges().get(0).getMessage().getBody(SecretKeyWithEncapsulation.class).getAlgorithm());
assertNotNull(resultEncrypted.getExchanges().get(0).getMessage().getBody());
assertEquals("Hello",
resultDecrypted.getExchanges().get(0).getMessage().getBody(String.class));
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 f5d645009ea4..28f2852163ab 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
@@ -310,6 +310,22 @@ With `--output=<file>` or without `--output`, the routes
are still merged, and t
the standard error, when the top-level `onException` of one file applies to
the routes of the other files
in the merged output.
+=== camel-pqc - generateSecretKeyEncapsulation stamps the JCE algorithm name
on the secret key
+
+The `SecretKeyWithEncapsulation` produced by `generateSecretKeyEncapsulation`
now carries the mapped JCE algorithm
+name, as the `extractSecretKeyEncapsulation` and
`extractSecretKeyFromEncapsulation` operations already did. Before, it
+carried the raw enum name.
+
+This is only visible for the two algorithms whose enum name differs from the
JCE name: `DESEDE` now yields `DESede`
+(was `DESEDE`), and `GOST3412_2015` now yields `GOST3412-2015` (was
`GOST3412_2015`). The raw names are not resolvable
+cipher algorithms, so a key labelled with one could not be passed to
`Cipher.getInstance(key.getAlgorithm())`, and the
+generate and extract ends of the same exchange disagreed on the key's
identity. Code asserting on the old value must
+be updated.
+
+`generateSecretKeyEncapsulation` now also rejects a missing
`symmetricKeyAlgorithm` with
+`Symmetric Algorithm needs to be specified`, as the extract operations already
did, instead of failing later inside
+BouncyCastle with `Missing argument`.
+
=== camel-jbang - camel ask asks before a call that changes something, reaches
out or runs code
`camel ask` now asks on the terminal before a file write (showing the diff), a
message to an endpoint the