This is an automated email from the ASF dual-hosted git repository.
acosentino 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 8c43aa4f700a CAMEL-23210 - Camel-PQC: Add input validation for
algorithm combinations and key sizes in PQCProducer (#22074)
8c43aa4f700a is described below
commit 8c43aa4f700aa607f5bc5ac9b8704472ffe2f9fc
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Mar 18 15:26:32 2026 +0100
CAMEL-23210 - Camel-PQC: Add input validation for algorithm combinations
and key sizes in PQCProducer (#22074)
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]>
---
.../apache/camel/component/pqc/PQCProducer.java | 157 +++++++++++++++
.../component/pqc/PQCInputValidationTest.java | 223 +++++++++++++++++++++
2 files changed, 380 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..d3e7ef4c029a 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,10 @@ 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 java.util.Set;
import javax.crypto.KeyAgreement;
import javax.crypto.KeyGenerator;
@@ -51,6 +54,51 @@ public class PQCProducer extends DefaultProducer {
private static final Logger LOG =
LoggerFactory.getLogger(PQCProducer.class);
+ // Valid symmetric key lengths per algorithm for startup validation.
+ // RC5 is excluded because it supports arbitrary key lengths (0-2040 bits).
+ 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.RC2.name(), new int[] { 40, 64,
128 }),
+ Map.entry(PQCSymmetricAlgorithms.ARIA.name(), new int[] { 128,
192, 256 }),
+ Map.entry(PQCSymmetricAlgorithms.CAMELLIA.name(), new int[] { 128,
192, 256 }),
+ Map.entry(PQCSymmetricAlgorithms.CAST5.name(), new int[] { 40, 64,
128 }),
+ 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 }));
+
+ // Signature algorithms not part of NIST FIPS 204/205 post-quantum
standards.
+ // XMSS/XMSSMT are NIST SP 800-208 but are stateful and not in FIPS
204/205.
+ private static final Set<String> NON_FIPS_PQ_SIGNATURES = Set.of(
+ PQCSignatureAlgorithms.XMSS.name(),
+ PQCSignatureAlgorithms.XMSSMT.name(),
+ PQCSignatureAlgorithms.DILITHIUM.name(),
+ PQCSignatureAlgorithms.FALCON.name(),
+ PQCSignatureAlgorithms.PICNIC.name(),
+ PQCSignatureAlgorithms.SNOVA.name(),
+ PQCSignatureAlgorithms.MAYO.name(),
+ PQCSignatureAlgorithms.SPHINCSPLUS.name());
+
+ // KEM algorithms not part of NIST FIPS 203 post-quantum standard.
+ private static final Set<String> NON_FIPS_PQ_KEMS = Set.of(
+ PQCKeyEncapsulationAlgorithms.BIKE.name(),
+ PQCKeyEncapsulationAlgorithms.HQC.name(),
+ PQCKeyEncapsulationAlgorithms.CMCE.name(),
+ PQCKeyEncapsulationAlgorithms.SABER.name(),
+ PQCKeyEncapsulationAlgorithms.FRODO.name(),
+ PQCKeyEncapsulationAlgorithms.NTRU.name(),
+ PQCKeyEncapsulationAlgorithms.NTRULPRime.name(),
+ PQCKeyEncapsulationAlgorithms.SNTRUPrime.name(),
+ PQCKeyEncapsulationAlgorithms.KYBER.name());
+
private Signature signer;
private KeyGenerator keyGenerator;
private KeyPair keyPair;
@@ -249,6 +297,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 +812,112 @@ 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) &&
NON_FIPS_PQ_SIGNATURES.contains(pqcAlg)) {
+ LOG.warn("PQC signature algorithm {} is not part of NIST FIPS
204/205. 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) &&
NON_FIPS_PQ_KEMS.contains(pqcAlg)) {
+ LOG.warn("PQC KEM algorithm {} is not part of NIST FIPS 203.
Consider using "
+ + "ML-KEM (FIPS 203) for production hybrid
deployments.",
+ pqcAlg);
+ }
+ }
+ }
+
+ private void logNistRecommendations(PQCConfiguration config) {
+ String kemAlg = config.getKeyEncapsulationAlgorithm();
+ if (PQCKeyEncapsulationAlgorithms.MLKEM.name().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 (PQCSignatureAlgorithms.MLDSA.name().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 (PQCSignatureAlgorithms.SLHDSA.name().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;
+ }
+ }
+
}
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..64a7ceb98860
--- /dev/null
+++
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCInputValidationTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+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 {
+
+ private final List<PQCEndpoint> createdEndpoints = new ArrayList<>();
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @AfterEach
+ void cleanupEndpoints() {
+ for (PQCEndpoint ep : createdEndpoints) {
+ try {
+ ep.stop();
+ } catch (Exception ignored) {
+ }
+ }
+ createdEndpoints.clear();
+ }
+
+ // -- 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");
+
+ try {
+ assertThatCode(producer::start).doesNotThrowAnyException();
+ } finally {
+ producer.stop();
+ }
+ }
+
+ @Test
+ void testValidAESKeyLength256Accepted() throws Exception {
+ PQCProducer producer = createProducer(
+ "pqc:test?operation=generateSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=AES&symmetricKeyLength=256"
+ +
"&keyEncapsulationAlgorithm=MLKEM");
+
+ try {
+ assertThatCode(producer::start).doesNotThrowAnyException();
+ } finally {
+ producer.stop();
+ }
+ }
+
+ @Test
+ void testValidChaChaKeyLength256Accepted() throws Exception {
+ PQCProducer producer = createProducer(
+ "pqc:test?operation=generateSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=CHACHA7539&symmetricKeyLength=256"
+ +
"&keyEncapsulationAlgorithm=MLKEM");
+
+ try {
+ assertThatCode(producer::start).doesNotThrowAnyException();
+ } finally {
+ producer.stop();
+ }
+ }
+
+ @Test
+ void testValidDESedeKeyLength192Accepted() throws Exception {
+ PQCProducer producer = createProducer(
+ "pqc:test?operation=generateSecretKeyEncapsulation"
+ +
"&symmetricKeyAlgorithm=DESEDE&symmetricKeyLength=192"
+ +
"&keyEncapsulationAlgorithm=MLKEM");
+
+ try {
+ assertThatCode(producer::start).doesNotThrowAnyException();
+ } finally {
+ 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");
+
+ try {
+ assertThatCode(producer::start).doesNotThrowAnyException();
+ } finally {
+ producer.stop();
+ }
+ }
+
+ // -- Helper --
+
+ private PQCProducer createProducer(String uri) throws Exception {
+ PQCComponent component = context.getComponent("pqc",
PQCComponent.class);
+ PQCEndpoint endpoint = (PQCEndpoint) component.createEndpoint(uri);
+ createdEndpoints.add(endpoint);
+ endpoint.start();
+ return new PQCProducer(endpoint);
+ }
+}