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

Reply via email to