This is an automated email from the ASF dual-hosted git repository.
oscerd 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 63e856713b92 CAMEL-23844: Camel-PQC - use the mapped JCE algorithm
name when restoring the secret key (#24678)
63e856713b92 is described below
commit 63e856713b9259d93d9478ecb69f3d5d406d665e
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Jul 14 14:40:27 2026 +0200
CAMEL-23844: Camel-PQC - use the mapped JCE algorithm name when restoring
the secret key (#24678)
CAMEL-23844: Camel-PQC use the mapped JCE algorithm name when restoring the
secret key
extractSecretKeyFromEncapsulation built the restored SecretKeySpec with the
raw
PQCSymmetricAlgorithms enum name instead of the mapped JCE algorithm name,
unlike
extractEncapsulation which maps it correctly.
For algorithms whose enum name differs from the JCE name this produced a
secret key
carrying an algorithm label that is not a resolvable cipher transformation.
GOST3412_2015 is the worst case: the JCE name is GOST3412-2015, and
Cipher.getInstance("GOST3412_2015") throws NoSuchAlgorithmException, so the
restored
key was unusable downstream. DESEDE vs DESede is only a case difference,
which the
case-insensitive JCA lookup tolerates.
Use PQCSymmetricAlgorithms.valueOf(...).getAlgorithm() in both the standard
and the
hybrid extract paths.
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../apache/camel/component/pqc/PQCProducer.java | 13 +-
.../pqc/PQCExtractSecretKeyAlgorithmNameTest.java | 147 +++++++++++++++++++++
2 files changed, 157 insertions(+), 3 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 920911b999e0..6a6006f242fc 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
@@ -603,7 +603,12 @@ public class PQCProducer extends DefaultProducer {
throw new IllegalArgumentException("Symmetric Algorithm needs to
be specified");
}
- SecretKey restoredKey = new SecretKeySpec(payload.getEncoded(),
getConfiguration().getSymmetricKeyAlgorithm());
+ // Use the mapped JCE algorithm name (as extractEncapsulation does),
not the raw enum name: for algorithms
+ // whose enum name differs from the JCE name (for example
GOST3412_2015 -> GOST3412-2015) the raw name is not a
+ // resolvable cipher algorithm, so the restored key would carry an
unusable algorithm label
+ SecretKey restoredKey = new SecretKeySpec(
+ payload.getEncoded(),
+
PQCSymmetricAlgorithms.valueOf(getConfiguration().getSymmetricKeyAlgorithm()).getAlgorithm());
if (!getConfiguration().isStoreExtractedSecretKeyAsHeader()) {
exchange.getMessage().setBody(restoredKey, SecretKey.class);
@@ -770,8 +775,10 @@ public class PQCProducer extends DefaultProducer {
throw new IllegalArgumentException("Symmetric Algorithm needs to
be specified");
}
- // Create a new SecretKeySpec with the correct algorithm
- SecretKey restoredKey = new
SecretKeySpec(hybridSecretKey.getEncoded(),
getConfiguration().getSymmetricKeyAlgorithm());
+ // Create a new SecretKeySpec with the mapped JCE algorithm name (not
the raw enum name)
+ SecretKey restoredKey = new SecretKeySpec(
+ hybridSecretKey.getEncoded(),
+
PQCSymmetricAlgorithms.valueOf(getConfiguration().getSymmetricKeyAlgorithm()).getAlgorithm());
if (!getConfiguration().isStoreExtractedSecretKeyAsHeader()) {
exchange.getMessage().setBody(restoredKey, SecretKey.class);
diff --git
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCExtractSecretKeyAlgorithmNameTest.java
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCExtractSecretKeyAlgorithmNameTest.java
new file mode 100644
index 000000000000..7a5edcd9f8cd
--- /dev/null
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCExtractSecretKeyAlgorithmNameTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.bouncycastle.jcajce.spec.MLKEMParameterSpec;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.junit.jupiter.api.BeforeAll;
+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.assertNotNull;
+
+/**
+ * The secret key restored by extractSecretKeyFromEncapsulation must carry the
mapped JCE algorithm name, not the raw
+ * PQCSymmetricAlgorithms enum name. For GOST3412_2015 the raw name is not a
resolvable cipher algorithm at all.
+ */
+public class PQCExtractSecretKeyAlgorithmNameTest extends CamelTestSupport {
+
+ @EndpointInject("mock:gost")
+ protected MockEndpoint resultGost;
+
+ @EndpointInject("mock:desede")
+ protected MockEndpoint resultDesede;
+
+ @BeforeAll
+ public static void startup() {
+ ensureProviders();
+ }
+
+ /**
+ * PQCEndpoint removes the BouncyCastle providers from the JVM when it
stops, so with more than one test method in
+ * the class they must be (re-)registered before each CamelContext is
built.
+ */
+ private static void ensureProviders() {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) ==
null) {
+ Security.addProvider(new BouncyCastlePQCProvider());
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:gost")
+
.to("pqc:keyenc?operation=generateSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=GOST3412_2015&symmetricKeyLength=256")
+
.to("pqc:keyenc?operation=extractSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=GOST3412_2015&symmetricKeyLength=256")
+
.to("pqc:keyenc?operation=extractSecretKeyFromEncapsulation"
+ +
"&symmetricKeyAlgorithm=GOST3412_2015&symmetricKeyLength=256")
+ .to("mock:gost");
+
+ from("direct:desede")
+
.to("pqc:keyenc?operation=generateSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192")
+
.to("pqc:keyenc?operation=extractSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192")
+
.to("pqc:keyenc?operation=extractSecretKeyFromEncapsulation"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192")
+ .to("mock:desede");
+ }
+ };
+ }
+
+ @Test
+ void testGost3412RestoredKeyUsesJceAlgorithmName() throws Exception {
+ resultGost.expectedMessageCount(1);
+ template.sendBody("direct:gost", "Hello");
+ resultGost.assertIsSatisfied();
+
+ SecretKey restored =
resultGost.getExchanges().get(0).getMessage().getBody(SecretKey.class);
+ assertNotNull(restored);
+ // the enum name is GOST3412_2015 but the JCE name is GOST3412-2015
+ assertEquals(PQCSymmetricAlgorithms.GOST3412_2015.getAlgorithm(),
restored.getAlgorithm());
+ assertEquals("GOST3412-2015", restored.getAlgorithm());
+ // with the raw enum name this throws NoSuchAlgorithmException, so the
restored key was unusable
+ assertDoesNotThrow(() -> Cipher.getInstance(restored.getAlgorithm(),
BouncyCastleProvider.PROVIDER_NAME));
+ }
+
+ @Test
+ void testDesedeRestoredKeyUsesJceAlgorithmName() throws Exception {
+ resultDesede.expectedMessageCount(1);
+ template.sendBody("direct:desede", "Hello");
+ resultDesede.assertIsSatisfied();
+
+ SecretKey restored =
resultDesede.getExchanges().get(0).getMessage().getBody(SecretKey.class);
+ assertNotNull(restored);
+ assertEquals(PQCSymmetricAlgorithms.DESEDE.getAlgorithm(),
restored.getAlgorithm());
+ assertEquals("DESede", restored.getAlgorithm());
+ assertDoesNotThrow(() -> Cipher.getInstance(restored.getAlgorithm(),
BouncyCastleProvider.PROVIDER_NAME));
+ }
+
+ @BindToRegistry("Keypair")
+ public KeyPair setKeyPair()
+ throws NoSuchAlgorithmException, NoSuchProviderException,
+ InvalidAlgorithmParameterException {
+ ensureProviders();
+ KeyPairGenerator kpg =
KeyPairGenerator.getInstance(PQCKeyEncapsulationAlgorithms.MLKEM.getAlgorithm(),
+ PQCKeyEncapsulationAlgorithms.MLKEM.getBcProvider());
+ kpg.initialize(MLKEMParameterSpec.ml_kem_512, new SecureRandom());
+ return kpg.generateKeyPair();
+ }
+
+ @BindToRegistry("KeyGenerator")
+ public KeyGenerator setKeyGenerator() throws NoSuchAlgorithmException,
NoSuchProviderException {
+ ensureProviders();
+ return
KeyGenerator.getInstance(PQCKeyEncapsulationAlgorithms.MLKEM.getAlgorithm(),
+ PQCKeyEncapsulationAlgorithms.MLKEM.getBcProvider());
+ }
+}