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());
+    }
+}

Reply via email to