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

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

commit 2be5b92232aa9d39bef57ab98dd4268d11e9db83
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Mar 17 10:19:22 2026 +0100

    CAMEL-PQC: Enforce key status checks before cryptographic operations in 
PQCProducer
    
    PQCProducer previously allowed cryptographic operations (signing, 
verification,
    KEM encapsulation/extraction) to proceed regardless of the key's lifecycle
    status. A key that had been explicitly revoked or expired via the
    KeyLifecycleManager could still be used for all operations, defeating the
    purpose of key lifecycle management and creating a potential security gap.
    
    This commit adds key status enforcement to PQCProducer that gates all
    cryptographic operations based on the key's metadata status:
    
    - REVOKED keys are now rejected for ALL operations (sign, verify, 
encapsulate,
      extract) with an IllegalStateException. Revoked keys indicate a compromise
      and must never be used, not even for verification.
    
    - EXPIRED keys are rejected for producing operations (sign, encapsulate,
      hybridSign, hybridGenerateSecretKeyEncapsulation) but remain allowed for
      consuming operations (verify, extract). This preserves the ability to 
verify
      old signatures or extract secrets from existing encapsulations while
      preventing new cryptographic material from being generated with stale 
keys.
    
    - DEPRECATED keys (typically set during rotation) continue to function for
      all operations but now emit a WARN-level log advising the user to rotate
      to an active key. This provides a graceful transition period.
    
    - ACTIVE and PENDING_ROTATION keys are always permitted without restriction.
    
    A new configuration flag 'strictKeyLifecycle' (default: true) controls this
    enforcement. Setting it to false restores the previous permissive behavior.
    Enforcement is also naturally skipped when no KeyLifecycleManager is 
configured
    or when the exchange does not carry a CamelPQCKeyId header, ensuring full
    backward compatibility with existing routes that do not use lifecycle
    management.
    
    The enforcement is implemented as a pre-check in PQCProducer.process() that
    runs before the operation dispatch. It uses two helper methods to classify
    operations: isCryptographicOperation() filters out lifecycle and stateful
    management operations that should not be gated, and isProducingOperation()
    distinguishes key-producing operations (sign, encapsulate) from 
key-consuming
    ones (verify, extract) to apply the differentiated EXPIRED key policy.
    
    Includes 12 new unit tests in PQCKeyStatusEnforcementTest covering every
    status/operation combination: REVOKED rejection for sign and verify, EXPIRED
    rejection for sign with allowance for verify, DEPRECATED warning behavior,
    ACTIVE and PENDING_ROTATION acceptance, strictKeyLifecycle=false bypass, and
    the no-KEY_ID-header skip path. All 126 camel-pqc tests pass.
---
 .../component/pqc/PQCComponentConfigurer.java      |   6 +
 .../camel/component/pqc/PQCEndpointConfigurer.java |   6 +
 .../camel/component/pqc/PQCEndpointUriFactory.java |   3 +-
 .../org/apache/camel/component/pqc/pqc.json        |  14 +-
 .../camel/component/pqc/PQCConfiguration.java      |  23 ++
 .../apache/camel/component/pqc/PQCProducer.java    | 100 +++++-
 .../component/pqc/PQCKeyStatusEnforcementTest.java | 381 +++++++++++++++++++++
 7 files changed, 525 insertions(+), 8 deletions(-)

diff --git 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
index a4da0eb7933c..9044003da182 100644
--- 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
+++ 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java
@@ -71,6 +71,8 @@ public class PQCComponentConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": 
getOrCreateConfiguration(target).setSigner(property(camelContext, 
java.security.Signature.class, value)); return true;
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": 
getOrCreateConfiguration(target).setStoreExtractedSecretKeyAsHeader(property(camelContext,
 boolean.class, value)); return true;
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": 
getOrCreateConfiguration(target).setStrictKeyLifecycle(property(camelContext, 
boolean.class, value)); return true;
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": 
getOrCreateConfiguration(target).setSymmetricKeyAlgorithm(property(camelContext,
 java.lang.String.class, value)); return true;
         case "symmetrickeylength":
@@ -128,6 +130,8 @@ public class PQCComponentConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": return java.security.Signature.class;
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": return boolean.class;
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": return boolean.class;
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": return java.lang.String.class;
         case "symmetrickeylength":
@@ -181,6 +185,8 @@ public class PQCComponentConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": return getOrCreateConfiguration(target).getSigner();
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": return 
getOrCreateConfiguration(target).isStoreExtractedSecretKeyAsHeader();
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": return 
getOrCreateConfiguration(target).isStrictKeyLifecycle();
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": return 
getOrCreateConfiguration(target).getSymmetricKeyAlgorithm();
         case "symmetrickeylength":
diff --git 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java
 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java
index 1d15c2a783b7..fe426f7b55dd 100644
--- 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java
+++ 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java
@@ -57,6 +57,8 @@ public class PQCEndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": 
target.getConfiguration().setSigner(property(camelContext, 
java.security.Signature.class, value)); return true;
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": 
target.getConfiguration().setStoreExtractedSecretKeyAsHeader(property(camelContext,
 boolean.class, value)); return true;
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": 
target.getConfiguration().setStrictKeyLifecycle(property(camelContext, 
boolean.class, value)); return true;
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": 
target.getConfiguration().setSymmetricKeyAlgorithm(property(camelContext, 
java.lang.String.class, value)); return true;
         case "symmetrickeylength":
@@ -107,6 +109,8 @@ public class PQCEndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": return java.security.Signature.class;
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": return boolean.class;
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": return boolean.class;
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": return java.lang.String.class;
         case "symmetrickeylength":
@@ -153,6 +157,8 @@ public class PQCEndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "signer": return target.getConfiguration().getSigner();
         case "storeextractedsecretkeyasheader":
         case "storeExtractedSecretKeyAsHeader": return 
target.getConfiguration().isStoreExtractedSecretKeyAsHeader();
+        case "strictkeylifecycle":
+        case "strictKeyLifecycle": return 
target.getConfiguration().isStrictKeyLifecycle();
         case "symmetrickeyalgorithm":
         case "symmetricKeyAlgorithm": return 
target.getConfiguration().getSymmetricKeyAlgorithm();
         case "symmetrickeylength":
diff --git 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java
 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java
index 60e66d94b1e8..1f1bfe4747fe 100644
--- 
a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java
+++ 
b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java
@@ -23,7 +23,7 @@ public class PQCEndpointUriFactory extends 
org.apache.camel.support.component.En
     private static final Set<String> SECRET_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(21);
+        Set<String> props = new HashSet<>(22);
         props.add("classicalKEMAlgorithm");
         props.add("classicalKeyAgreement");
         props.add("classicalKeyPair");
@@ -43,6 +43,7 @@ public class PQCEndpointUriFactory extends 
org.apache.camel.support.component.En
         props.add("signatureAlgorithm");
         props.add("signer");
         props.add("storeExtractedSecretKeyAsHeader");
+        props.add("strictKeyLifecycle");
         props.add("symmetricKeyAlgorithm");
         props.add("symmetricKeyLength");
         PROPERTY_NAMES = Collections.unmodifiableSet(props);
diff --git 
a/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
 
b/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
index a7ad60e9a84c..04323a8b95c0 100644
--- 
a/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
+++ 
b/components/camel-pqc/src/generated/resources/META-INF/org/apache/camel/component/pqc/pqc.json
@@ -44,10 +44,11 @@
     "signatureAlgorithm": { "index": 17, "kind": "property", "displayName": 
"Signature Algorithm", "group": "advanced", "label": "advanced", "required": 
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", 
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", 
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", "c [...]
     "signer": { "index": 18, "kind": "property", "displayName": "Signer", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote": 
"", "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField": 
"configuration", "description": "The Signer to be used" },
     "storeExtractedSecretKeyAsHeader": { "index": 19, "kind": "property", 
"displayName": "Store Extracted Secret Key As Header", "group": "advanced", 
"label": "advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField": 
"configuration", "description": "In the context of extractSec [...]
-    "symmetricKeyAlgorithm": { "index": 20, "kind": "property", "displayName": 
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced", 
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ 
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", 
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", 
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "co [...]
-    "symmetricKeyLength": { "index": 21, "kind": "property", "displayName": 
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "The required length of 
the symmetric key used" },
-    "healthCheckConsumerEnabled": { "index": 22, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
-    "healthCheckProducerEnabled": { "index": 23, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
+    "strictKeyLifecycle": { "index": 20, "kind": "property", "displayName": 
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "Whether to enforce key 
status checks before cryptographic  [...]
+    "symmetricKeyAlgorithm": { "index": 21, "kind": "property", "displayName": 
"Symmetric Key Algorithm", "group": "advanced", "label": "advanced", 
"required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ 
"AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", 
"DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", 
"SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "co [...]
+    "symmetricKeyLength": { "index": 22, "kind": "property", "displayName": 
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "The required length of 
the symmetric key used" },
+    "healthCheckConsumerEnabled": { "index": 23, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
+    "healthCheckProducerEnabled": { "index": 24, "kind": "property", 
"displayName": "Health Check Producer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all producer based health checks 
from this component. Notice: Camel has by default disabled all producer based 
health-checks. You can turn on produce [...]
   },
   "headers": {
     "CamelPQCOperation": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The operation we want to perform", "constantName": 
"org.apache.camel.component.pqc.PQCConstants#OPERATION" },
@@ -94,7 +95,8 @@
     "signatureAlgorithm": { "index": 16, "kind": "parameter", "displayName": 
"Signature Algorithm", "group": "advanced", "label": "advanced", "required": 
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", 
"SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", 
"SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", " [...]
     "signer": { "index": 17, "kind": "parameter", "displayName": "Signer", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "java.security.Signature", "deprecated": false, "deprecationNote": 
"", "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField": 
"configuration", "description": "The Signer to be used" },
     "storeExtractedSecretKeyAsHeader": { "index": 18, "kind": "parameter", 
"displayName": "Store Extracted Secret Key As Header", "group": "advanced", 
"label": "advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.pqc.PQCConfiguration", "configurationField": 
"configuration", "description": "In the context of extractSe [...]
-    "symmetricKeyAlgorithm": { "index": 19, "kind": "parameter", 
"displayName": "Symmetric Key Algorithm", "group": "advanced", "label": 
"advanced", "required": false, "type": "enum", "javaType": "java.lang.String", 
"enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", 
"CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", 
"HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "c [...]
-    "symmetricKeyLength": { "index": 20, "kind": "parameter", "displayName": 
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "The required length of 
the symmetric key used" }
+    "strictKeyLifecycle": { "index": 19, "kind": "parameter", "displayName": 
"Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "Whether to enforce key 
status checks before cryptographic [...]
+    "symmetricKeyAlgorithm": { "index": 20, "kind": "parameter", 
"displayName": "Symmetric Key Algorithm", "group": "advanced", "label": 
"advanced", "required": false, "type": "enum", "javaType": "java.lang.String", 
"enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", 
"CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", 
"HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "c [...]
+    "symmetricKeyLength": { "index": 21, "kind": "parameter", "displayName": 
"Symmetric Key Length", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 
128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", 
"configurationField": "configuration", "description": "The required length of 
the symmetric key used" }
   }
 }
diff --git 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java
 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java
index be91d6ffb789..938e2f706866 100644
--- 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java
+++ 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java
@@ -105,6 +105,15 @@ public class PQCConfiguration implements Cloneable {
     @Metadata(label = "advanced", autowired = true)
     private KeyLifecycleManager keyLifecycleManager;
 
+    @UriParam(defaultValue = "true",
+              description = "Whether to enforce key status checks before 
cryptographic operations. "
+                            + "When enabled, REVOKED keys are rejected for all 
operations, "
+                            + "EXPIRED keys are rejected for 
signing/encapsulation but allowed for verification/extraction, "
+                            + "and DEPRECATED keys produce a warning but still 
function. "
+                            + "Requires a KeyLifecycleManager and a 
CamelPQCKeyId header to be set.")
+    @Metadata(label = "advanced")
+    private boolean strictKeyLifecycle = true;
+
     public PQCOperations getOperation() {
         return operation;
     }
@@ -316,6 +325,20 @@ public class PQCConfiguration implements Cloneable {
         this.keyLifecycleManager = keyLifecycleManager;
     }
 
+    public boolean isStrictKeyLifecycle() {
+        return strictKeyLifecycle;
+    }
+
+    /**
+     * Whether to enforce key status checks before cryptographic operations. 
When enabled, REVOKED keys are rejected for
+     * all operations, EXPIRED keys are rejected for signing/encapsulation but 
allowed for verification/extraction, and
+     * DEPRECATED keys produce a warning but still function. Requires a 
KeyLifecycleManager and a CamelPQCKeyId header
+     * to be set.
+     */
+    public void setStrictKeyLifecycle(boolean strictKeyLifecycle) {
+        this.strictKeyLifecycle = strictKeyLifecycle;
+    }
+
     // *************************************************
     //
     // *************************************************
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 5ab1c2d89a0a..d2d7993551ff 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
@@ -41,12 +41,16 @@ import org.bouncycastle.jcajce.spec.KEMGenerateSpec;
 import org.bouncycastle.pqc.jcajce.interfaces.LMSPrivateKey;
 import org.bouncycastle.pqc.jcajce.interfaces.XMSSMTPrivateKey;
 import org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A Producer which sign or verify a payload
  */
 public class PQCProducer extends DefaultProducer {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(PQCProducer.class);
+
     private Signature signer;
     private KeyGenerator keyGenerator;
     private KeyPair keyPair;
@@ -62,7 +66,9 @@ public class PQCProducer extends DefaultProducer {
 
     @Override
     public void process(Exchange exchange) throws Exception {
-        switch (determineOperation(exchange)) {
+        PQCOperations operation = determineOperation(exchange);
+        enforceKeyStatus(exchange, operation);
+        switch (operation) {
             case sign:
                 signature(exchange);
                 break;
@@ -143,6 +149,98 @@ public class PQCProducer extends DefaultProducer {
         return getEndpoint().getConfiguration();
     }
 
+    /**
+     * Enforces key status checks before cryptographic operations when strict 
key lifecycle mode is enabled. Looks up
+     * the key metadata via the configured {@link KeyLifecycleManager} using 
the {@code CamelPQCKeyId} header.
+     *
+     * <ul>
+     * <li>REVOKED keys are rejected for all operations (sign, verify, 
encapsulate, extract).</li>
+     * <li>EXPIRED keys are rejected for signing/encapsulation but allowed for 
verification/extraction.</li>
+     * <li>DEPRECATED keys produce a WARN log but still function (transition 
period).</li>
+     * <li>ACTIVE and PENDING_ROTATION keys are always allowed.</li>
+     * </ul>
+     */
+    private void enforceKeyStatus(Exchange exchange, PQCOperations operation) 
throws Exception {
+        if (!getConfiguration().isStrictKeyLifecycle()) {
+            return;
+        }
+
+        KeyLifecycleManager klm = getConfiguration().getKeyLifecycleManager();
+        if (klm == null) {
+            return;
+        }
+
+        if (!isCryptographicOperation(operation)) {
+            return;
+        }
+
+        String keyId = exchange.getMessage().getHeader(PQCConstants.KEY_ID, 
String.class);
+        if (ObjectHelper.isEmpty(keyId)) {
+            return;
+        }
+
+        KeyMetadata metadata = klm.getKeyMetadata(keyId);
+        if (metadata == null) {
+            return;
+        }
+
+        KeyMetadata.KeyStatus status = metadata.getStatus();
+
+        switch (status) {
+            case REVOKED:
+                throw new IllegalStateException(
+                        "Key '" + keyId + "' has been revoked and cannot be 
used for any cryptographic operation. "
+                                                + "Reason: " + 
(metadata.getDescription() != null
+                                                        ? 
metadata.getDescription() : "not specified"));
+            case EXPIRED:
+                if (isProducingOperation(operation)) {
+                    throw new IllegalStateException(
+                            "Key '" + keyId + "' has expired and cannot be 
used for " + operation
+                                                    + ". Expired keys can only 
be used for verification or extraction operations.");
+                }
+                LOG.info("Using expired key '{}' for {} operation 
(verification/extraction of existing data)", keyId,
+                        operation);
+                break;
+            case DEPRECATED:
+                LOG.warn("Key '{}' is deprecated. Consider rotating to an 
active key. Operation: {}", keyId, operation);
+                break;
+            case ACTIVE:
+            case PENDING_ROTATION:
+            default:
+                break;
+        }
+    }
+
+    private boolean isCryptographicOperation(PQCOperations operation) {
+        switch (operation) {
+            case sign:
+            case verify:
+            case hybridSign:
+            case hybridVerify:
+            case generateSecretKeyEncapsulation:
+            case extractSecretKeyEncapsulation:
+            case extractSecretKeyFromEncapsulation:
+            case hybridGenerateSecretKeyEncapsulation:
+            case hybridExtractSecretKeyEncapsulation:
+            case hybridExtractSecretKeyFromEncapsulation:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private boolean isProducingOperation(PQCOperations operation) {
+        switch (operation) {
+            case sign:
+            case hybridSign:
+            case generateSecretKeyEncapsulation:
+            case hybridGenerateSecretKeyEncapsulation:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     @Override
     public PQCEndpoint getEndpoint() {
         return (PQCEndpoint) super.getEndpoint();
diff --git 
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyStatusEnforcementTest.java
 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyStatusEnforcementTest.java
new file mode 100644
index 000000000000..46a97da273ec
--- /dev/null
+++ 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyStatusEnforcementTest.java
@@ -0,0 +1,381 @@
+/*
+ * 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.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.List;
+
+import org.apache.camel.BindToRegistry;
+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.component.pqc.lifecycle.FileBasedKeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyMetadata;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests for key status enforcement in PQCProducer. Validates that REVOKED 
keys are rejected for all operations, EXPIRED
+ * keys are rejected for signing/encapsulation but allowed for 
verification/extraction, DEPRECATED keys produce a WARN
+ * but still function, and the strictKeyLifecycle flag controls enforcement.
+ */
+public class PQCKeyStatusEnforcementTest extends CamelTestSupport {
+
+    @TempDir
+    Path tempDir;
+
+    @EndpointInject("mock:signed")
+    protected MockEndpoint mockSigned;
+
+    @EndpointInject("mock:verified")
+    protected MockEndpoint mockVerified;
+
+    @EndpointInject("mock:signStrict")
+    protected MockEndpoint mockSignStrict;
+
+    @EndpointInject("mock:verifyStrict")
+    protected MockEndpoint mockVerifyStrict;
+
+    @EndpointInject("mock:signDisabled")
+    protected MockEndpoint mockSignDisabled;
+
+    @Produce("direct:sign")
+    protected ProducerTemplate signTemplate;
+
+    @Produce("direct:signAndVerify")
+    protected ProducerTemplate signAndVerifyTemplate;
+
+    @Produce("direct:signDisabledStrict")
+    protected ProducerTemplate signDisabledStrictTemplate;
+
+    private KeyLifecycleManager keyManager;
+    private KeyPair sharedKeyPair;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // Sign-only route with strict lifecycle (default: true)
+                from("direct:sign")
+                        
.to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM")
+                        .to("mock:signed");
+
+                // Sign then verify route with strict lifecycle
+                from("direct:signAndVerify")
+                        
.to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM")
+                        .to("mock:signStrict")
+                        
.to("pqc:verify?operation=verify&signatureAlgorithm=DILITHIUM")
+                        .to("mock:verifyStrict");
+
+                // Sign route with strict lifecycle disabled
+                from("direct:signDisabledStrict")
+                        
.to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM&strictKeyLifecycle=false")
+                        .to("mock:signDisabled");
+            }
+        };
+    }
+
+    @AfterEach
+    public void cleanup() throws Exception {
+        if (keyManager != null) {
+            List<KeyMetadata> keys = keyManager.listKeys();
+            for (KeyMetadata metadata : keys) {
+                try {
+                    keyManager.deleteKey(metadata.getKeyId());
+                } catch (Exception e) {
+                    // Ignore cleanup errors
+                }
+            }
+        }
+    }
+
+    // -- REVOKED key tests --
+
+    @Test
+    void testRevokedKeyRejectedForSign() throws Exception {
+        // Revoke the key
+        keyManager.revokeKey("test-key", "Compromised");
+
+        // Attempt to sign with a revoked key should fail
+        CamelExecutionException exception = 
assertThrows(CamelExecutionException.class, () -> {
+            signTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        });
+
+        assertInstanceOf(IllegalStateException.class, exception.getCause());
+        assertTrue(exception.getCause().getMessage().contains("revoked"));
+        assertTrue(exception.getCause().getMessage().contains("test-key"));
+    }
+
+    @Test
+    void testRevokedKeyRejectedForVerify() throws Exception {
+        // First sign with an active key (no KEY_ID, so no enforcement)
+        mockSigned.expectedMessageCount(1);
+        signTemplate.sendBody("Hello");
+        mockSigned.assertIsSatisfied();
+
+        byte[] signature = 
mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, 
byte[].class);
+        assertNotNull(signature);
+
+        // Now revoke the key and try to verify
+        keyManager.revokeKey("test-key", "Key compromise detected");
+
+        // Attempt to verify with a revoked key should also fail
+        CamelExecutionException exception = 
assertThrows(CamelExecutionException.class, () -> {
+            signAndVerifyTemplate.sendBodyAndHeader("Hello", 
PQCConstants.KEY_ID, "test-key");
+        });
+
+        assertInstanceOf(IllegalStateException.class, exception.getCause());
+        assertTrue(exception.getCause().getMessage().contains("revoked"));
+    }
+
+    // -- EXPIRED key tests --
+
+    @Test
+    void testExpiredKeyRejectedForSign() throws Exception {
+        // Expire the key
+        keyManager.expireKey("test-key");
+
+        // Attempt to sign with an expired key should fail
+        CamelExecutionException exception = 
assertThrows(CamelExecutionException.class, () -> {
+            signTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        });
+
+        assertInstanceOf(IllegalStateException.class, exception.getCause());
+        assertTrue(exception.getCause().getMessage().contains("expired"));
+        assertTrue(exception.getCause().getMessage().contains("test-key"));
+    }
+
+    @Test
+    void testExpiredKeyAllowedForVerify() throws Exception {
+        // First sign with the active key (no KEY_ID to skip enforcement for 
the sign step)
+        mockSignStrict.expectedMessageCount(1);
+        mockVerifyStrict.expectedMessageCount(1);
+        signAndVerifyTemplate.sendBody("Hello");
+        mockSignStrict.assertIsSatisfied();
+        mockVerifyStrict.assertIsSatisfied();
+
+        byte[] signature
+                = 
mockSignStrict.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE,
 byte[].class);
+        assertNotNull(signature);
+
+        // Now expire the key
+        keyManager.expireKey("test-key");
+
+        // Verify should still work (expired keys allowed for verification)
+        mockVerifyStrict.reset();
+        mockVerifyStrict.expectedMessageCount(1);
+        mockSignStrict.reset();
+        mockSignStrict.expectedMessageCount(1);
+
+        // Sign without enforcement (no KEY_ID), then verify with expired key 
(KEY_ID set)
+        signAndVerifyTemplate.sendBody("Hello");
+        mockVerifyStrict.assertIsSatisfied();
+
+        // Verification with an expired key should succeed (no exception)
+        assertTrue(
+                
mockVerifyStrict.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY,
 Boolean.class));
+    }
+
+    // -- DEPRECATED key tests --
+
+    @Test
+    void testDeprecatedKeyAllowedForSignWithWarning() throws Exception {
+        // Deprecate the key (simulating a rotation)
+        KeyMetadata metadata = keyManager.getKeyMetadata("test-key");
+        metadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+        keyManager.updateKeyMetadata("test-key", metadata);
+
+        // Sign should still work with a deprecated key (just logs a warning)
+        mockSigned.expectedMessageCount(1);
+        signTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockSigned.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, 
byte[].class));
+    }
+
+    @Test
+    void testDeprecatedKeyAllowedForVerifyWithWarning() throws Exception {
+        // First sign with active key
+        mockSignStrict.expectedMessageCount(1);
+        mockVerifyStrict.expectedMessageCount(1);
+        signAndVerifyTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockSignStrict.assertIsSatisfied();
+        mockVerifyStrict.assertIsSatisfied();
+
+        // Now deprecate the key
+        KeyMetadata metadata = keyManager.getKeyMetadata("test-key");
+        metadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+        keyManager.updateKeyMetadata("test-key", metadata);
+
+        // Verify with deprecated key should work
+        mockSignStrict.reset();
+        mockSignStrict.expectedMessageCount(1);
+        mockVerifyStrict.reset();
+        mockVerifyStrict.expectedMessageCount(1);
+
+        signAndVerifyTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockVerifyStrict.assertIsSatisfied();
+        assertTrue(
+                
mockVerifyStrict.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY,
 Boolean.class));
+    }
+
+    // -- strictKeyLifecycle=false tests --
+
+    @Test
+    void testStrictKeyLifecycleDisabledAllowsRevokedKey() throws Exception {
+        // Revoke the key
+        keyManager.revokeKey("test-key", "Compromised");
+
+        // With strictKeyLifecycle=false, even revoked keys should work
+        mockSignDisabled.expectedMessageCount(1);
+        signDisabledStrictTemplate.sendBodyAndHeader("Hello", 
PQCConstants.KEY_ID, "test-key");
+        mockSignDisabled.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSignDisabled.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE,
 byte[].class));
+    }
+
+    @Test
+    void testStrictKeyLifecycleDisabledAllowsExpiredKey() throws Exception {
+        // Expire the key
+        keyManager.expireKey("test-key");
+
+        // With strictKeyLifecycle=false, expired keys should work for signing
+        mockSignDisabled.expectedMessageCount(1);
+        signDisabledStrictTemplate.sendBodyAndHeader("Hello", 
PQCConstants.KEY_ID, "test-key");
+        mockSignDisabled.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSignDisabled.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE,
 byte[].class));
+    }
+
+    // -- No KEY_ID header tests --
+
+    @Test
+    void testNoKeyIdHeaderSkipsEnforcement() throws Exception {
+        // Even if key is revoked, without KEY_ID header enforcement is skipped
+        keyManager.revokeKey("test-key", "Compromised");
+
+        mockSigned.expectedMessageCount(1);
+        // Send without KEY_ID header
+        signTemplate.sendBody("Hello");
+        mockSigned.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, 
byte[].class));
+    }
+
+    // -- ACTIVE key tests --
+
+    @Test
+    void testActiveKeyAllowedForSign() throws Exception {
+        mockSigned.expectedMessageCount(1);
+        signTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockSigned.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, 
byte[].class));
+    }
+
+    @Test
+    void testActiveKeyAllowedForSignAndVerify() throws Exception {
+        mockSignStrict.expectedMessageCount(1);
+        mockVerifyStrict.expectedMessageCount(1);
+        signAndVerifyTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockSignStrict.assertIsSatisfied();
+        mockVerifyStrict.assertIsSatisfied();
+        assertTrue(
+                
mockVerifyStrict.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY,
 Boolean.class));
+    }
+
+    // -- PENDING_ROTATION key tests --
+
+    @Test
+    void testPendingRotationKeyAllowedForSign() throws Exception {
+        KeyMetadata metadata = keyManager.getKeyMetadata("test-key");
+        metadata.setStatus(KeyMetadata.KeyStatus.PENDING_ROTATION);
+        keyManager.updateKeyMetadata("test-key", metadata);
+
+        mockSigned.expectedMessageCount(1);
+        signTemplate.sendBodyAndHeader("Hello", PQCConstants.KEY_ID, 
"test-key");
+        mockSigned.assertIsSatisfied();
+
+        assertNotNull(
+                
mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, 
byte[].class));
+    }
+
+    // -- Registry bindings --
+
+    @BindToRegistry("Keypair")
+    public KeyPair setKeyPair() throws Exception {
+        ensureProviders();
+        if (sharedKeyPair == null) {
+            initKeyManager();
+        }
+        return sharedKeyPair;
+    }
+
+    @BindToRegistry("KeyLifecycleManager")
+    public KeyLifecycleManager getKeyLifecycleManager() throws Exception {
+        ensureProviders();
+        if (keyManager == null) {
+            initKeyManager();
+        }
+        return keyManager;
+    }
+
+    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());
+        }
+    }
+
+    private void initKeyManager() throws Exception {
+        keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+        // Generate a key pair using KeyPairGenerator directly and store it 
via the manager
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance(
+                PQCSignatureAlgorithms.DILITHIUM.getAlgorithm(),
+                PQCSignatureAlgorithms.DILITHIUM.getBcProvider());
+        kpGen.initialize(DilithiumParameterSpec.dilithium2, new 
SecureRandom());
+        sharedKeyPair = kpGen.generateKeyPair();
+
+        // Store the key in the lifecycle manager so we can test status 
enforcement
+        KeyMetadata metadata = new KeyMetadata("test-key", "DILITHIUM");
+        keyManager.storeKey("test-key", sharedKeyPair, metadata);
+    }
+}


Reply via email to