This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch task-31
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 20479f1c48e6928173cd8a65225c6b88e0115001
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Mar 18 10:43:27 2026 +0100

    CAMEL-23210 - Camel-PQC: Add input validation for algorithm combinations 
and key sizes in PQCProducer
    
    Add startup validation in PQCProducer.doStart() to reject invalid
    symmetric key lengths for KEM operations. A static map defines valid
    key sizes for each of the 15 symmetric algorithms with fixed key
    requirements (AES, ARIA, CAMELLIA, CAST6, CHACHA7539, DSTU7624,
    GOST28147, GOST3412_2015, GRAIN128, HC128, HC256, SALSA20, SEED,
    SM4, DESEDE). Algorithms with variable key lengths (RC2, RC5, CAST5)
    are intentionally excluded.
    
    Validation only applies to the six KEM operation types; signature,
    lifecycle, and stateful operations are unaffected. Additionally, warn
    at startup about non-recommended hybrid combinations (RSA in hybrid
    signatures, non-NIST PQC algorithms) and log NIST parameter set
    guidance for ML-KEM, ML-DSA, and SLH-DSA.
    
    Includes 12 new unit tests in PQCInputValidationTest covering invalid
    key lengths, valid key lengths, hybrid KEM validation, and non-KEM
    passthrough scenarios.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
     # in VALID_SYMMETRIC_KEY_LENGTHS map to improve traceability and avoid
     # potential typos
     # assertThatThrownBy/assertThatCode for more precise failure messages
---
 .../apache/camel/component/pqc/PQCProducer.java    | 160 +++++++++++++++++
 .../component/pqc/PQCInputValidationTest.java      | 190 +++++++++++++++++++++
 2 files changed, 350 insertions(+)

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 d2d7993551ff..25a35c2e1d15 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
@@ -18,7 +18,9 @@ package org.apache.camel.component.pqc;
 
 import java.security.*;
 import java.security.cert.Certificate;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import javax.crypto.KeyAgreement;
 import javax.crypto.KeyGenerator;
@@ -51,6 +53,24 @@ public class PQCProducer extends DefaultProducer {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(PQCProducer.class);
 
+    // Valid symmetric key lengths per algorithm for startup validation
+    private static final Map<String, int[]> VALID_SYMMETRIC_KEY_LENGTHS = 
Map.ofEntries(
+            Map.entry(PQCSymmetricAlgorithms.AES.name(), new int[] { 128, 192, 
256 }),
+            Map.entry(PQCSymmetricAlgorithms.ARIA.name(), new int[] { 128, 
192, 256 }),
+            Map.entry(PQCSymmetricAlgorithms.CAMELLIA.name(), new int[] { 128, 
192, 256 }),
+            Map.entry(PQCSymmetricAlgorithms.CAST6.name(), new int[] { 128, 
160, 192, 224, 256 }),
+            Map.entry(PQCSymmetricAlgorithms.CHACHA7539.name(), new int[] { 
256 }),
+            Map.entry(PQCSymmetricAlgorithms.DSTU7624.name(), new int[] { 128, 
256, 512 }),
+            Map.entry(PQCSymmetricAlgorithms.GOST28147.name(), new int[] { 256 
}),
+            Map.entry(PQCSymmetricAlgorithms.GOST3412_2015.name(), new int[] { 
256 }),
+            Map.entry(PQCSymmetricAlgorithms.GRAIN128.name(), new int[] { 128 
}),
+            Map.entry(PQCSymmetricAlgorithms.HC128.name(), new int[] { 128 }),
+            Map.entry(PQCSymmetricAlgorithms.HC256.name(), new int[] { 256 }),
+            Map.entry(PQCSymmetricAlgorithms.SALSA20.name(), new int[] { 128, 
256 }),
+            Map.entry(PQCSymmetricAlgorithms.SEED.name(), new int[] { 128 }),
+            Map.entry(PQCSymmetricAlgorithms.SM4.name(), new int[] { 128 }),
+            Map.entry(PQCSymmetricAlgorithms.DESEDE.name(), new int[] { 128, 
192 }));
+
     private Signature signer;
     private KeyGenerator keyGenerator;
     private KeyPair keyPair;
@@ -249,6 +269,7 @@ public class PQCProducer extends DefaultProducer {
     @Override
     protected void doStart() throws Exception {
         super.doStart();
+        validateConfiguration();
 
         if (getConfiguration().getOperation().equals(PQCOperations.sign)
                 || 
getConfiguration().getOperation().equals(PQCOperations.verify)) {
@@ -763,4 +784,143 @@ public class PQCProducer extends DefaultProducer {
         }
     }
 
+    // ========== Configuration Validation ==========
+
+    /**
+     * Validates the producer configuration at startup to catch invalid or 
non-recommended algorithm combinations and
+     * key sizes early, before any cryptographic operation is attempted.
+     */
+    private void validateConfiguration() {
+        PQCConfiguration config = getConfiguration();
+        PQCOperations op = config.getOperation();
+
+        validateSymmetricKeyLength(config, op);
+        warnHybridCombinations(config, op);
+        logNistRecommendations(config);
+    }
+
+    private void validateSymmetricKeyLength(PQCConfiguration config, 
PQCOperations op) {
+        if (!isKEMOperation(op)) {
+            return;
+        }
+        String symAlg = config.getSymmetricKeyAlgorithm();
+        if (ObjectHelper.isEmpty(symAlg)) {
+            return;
+        }
+        int keyLen = config.getSymmetricKeyLength();
+        int[] validLengths = VALID_SYMMETRIC_KEY_LENGTHS.get(symAlg);
+        if (validLengths != null) {
+            boolean valid = false;
+            for (int len : validLengths) {
+                if (len == keyLen) {
+                    valid = true;
+                    break;
+                }
+            }
+            if (!valid) {
+                throw new IllegalArgumentException(
+                        "Invalid symmetric key length " + keyLen + " for 
algorithm " + symAlg
+                                                   + ". Valid key lengths: " + 
Arrays.toString(validLengths));
+            }
+        }
+    }
+
+    private void warnHybridCombinations(PQCConfiguration config, PQCOperations 
op) {
+        if (op == PQCOperations.hybridSign || op == 
PQCOperations.hybridVerify) {
+            String classicalAlg = config.getClassicalSignatureAlgorithm();
+            if (ObjectHelper.isNotEmpty(classicalAlg)) {
+                try {
+                    PQCClassicalSignatureAlgorithms classical = 
PQCClassicalSignatureAlgorithms.valueOf(classicalAlg);
+                    if (classical.isRSA()) {
+                        LOG.warn("Using RSA ({}) in hybrid signature mode. 
ECDSA or EdDSA (Ed25519/Ed448) "
+                                 + "is recommended for new hybrid deployments 
due to smaller signature sizes "
+                                 + "and better performance.",
+                                classicalAlg);
+                    }
+                } catch (IllegalArgumentException e) {
+                    // Unknown classical algorithm - will fail later during 
init
+                }
+            }
+            String pqcAlg = config.getSignatureAlgorithm();
+            if (ObjectHelper.isNotEmpty(pqcAlg) && isNonNistSignature(pqcAlg)) 
{
+                LOG.warn("PQC signature algorithm {} is not NIST-standardized. 
Consider using "
+                         + "ML-DSA (FIPS 204) or SLH-DSA (FIPS 205) for 
production hybrid deployments.",
+                        pqcAlg);
+            }
+        }
+
+        if (op == PQCOperations.hybridGenerateSecretKeyEncapsulation
+                || op == PQCOperations.hybridExtractSecretKeyEncapsulation
+                || op == 
PQCOperations.hybridExtractSecretKeyFromEncapsulation) {
+            String pqcAlg = config.getKeyEncapsulationAlgorithm();
+            if (ObjectHelper.isNotEmpty(pqcAlg) && isNonNistKEM(pqcAlg)) {
+                LOG.warn("PQC KEM algorithm {} is not NIST-standardized. 
Consider using "
+                         + "ML-KEM (FIPS 203) for production hybrid 
deployments.",
+                        pqcAlg);
+            }
+        }
+    }
+
+    private void logNistRecommendations(PQCConfiguration config) {
+        String kemAlg = config.getKeyEncapsulationAlgorithm();
+        if ("MLKEM".equals(kemAlg)) {
+            LOG.info("Using ML-KEM (NIST FIPS 203). Available parameter sets: "
+                     + "ML-KEM-512 (Level 1), ML-KEM-768 (Level 3, 
recommended), ML-KEM-1024 (Level 5)");
+        }
+
+        String sigAlg = config.getSignatureAlgorithm();
+        if ("MLDSA".equals(sigAlg)) {
+            LOG.info("Using ML-DSA (NIST FIPS 204). Available parameter sets: "
+                     + "ML-DSA-44 (Level 2), ML-DSA-65 (Level 3, recommended), 
ML-DSA-87 (Level 5)");
+        } else if ("SLHDSA".equals(sigAlg)) {
+            LOG.info("Using SLH-DSA (NIST FIPS 205). Stateless hash-based 
signature scheme suitable for "
+                     + "applications where stateful key management is not 
feasible");
+        }
+    }
+
+    private boolean isKEMOperation(PQCOperations op) {
+        switch (op) {
+            case generateSecretKeyEncapsulation:
+            case extractSecretKeyEncapsulation:
+            case extractSecretKeyFromEncapsulation:
+            case hybridGenerateSecretKeyEncapsulation:
+            case hybridExtractSecretKeyEncapsulation:
+            case hybridExtractSecretKeyFromEncapsulation:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private boolean isNonNistSignature(String alg) {
+        switch (alg) {
+            case "DILITHIUM":
+            case "FALCON":
+            case "PICNIC":
+            case "SNOVA":
+            case "MAYO":
+            case "SPHINCSPLUS":
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private boolean isNonNistKEM(String alg) {
+        switch (alg) {
+            case "BIKE":
+            case "HQC":
+            case "CMCE":
+            case "SABER":
+            case "FRODO":
+            case "NTRU":
+            case "NTRULPRime":
+            case "SNTRUPrime":
+            case "KYBER":
+                return true;
+            default:
+                return false;
+        }
+    }
+
 }
diff --git 
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCInputValidationTest.java
 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCInputValidationTest.java
new file mode 100644
index 000000000000..586b059d04ae
--- /dev/null
+++ 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCInputValidationTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for input validation of algorithm combinations and key sizes in 
PQCProducer. Validates that invalid symmetric
+ * key lengths are rejected at startup and that non-KEM operations are not 
affected by key length validation.
+ */
+public class PQCInputValidationTest extends CamelTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    // -- Invalid symmetric key length tests (should throw at startup) --
+
+    @Test
+    void testInvalidAESKeyLength64ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=64"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 64 for 
algorithm AES");
+    }
+
+    @Test
+    void testInvalidAESKeyLength512ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=512"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 512 for 
algorithm AES");
+    }
+
+    @Test
+    void testInvalidChaChaKeyLength128ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=CHACHA7539&symmetricKeyLength=128"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 128 for 
algorithm CHACHA7539");
+    }
+
+    @Test
+    void testInvalidGOSTKeyLength128ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=GOST28147&symmetricKeyLength=128"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 128 for 
algorithm GOST28147");
+    }
+
+    @Test
+    void testInvalidHC256KeyLength128ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=extractSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=HC256&symmetricKeyLength=128"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 128 for 
algorithm HC256");
+    }
+
+    @Test
+    void testInvalidDESedeKeyLength64ThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=64"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 64 for 
algorithm DESEDE");
+    }
+
+    @Test
+    void testInvalidKeyLengthForHybridKEMThrowsAtStartup() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=hybridGenerateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=64"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM"
+                                              + 
"&classicalKEMAlgorithm=X25519");
+
+        assertThatThrownBy(producer::start)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid symmetric key length 64 for 
algorithm AES");
+    }
+
+    // -- Valid symmetric key length tests (should start without error) --
+
+    @Test
+    void testValidAESKeyLength128Accepted() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=128"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatCode(producer::start).doesNotThrowAnyException();
+        producer.stop();
+    }
+
+    @Test
+    void testValidAESKeyLength256Accepted() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=256"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatCode(producer::start).doesNotThrowAnyException();
+        producer.stop();
+    }
+
+    @Test
+    void testValidChaChaKeyLength256Accepted() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=CHACHA7539&symmetricKeyLength=256"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatCode(producer::start).doesNotThrowAnyException();
+        producer.stop();
+    }
+
+    @Test
+    void testValidDESedeKeyLength192Accepted() throws Exception {
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=generateSecretKeyEncapsulation"
+                                              + 
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192"
+                                              + 
"&keyEncapsulationAlgorithm=MLKEM");
+
+        assertThatCode(producer::start).doesNotThrowAnyException();
+        producer.stop();
+    }
+
+    // -- Key length not validated for non-KEM operations --
+
+    @Test
+    void testKeyLengthNotValidatedForSignatureOperations() throws Exception {
+        // symmetricKeyLength=64 is invalid for AES but should not matter for 
sign operations
+        PQCProducer producer = createProducer(
+                "pqc:test?operation=sign&signatureAlgorithm=MLDSA"
+                                              + 
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=64");
+
+        assertThatCode(producer::start).doesNotThrowAnyException();
+        producer.stop();
+    }
+
+    // -- Helper --
+
+    private PQCProducer createProducer(String uri) throws Exception {
+        PQCComponent component = context.getComponent("pqc", 
PQCComponent.class);
+        PQCEndpoint endpoint = (PQCEndpoint) component.createEndpoint(uri);
+        endpoint.start();
+        return new PQCProducer(endpoint);
+    }
+}

Reply via email to