You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/07/08 21:03:12 UTC
(camel) branch main updated: CAMEL-23849: Camel-PQC - add automated key rotation scheduling (#24455)
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 4ef53c0e71a3 CAMEL-23849: Camel-PQC - add automated key rotation scheduling (#24455)
4ef53c0e71a3 is described below
commit 4ef53c0e71a3eb163133ab2ec15c140eac61b9b7
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Wed Jul 8 23:03:05 2026 +0200
CAMEL-23849: Camel-PQC - add automated key rotation scheduling (#24455)
* CAMEL-23849: Camel-PQC add automated key rotation scheduling
Add KeyRotationScheduler, an opt-in background service that periodically
evaluates the keys of a KeyLifecycleManager and rotates the ones that need
rotation according to a configurable policy (check interval, max age, max
usage, key filter). Rotated keys are replaced by a freshly generated key
and the previous key is deprecated. The scheduler is a Camel Service (can
be added to the CamelContext), notifies an optional KeyRotationListener on
each rotation and error, and exposes counters for monitoring.
Co-Authored-By: Claude Opus 4.8 (1M context) <no...@anthropic.com>
* CAMEL-23849: Address review - CamelContext ExecutorServiceManager and component options
Review feedback on PR #24455:
- KeyRotationScheduler implements CamelContextAware and, when a CamelContext is
present, obtains its scheduled executor from the ExecutorServiceManager (with a
standalone Executors fallback for non-Camel use); shutdown also goes through the
manager.
- Add camel.component.pqc.* options (keyRotationSchedulerEnabled,
keyRotationCheckInterval, keyRotationMaxAge, keyRotationMaxUsage) so the scheduler
can be configured and started without Java, reusing the existing keyLifecycleManager
component option. Regenerated catalog and component-dsl.
- Docs: property-driven configuration example.
Co-Authored-By: Claude Opus 4.8 (1M context) <no...@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <no...@anthropic.com>
---
.../org/apache/camel/catalog/components/pqc.json | 26 +-
.../component/pqc/PQCComponentConfigurer.java | 24 ++
.../org/apache/camel/component/pqc/pqc.json | 26 +-
.../camel-pqc/src/main/docs/pqc-key-lifecycle.adoc | 67 ++++
.../apache/camel/component/pqc/PQCComponent.java | 99 ++++++
.../pqc/lifecycle/KeyRotationScheduler.java | 360 +++++++++++++++++++++
.../pqc/lifecycle/KeyRotationSchedulerTest.java | 284 ++++++++++++++++
.../component/dsl/PqcComponentBuilderFactory.java | 73 +++++
8 files changed, 937 insertions(+), 22 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
index 067244919a3f..a57b6015bc51 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pqc.json
@@ -39,17 +39,21 @@
"keyLifecycleManager": { "index": 12, "kind": "property", "displayName": "Key Lifecycle Manager", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyLifecycleManager to [...]
"keyPair": { "index": 13, "kind": "property", "displayName": "Key Pair", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyPair to be used" },
"keyPairAlias": { "index": 14, "kind": "property", "displayName": "Key Pair Alias", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyPair alias to use in combination with KeyStore parameter" },
- "keyStore": { "index": 15, "kind": "property", "displayName": "Key Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyStore where we could get Cryptographic material" },
- "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeySto [...]
- "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" },
- "statefulKeyWarningThreshold": { "index": 19, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exh [...]
- "storeExtractedSecretKeyAsHeader": { "index": 20, "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 [...]
- "strictKeyLifecycle": { "index": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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 [...]
+ "keyRotationCheckInterval": { "index": 15, "kind": "property", "displayName": "Key Rotation Check Interval", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "3600000", "description": "Interval between key rotation checks when the scheduler is enabled." },
+ "keyRotationMaxAge": { "index": 16, "kind": "property", "displayName": "Key Rotation Max Age", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "When the scheduler is enabled, rotate keys older than this age. If not set, age is not used as a rotation signal." },
+ "keyRotationMaxUsage": { "index": 17, "kind": "property", "displayName": "Key Rotation Max Usage", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "When the scheduler is enabled, rotate keys whose recorded usage count reaches this value. 0 disables usage-based rotation." },
+ "keyRotationSchedulerEnabled": { "index": 18, "kind": "property", "displayName": "Key Rotation Scheduler Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to start an automated background key rotation scheduler for this component. Requires keyLifecycleManager to be set. The scheduler periodically rotates keys that exce [...]
+ "keyStore": { "index": 19, "kind": "property", "displayName": "Key Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyStore where we could get Cryptographic material" },
+ "keyStorePassword": { "index": 20, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeySto [...]
+ "signatureAlgorithm": { "index": 21, "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": 22, "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" },
+ "statefulKeyWarningThreshold": { "index": 23, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exh [...]
+ "storeExtractedSecretKeyAsHeader": { "index": 24, "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 [...]
+ "strictKeyLifecycle": { "index": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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" },
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 39e46f037f72..4799514a49fa 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
@@ -59,6 +59,14 @@ public class PQCComponentConfigurer extends PropertyConfigurerSupport implements
case "keyPair": getOrCreateConfiguration(target).setKeyPair(property(camelContext, java.security.KeyPair.class, value)); return true;
case "keypairalias":
case "keyPairAlias": getOrCreateConfiguration(target).setKeyPairAlias(property(camelContext, java.lang.String.class, value)); return true;
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval": target.setKeyRotationCheckInterval(property(camelContext, java.time.Duration.class, value).toMillis()); return true;
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge": target.setKeyRotationMaxAge(property(camelContext, java.time.Duration.class, value).toMillis()); return true;
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage": target.setKeyRotationMaxUsage(property(camelContext, long.class, value)); return true;
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled": target.setKeyRotationSchedulerEnabled(property(camelContext, boolean.class, value)); return true;
case "keystore":
case "keyStore": getOrCreateConfiguration(target).setKeyStore(property(camelContext, java.security.KeyStore.class, value)); return true;
case "keystorepassword":
@@ -120,6 +128,14 @@ public class PQCComponentConfigurer extends PropertyConfigurerSupport implements
case "keyPair": return java.security.KeyPair.class;
case "keypairalias":
case "keyPairAlias": return java.lang.String.class;
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval": return long.class;
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge": return long.class;
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage": return long.class;
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled": return boolean.class;
case "keystore":
case "keyStore": return java.security.KeyStore.class;
case "keystorepassword":
@@ -177,6 +193,14 @@ public class PQCComponentConfigurer extends PropertyConfigurerSupport implements
case "keyPair": return getOrCreateConfiguration(target).getKeyPair();
case "keypairalias":
case "keyPairAlias": return getOrCreateConfiguration(target).getKeyPairAlias();
+ case "keyrotationcheckinterval":
+ case "keyRotationCheckInterval": return target.getKeyRotationCheckInterval();
+ case "keyrotationmaxage":
+ case "keyRotationMaxAge": return target.getKeyRotationMaxAge();
+ case "keyrotationmaxusage":
+ case "keyRotationMaxUsage": return target.getKeyRotationMaxUsage();
+ case "keyrotationschedulerenabled":
+ case "keyRotationSchedulerEnabled": return target.isKeyRotationSchedulerEnabled();
case "keystore":
case "keyStore": return getOrCreateConfiguration(target).getKeyStore();
case "keystorepassword":
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 067244919a3f..a57b6015bc51 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
@@ -39,17 +39,21 @@
"keyLifecycleManager": { "index": 12, "kind": "property", "displayName": "Key Lifecycle Manager", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyLifecycleManager to [...]
"keyPair": { "index": 13, "kind": "property", "displayName": "Key Pair", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyPair to be used" },
"keyPairAlias": { "index": 14, "kind": "property", "displayName": "Key Pair Alias", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyPair alias to use in combination with KeyStore parameter" },
- "keyStore": { "index": 15, "kind": "property", "displayName": "Key Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyStore where we could get Cryptographic material" },
- "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeySto [...]
- "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" },
- "statefulKeyWarningThreshold": { "index": 19, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exh [...]
- "storeExtractedSecretKeyAsHeader": { "index": 20, "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 [...]
- "strictKeyLifecycle": { "index": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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 [...]
+ "keyRotationCheckInterval": { "index": 15, "kind": "property", "displayName": "Key Rotation Check Interval", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "3600000", "description": "Interval between key rotation checks when the scheduler is enabled." },
+ "keyRotationMaxAge": { "index": 16, "kind": "property", "displayName": "Key Rotation Max Age", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "When the scheduler is enabled, rotate keys older than this age. If not set, age is not used as a rotation signal." },
+ "keyRotationMaxUsage": { "index": 17, "kind": "property", "displayName": "Key Rotation Max Usage", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "When the scheduler is enabled, rotate keys whose recorded usage count reaches this value. 0 disables usage-based rotation." },
+ "keyRotationSchedulerEnabled": { "index": 18, "kind": "property", "displayName": "Key Rotation Scheduler Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to start an automated background key rotation scheduler for this component. Requires keyLifecycleManager to be set. The scheduler periodically rotates keys that exce [...]
+ "keyStore": { "index": 19, "kind": "property", "displayName": "Key Store", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.KeyStore", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "A KeyStore where we could get Cryptographic material" },
+ "keyStorePassword": { "index": 20, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeySto [...]
+ "signatureAlgorithm": { "index": 21, "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": 22, "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" },
+ "statefulKeyWarningThreshold": { "index": 23, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exh [...]
+ "storeExtractedSecretKeyAsHeader": { "index": 24, "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 [...]
+ "strictKeyLifecycle": { "index": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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" },
diff --git a/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc b/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
index 9ef0d21b04d6..adbd2af4c146 100644
--- a/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
+++ b/components/camel-pqc/src/main/docs/pqc-key-lifecycle.adoc
@@ -816,6 +816,73 @@ if (needsRotation) {
}
----
+=== Automated Rotation Scheduling
+
+Rather than calling `needsRotation()`/`rotateKey()` by hand, the `KeyRotationScheduler` can evaluate all managed keys
+periodically in the background and rotate the ones that breach a configurable policy. It is opt-in: nothing runs until
+the scheduler is started.
+
+On every tick the scheduler lists the keys of its `KeyLifecycleManager`, keeps the ones matching the key filter (by
+default only `ACTIVE` keys) and, for each, delegates the decision to `needsRotation(keyId, maxKeyAge, maxKeyUsage)`. A
+key that needs rotation is replaced by a freshly generated key while the previous key is deprecated. At least one
+rotation signal must be configured - a maximum age, a maximum usage, or a per-key `nextRotationAt`/`PENDING_ROTATION`
+status recorded in the key metadata - otherwise no key will ever be selected.
+
+._Java-only: scheduling automated key rotation_
+[source,java]
+----
+KeyLifecycleManager keyManager = new FileBasedKeyLifecycleManager("/secure/keys");
+
+KeyRotationScheduler scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofHours(6)) // how often to evaluate the keys (default: 1 hour)
+ .setMaxKeyAge(Duration.ofDays(90)) // rotate keys older than 90 days
+ .setMaxKeyUsage(1_000_000) // and/or rotate keys used more than 1,000,000 times
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(String oldKeyId, String newKeyId, KeyMetadata previous, KeyPair newKeyPair) {
+ logger.info("Rotated {} -> {}", oldKeyId, newKeyId);
+ }
+ });
+
+scheduler.start(); // begin the background schedule
+// ... application runs ...
+scheduler.stop(); // stop the background schedule
+----
+
+Because `KeyRotationScheduler` is a Camel `Service`, it can instead be added to the `CamelContext` so that it is started
+and stopped together with the context:
+
+._Java-only: managing the scheduler with the CamelContext_
+[source,java]
+----
+camelContext.addService(scheduler); // started and stopped with the context
+----
+
+Alternatively, the scheduler can be started and configured entirely through component options - no Java code - by setting
+`camel.component.pqc.*` properties (for example in `application.properties`). Bind a `KeyLifecycleManager` in the
+registry and enable the scheduler:
+
+._application.properties: property-driven key rotation_
+[source,properties]
+----
+# a KeyLifecycleManager bean must be available in the registry, e.g. #myKeyManager
+camel.component.pqc.key-lifecycle-manager = #myKeyManager
+camel.component.pqc.key-rotation-scheduler-enabled = true
+camel.component.pqc.key-rotation-check-interval = 6h
+camel.component.pqc.key-rotation-max-age = 90d
+camel.component.pqc.key-rotation-max-usage = 1000000
+----
+
+When `keyRotationSchedulerEnabled` is `true`, the `pqc` component builds a `KeyRotationScheduler` around the configured
+`keyLifecycleManager` and starts and stops it with the component. The same options are available in the YAML DSL and as
+`PQCComponent` setters, which makes the scheduler usable from Spring Boot and Quarkus without any Java code.
+
+The default id of a replacement key is the previous key id with a rotation marker and timestamp appended; supply a
+`setKeyIdStrategy(Function<KeyMetadata, String>)` to control it, and a `setKeyFilter(Predicate<KeyMetadata>)` to change
+which keys are considered. The scheduler also exposes counters (`getChecksPerformed()`, `getRotationsPerformed()`,
+`getRotationFailures()`, `getLastCheckAt()`) for monitoring. A single pass can be triggered on demand with
+`checkAndRotate()`.
+
== Key Export and Import
Keys can be exported and imported in multiple formats:
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
index 6734fc1eb1d3..a8e9942bd882 100644
--- a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCComponent.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.pqc;
+import java.time.Duration;
import java.util.Map;
import org.apache.camel.CamelContext;
@@ -23,6 +24,8 @@ import org.apache.camel.Endpoint;
import org.apache.camel.component.pqc.crypto.*;
import org.apache.camel.component.pqc.crypto.hybrid.*;
import org.apache.camel.component.pqc.crypto.kem.*;
+import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyRotationScheduler;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.HealthCheckComponent;
@@ -36,6 +39,24 @@ public class PQCComponent extends HealthCheckComponent {
@Metadata
private PQCConfiguration configuration = new PQCConfiguration();
+ @Metadata(label = "advanced", defaultValue = "false",
+ description = "Whether to start an automated background key rotation scheduler for this component."
+ + " Requires keyLifecycleManager to be set. The scheduler periodically rotates keys that"
+ + " exceed the configured age and/or usage policy.")
+ private boolean keyRotationSchedulerEnabled;
+ @Metadata(label = "advanced", defaultValue = "3600000", javaType = "java.time.Duration",
+ description = "Interval between key rotation checks when the scheduler is enabled.")
+ private long keyRotationCheckInterval = 3600000L;
+ @Metadata(label = "advanced", javaType = "java.time.Duration",
+ description = "When the scheduler is enabled, rotate keys older than this age. If not set, age is not used"
+ + " as a rotation signal.")
+ private long keyRotationMaxAge;
+ @Metadata(label = "advanced", defaultValue = "0",
+ description = "When the scheduler is enabled, rotate keys whose recorded usage count reaches this value. 0"
+ + " disables usage-based rotation.")
+ private long keyRotationMaxUsage;
+
+ private KeyRotationScheduler keyRotationScheduler;
public PQCComponent() {
this(null);
@@ -260,6 +281,31 @@ public class PQCComponent extends HealthCheckComponent {
}
}
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ if (keyRotationSchedulerEnabled) {
+ KeyLifecycleManager manager = configuration != null ? configuration.getKeyLifecycleManager() : null;
+ ObjectHelper.notNull(manager,
+ "keyLifecycleManager (required when keyRotationSchedulerEnabled=true)");
+ keyRotationScheduler = new KeyRotationScheduler(manager)
+ .setCheckInterval(Duration.ofMillis(keyRotationCheckInterval))
+ .setMaxKeyAge(keyRotationMaxAge > 0 ? Duration.ofMillis(keyRotationMaxAge) : null)
+ .setMaxKeyUsage(keyRotationMaxUsage);
+ keyRotationScheduler.setCamelContext(getCamelContext());
+ keyRotationScheduler.start();
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (keyRotationScheduler != null) {
+ keyRotationScheduler.stop();
+ keyRotationScheduler = null;
+ }
+ super.doStop();
+ }
+
public PQCConfiguration getConfiguration() {
return configuration;
}
@@ -270,4 +316,57 @@ public class PQCComponent extends HealthCheckComponent {
public void setConfiguration(PQCConfiguration configuration) {
this.configuration = configuration;
}
+
+ public boolean isKeyRotationSchedulerEnabled() {
+ return keyRotationSchedulerEnabled;
+ }
+
+ /**
+ * Whether to start an automated background key rotation scheduler for this component. Requires keyLifecycleManager
+ * to be set. The scheduler periodically rotates keys that exceed the configured age and/or usage policy.
+ */
+ public void setKeyRotationSchedulerEnabled(boolean keyRotationSchedulerEnabled) {
+ this.keyRotationSchedulerEnabled = keyRotationSchedulerEnabled;
+ }
+
+ public long getKeyRotationCheckInterval() {
+ return keyRotationCheckInterval;
+ }
+
+ /**
+ * Interval between key rotation checks when the scheduler is enabled.
+ */
+ public void setKeyRotationCheckInterval(long keyRotationCheckInterval) {
+ this.keyRotationCheckInterval = keyRotationCheckInterval;
+ }
+
+ public long getKeyRotationMaxAge() {
+ return keyRotationMaxAge;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys older than this age. If not set, age is not used as a rotation signal.
+ */
+ public void setKeyRotationMaxAge(long keyRotationMaxAge) {
+ this.keyRotationMaxAge = keyRotationMaxAge;
+ }
+
+ public long getKeyRotationMaxUsage() {
+ return keyRotationMaxUsage;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys whose recorded usage count reaches this value. 0 disables usage-based
+ * rotation.
+ */
+ public void setKeyRotationMaxUsage(long keyRotationMaxUsage) {
+ this.keyRotationMaxUsage = keyRotationMaxUsage;
+ }
+
+ /**
+ * The automated key rotation scheduler created when keyRotationSchedulerEnabled is true, or null.
+ */
+ public KeyRotationScheduler getKeyRotationScheduler() {
+ return keyRotationScheduler;
+ }
}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java
new file mode 100644
index 000000000000..d936576a43b3
--- /dev/null
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyRotationScheduler.java
@@ -0,0 +1,360 @@
+/*
+ * 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.lifecycle;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An opt-in background scheduler that periodically evaluates the keys managed by a {@link KeyLifecycleManager} and
+ * rotates the ones that need rotation according to a configurable policy (maximum age and/or maximum usage). A rotated
+ * key is replaced by a freshly generated key while the previous key is deprecated - this is the behaviour of
+ * {@link KeyLifecycleManager#rotateKey(String, String, String)}.
+ * <p/>
+ * The scheduler is inactive until {@link #start()} is called. It implements the Camel {@code Service} contract, so it
+ * can be added to a {@code CamelContext} to participate in the context lifecycle:
+ *
+ * <pre>{@code
+ * KeyRotationScheduler scheduler = new KeyRotationScheduler(keyManager)
+ * .setCheckInterval(Duration.ofHours(6))
+ * .setMaxKeyAge(Duration.ofDays(90))
+ * .setMaxKeyUsage(1_000_000);
+ * camelContext.addService(scheduler); // started and stopped with the context
+ * }</pre>
+ *
+ * On every tick the scheduler lists the managed keys, keeps only those matching the configured {@link #setKeyFilter key
+ * filter} (by default only {@link KeyMetadata.KeyStatus#ACTIVE ACTIVE} keys) and, for each of them, delegates the
+ * decision to {@link KeyLifecycleManager#needsRotation(String, Duration, long)}. When a key needs rotation, the id of
+ * its replacement is produced by the configured {@link #setKeyIdStrategy key id strategy} (by default the previous key
+ * id with a rotation marker and timestamp appended).
+ * <p/>
+ * At least one rotation signal should be configured (a {@link #setMaxKeyAge maximum age}, a {@link #setMaxKeyUsage
+ * maximum usage}, or a per-key {@code nextRotationAt}/{@code PENDING_ROTATION} status recorded in the key metadata);
+ * otherwise no key will ever be selected for rotation.
+ * <p/>
+ * A single check can also be triggered on demand with {@link #checkAndRotate()}, which is what the scheduled task
+ * calls. The method is thread-safe: concurrent invocations (for example a manual call overlapping a scheduled tick) are
+ * serialized.
+ */
+public class KeyRotationScheduler extends ServiceSupport implements CamelContextAware {
+
+ private static final Logger LOG = LoggerFactory.getLogger(KeyRotationScheduler.class);
+
+ private final KeyLifecycleManager keyManager;
+
+ private Duration checkInterval = Duration.ofHours(1);
+ private Duration maxKeyAge;
+ private long maxKeyUsage;
+ private Predicate<KeyMetadata> keyFilter = metadata -> metadata.getStatus() == KeyMetadata.KeyStatus.ACTIVE;
+ private Function<KeyMetadata, String> keyIdStrategy = KeyRotationScheduler::defaultRotatedKeyId;
+ private KeyRotationListener listener;
+
+ private final AtomicLong checksPerformed = new AtomicLong();
+ private final AtomicLong rotationsPerformed = new AtomicLong();
+ private final AtomicLong rotationFailures = new AtomicLong();
+ private volatile Instant lastCheckAt;
+
+ private ScheduledExecutorService executorService;
+
+ private CamelContext camelContext;
+
+ public KeyRotationScheduler(KeyLifecycleManager keyManager) {
+ this.keyManager = ObjectHelper.notNull(keyManager, "keyManager");
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ /**
+ * Performs a single rotation pass over the managed keys and returns how many keys were rotated. This is invoked by
+ * the scheduled task and can also be called manually. Never throws for an individual key failure: such failures are
+ * counted, logged and reported to the {@link KeyRotationListener listener}, then the pass continues with the next
+ * key.
+ *
+ * @return the number of keys rotated during this pass
+ */
+ public synchronized int checkAndRotate() {
+ checksPerformed.incrementAndGet();
+ lastCheckAt = Instant.now();
+
+ List<KeyMetadata> keys;
+ try {
+ keys = keyManager.listKeys();
+ } catch (Exception e) {
+ LOG.warn("Failed to list keys during rotation check", e);
+ notifyError(null, e);
+ return 0;
+ }
+
+ int rotated = 0;
+ for (KeyMetadata metadata : keys) {
+ if (metadata == null || !keyFilter.test(metadata)) {
+ continue;
+ }
+ String keyId = metadata.getKeyId();
+ try {
+ if (keyManager.needsRotation(keyId, maxKeyAge, maxKeyUsage)) {
+ String newKeyId = keyIdStrategy.apply(metadata);
+ LOG.info("Rotating PQC key '{}' -> '{}' (algorithm={})", keyId, newKeyId, metadata.getAlgorithm());
+ KeyPair newKeyPair = keyManager.rotateKey(keyId, newKeyId, metadata.getAlgorithm());
+ rotationsPerformed.incrementAndGet();
+ rotated++;
+ notifyRotated(keyId, newKeyId, metadata, newKeyPair);
+ }
+ } catch (Exception e) {
+ rotationFailures.incrementAndGet();
+ LOG.warn("Failed to rotate PQC key '{}'", keyId, e);
+ notifyError(keyId, e);
+ }
+ }
+ return rotated;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ ObjectHelper.notNull(checkInterval, "checkInterval");
+ if (checkInterval.isNegative() || checkInterval.isZero()) {
+ throw new IllegalArgumentException("checkInterval must be a positive duration");
+ }
+ long millis = checkInterval.toMillis();
+ // Prefer Camel's ExecutorServiceManager (centralized lifecycle, profiling, naming) when running inside a
+ // CamelContext; fall back to a self-managed daemon executor for standalone use.
+ if (camelContext != null) {
+ executorService = camelContext.getExecutorServiceManager()
+ .newSingleThreadScheduledExecutor(this, "PQCKeyRotationScheduler");
+ } else {
+ executorService = Executors.newSingleThreadScheduledExecutor(new RotationThreadFactory());
+ }
+ executorService.scheduleWithFixedDelay(this::runScheduledCheck, millis, millis, TimeUnit.MILLISECONDS);
+ LOG.info("Started PQC KeyRotationScheduler (interval={}, maxAge={}, maxUsage={})",
+ checkInterval, maxKeyAge, maxKeyUsage);
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (executorService != null) {
+ if (camelContext != null) {
+ camelContext.getExecutorServiceManager().shutdownNow(executorService);
+ } else {
+ executorService.shutdownNow();
+ }
+ executorService = null;
+ }
+ LOG.info("Stopped PQC KeyRotationScheduler (checks={}, rotations={}, failures={})",
+ checksPerformed.get(), rotationsPerformed.get(), rotationFailures.get());
+ }
+
+ private void runScheduledCheck() {
+ try {
+ checkAndRotate();
+ } catch (Throwable t) {
+ // Swallow everything so a single failing pass does not cancel the recurring schedule
+ LOG.warn("Unexpected error during scheduled PQC key rotation check", t);
+ }
+ }
+
+ private void notifyRotated(String oldKeyId, String newKeyId, KeyMetadata previousMetadata, KeyPair newKeyPair) {
+ if (listener != null) {
+ try {
+ listener.onRotated(oldKeyId, newKeyId, previousMetadata, newKeyPair);
+ } catch (Exception e) {
+ LOG.warn("KeyRotationListener.onRotated threw an exception", e);
+ }
+ }
+ }
+
+ private void notifyError(String keyId, Exception error) {
+ if (listener != null) {
+ try {
+ listener.onError(keyId, error);
+ } catch (Exception e) {
+ LOG.warn("KeyRotationListener.onError threw an exception", e);
+ }
+ }
+ }
+
+ private static String defaultRotatedKeyId(KeyMetadata metadata) {
+ return metadata.getKeyId() + "-rotated-" + Instant.now().toEpochMilli();
+ }
+
+ public KeyLifecycleManager getKeyManager() {
+ return keyManager;
+ }
+
+ public Duration getCheckInterval() {
+ return checkInterval;
+ }
+
+ /**
+ * How often the managed keys are evaluated for rotation. Must be a positive duration. Defaults to one hour.
+ */
+ public KeyRotationScheduler setCheckInterval(Duration checkInterval) {
+ this.checkInterval = ObjectHelper.notNull(checkInterval, "checkInterval");
+ return this;
+ }
+
+ public Duration getMaxKeyAge() {
+ return maxKeyAge;
+ }
+
+ /**
+ * Rotate a key once it is older than this age. When {@code null} (the default) age is not used as a rotation
+ * signal.
+ */
+ public KeyRotationScheduler setMaxKeyAge(Duration maxKeyAge) {
+ this.maxKeyAge = maxKeyAge;
+ return this;
+ }
+
+ public long getMaxKeyUsage() {
+ return maxKeyUsage;
+ }
+
+ /**
+ * Rotate a key once its recorded usage count reaches this value. When {@code 0} or negative (the default) usage is
+ * not used as a rotation signal.
+ */
+ public KeyRotationScheduler setMaxKeyUsage(long maxKeyUsage) {
+ this.maxKeyUsage = maxKeyUsage;
+ return this;
+ }
+
+ public Predicate<KeyMetadata> getKeyFilter() {
+ return keyFilter;
+ }
+
+ /**
+ * Selects which keys are considered for rotation. Defaults to only {@link KeyMetadata.KeyStatus#ACTIVE ACTIVE}
+ * keys.
+ */
+ public KeyRotationScheduler setKeyFilter(Predicate<KeyMetadata> keyFilter) {
+ this.keyFilter = ObjectHelper.notNull(keyFilter, "keyFilter");
+ return this;
+ }
+
+ public Function<KeyMetadata, String> getKeyIdStrategy() {
+ return keyIdStrategy;
+ }
+
+ /**
+ * Produces the id of the replacement key from the metadata of the key being rotated. Defaults to the previous key
+ * id with a rotation marker and timestamp appended.
+ */
+ public KeyRotationScheduler setKeyIdStrategy(Function<KeyMetadata, String> keyIdStrategy) {
+ this.keyIdStrategy = ObjectHelper.notNull(keyIdStrategy, "keyIdStrategy");
+ return this;
+ }
+
+ public KeyRotationListener getListener() {
+ return listener;
+ }
+
+ /**
+ * An optional listener notified on every rotation and on every rotation error.
+ */
+ public KeyRotationScheduler setListener(KeyRotationListener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ /**
+ * The number of rotation passes performed so far (scheduled and manual).
+ */
+ public long getChecksPerformed() {
+ return checksPerformed.get();
+ }
+
+ /**
+ * The number of keys successfully rotated so far.
+ */
+ public long getRotationsPerformed() {
+ return rotationsPerformed.get();
+ }
+
+ /**
+ * The number of key rotation attempts that failed so far.
+ */
+ public long getRotationFailures() {
+ return rotationFailures.get();
+ }
+
+ /**
+ * The instant of the last rotation pass, or {@code null} if no pass has run yet.
+ */
+ public Instant getLastCheckAt() {
+ return lastCheckAt;
+ }
+
+ /**
+ * Listener callbacks invoked by a {@link KeyRotationScheduler}. All methods have an empty default implementation,
+ * so an implementation only needs to override the callbacks it cares about.
+ */
+ public interface KeyRotationListener {
+
+ /**
+ * Invoked after a key has been rotated.
+ *
+ * @param oldKeyId the id of the key that was rotated (and is now deprecated)
+ * @param newKeyId the id of the freshly generated replacement key
+ * @param previousMetadata the metadata of the rotated key at the time of rotation
+ * @param newKeyPair the freshly generated key pair
+ */
+ default void onRotated(String oldKeyId, String newKeyId, KeyMetadata previousMetadata, KeyPair newKeyPair) {
+ }
+
+ /**
+ * Invoked when a rotation attempt fails.
+ *
+ * @param keyId the id of the key that could not be rotated, or {@code null} when listing the keys failed
+ * @param error the failure
+ */
+ default void onError(String keyId, Exception error) {
+ }
+ }
+
+ private static final class RotationThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, "PQCKeyRotationScheduler");
+ thread.setDaemon(true);
+ return thread;
+ }
+ }
+}
diff --git a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java
new file mode 100644
index 000000000000..35fb53d7d4e2
--- /dev/null
+++ b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/lifecycle/KeyRotationSchedulerTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.lifecycle;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.Security;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.component.pqc.PQCComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+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.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class KeyRotationSchedulerTest {
+
+ @TempDir
+ Path tempDir;
+
+ private FileBasedKeyLifecycleManager keyManager;
+ private KeyRotationScheduler scheduler;
+
+ @BeforeAll
+ static void startup() {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastlePQCProvider());
+ }
+ }
+
+ @AfterEach
+ void cleanup() throws Exception {
+ if (scheduler != null && scheduler.isStarted()) {
+ scheduler.stop();
+ }
+ if (keyManager != null) {
+ for (KeyMetadata metadata : keyManager.listKeys()) {
+ keyManager.deleteKey(metadata.getKeyId());
+ }
+ }
+ }
+
+ private FileBasedKeyLifecycleManager newManager() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+ return keyManager;
+ }
+
+ /** Generates a Dilithium key and drives its recorded usage count to the given value. */
+ private void generateKeyWithUsage(String keyId, long usage) throws Exception {
+ keyManager.generateKeyPair("DILITHIUM", keyId, DilithiumParameterSpec.dilithium2);
+ KeyMetadata metadata = keyManager.getKeyMetadata(keyId);
+ for (long i = 0; i < usage; i++) {
+ metadata.updateLastUsed();
+ }
+ keyManager.updateKeyMetadata(keyId, metadata);
+ }
+
+ @Test
+ void testRotatesKeyThatExceedsUsagePolicy() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(50);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(1, rotated);
+ assertEquals(1, scheduler.getChecksPerformed());
+ assertEquals(1, scheduler.getRotationsPerformed());
+ assertEquals(0, scheduler.getRotationFailures());
+ assertNotNull(scheduler.getLastCheckAt());
+
+ // The old key is deprecated and a single fresh ACTIVE replacement exists
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, keyManager.getKeyMetadata("app-key").getStatus());
+ List<KeyMetadata> active = keyManager.listKeys().stream()
+ .filter(m -> m.getStatus() == KeyMetadata.KeyStatus.ACTIVE)
+ .toList();
+ assertEquals(1, active.size());
+ assertNotEquals("app-key", active.get(0).getKeyId());
+ assertEquals("DILITHIUM", active.get(0).getAlgorithm());
+ }
+
+ @Test
+ void testDoesNotRotateWhenPolicyNotBreached() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 10);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(1000);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(0, rotated);
+ assertEquals(0, scheduler.getRotationsPerformed());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, keyManager.getKeyMetadata("app-key").getStatus());
+ assertEquals(1, keyManager.listKeys().size());
+ }
+
+ @Test
+ void testKeyFilterExcludesNonActiveKeys() throws Exception {
+ newManager();
+ // Would breach the usage policy, but the key is not ACTIVE so the default filter skips it
+ generateKeyWithUsage("app-key", 100);
+ KeyMetadata metadata = keyManager.getKeyMetadata("app-key");
+ metadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+ keyManager.updateKeyMetadata("app-key", metadata);
+
+ scheduler = new KeyRotationScheduler(keyManager).setMaxKeyUsage(50);
+ int rotated = scheduler.checkAndRotate();
+
+ assertEquals(0, rotated);
+ assertEquals(0, scheduler.getRotationsPerformed());
+ assertEquals(1, keyManager.listKeys().size());
+ }
+
+ @Test
+ void testCustomKeyIdStrategy() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setMaxKeyUsage(50)
+ .setKeyIdStrategy(m -> m.getKeyId() + "-v2");
+ scheduler.checkAndRotate();
+
+ KeyMetadata rotated = keyManager.getKeyMetadata("app-key-v2");
+ assertNotNull(rotated);
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, rotated.getStatus());
+ }
+
+ @Test
+ void testListenerInvokedOnRotation() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ AtomicReference<String> oldId = new AtomicReference<>();
+ AtomicReference<String> newId = new AtomicReference<>();
+ AtomicReference<KeyPair> newPair = new AtomicReference<>();
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setMaxKeyUsage(50)
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata previousMetadata, KeyPair newKeyPair) {
+ oldId.set(oldKeyId);
+ newId.set(newKeyId);
+ newPair.set(newKeyPair);
+ }
+ });
+ scheduler.checkAndRotate();
+
+ assertEquals("app-key", oldId.get());
+ assertNotNull(newId.get());
+ assertNotEquals("app-key", newId.get());
+ assertNotNull(newPair.get());
+ assertNotNull(newPair.get().getPrivate());
+ }
+
+ @Test
+ void testScheduledRotation() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ CountDownLatch rotated = new CountDownLatch(1);
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofMillis(50))
+ .setMaxKeyUsage(50)
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata previousMetadata, KeyPair newKeyPair) {
+ rotated.countDown();
+ }
+ });
+
+ scheduler.start();
+ assertTrue(rotated.await(10, TimeUnit.SECONDS), "Scheduled rotation did not run in time");
+ scheduler.stop();
+
+ assertFalse(scheduler.isStarted());
+ assertTrue(scheduler.getRotationsPerformed() >= 1);
+ assertTrue(scheduler.getChecksPerformed() >= 1);
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, keyManager.getKeyMetadata("app-key").getStatus());
+ }
+
+ @Test
+ void testStartRejectsNonPositiveInterval() throws Exception {
+ newManager();
+ scheduler = new KeyRotationScheduler(keyManager).setCheckInterval(Duration.ZERO);
+ assertThrows(RuntimeException.class, () -> scheduler.start());
+ }
+
+ @Test
+ void testScheduledRotationWithCamelContext() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ CountDownLatch rotated = new CountDownLatch(1);
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+ // With a CamelContext set, the scheduler obtains its executor from the ExecutorServiceManager
+ scheduler = new KeyRotationScheduler(keyManager)
+ .setCheckInterval(Duration.ofMillis(50))
+ .setMaxKeyUsage(50)
+ .setListener(new KeyRotationScheduler.KeyRotationListener() {
+ @Override
+ public void onRotated(
+ String oldKeyId, String newKeyId, KeyMetadata previousMetadata, KeyPair newKeyPair) {
+ rotated.countDown();
+ }
+ });
+ scheduler.setCamelContext(context);
+ scheduler.start();
+
+ assertTrue(rotated.await(10, TimeUnit.SECONDS), "Scheduled rotation did not run in time");
+ assertSame(context, scheduler.getCamelContext());
+ scheduler.stop();
+ }
+
+ assertTrue(scheduler.getRotationsPerformed() >= 1);
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, keyManager.getKeyMetadata("app-key").getStatus());
+ }
+
+ @Test
+ void testComponentOptionsStartScheduler() throws Exception {
+ newManager();
+ generateKeyWithUsage("app-key", 100);
+
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+ PQCComponent component = new PQCComponent();
+ component.setCamelContext(context);
+ component.getConfiguration().setKeyLifecycleManager(keyManager);
+ component.setKeyRotationSchedulerEnabled(true);
+ component.setKeyRotationCheckInterval(3_600_000L); // 1h - won't auto-fire during the test
+ component.setKeyRotationMaxUsage(50);
+ component.start();
+
+ KeyRotationScheduler s = component.getKeyRotationScheduler();
+ assertNotNull(s);
+ assertSame(context, s.getCamelContext());
+ assertTrue(s.isStarted());
+
+ // Deterministic manual trigger: the component wired the manager and policy correctly
+ assertEquals(1, s.checkAndRotate());
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, keyManager.getKeyMetadata("app-key").getStatus());
+
+ component.stop();
+ assertFalse(s.isStarted());
+ assertNull(component.getKeyRotationScheduler());
+ }
+ }
+}
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
index 6d9dbb4f6003..be73ea8fdd64 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java
@@ -307,6 +307,75 @@ public interface PqcComponentBuilderFactory {
return this;
}
+
+ /**
+ * Interval between key rotation checks when the scheduler is enabled.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 3600000
+ * Group: advanced
+ *
+ * @param keyRotationCheckInterval the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationCheckInterval(long keyRotationCheckInterval) {
+ doSetProperty("keyRotationCheckInterval", keyRotationCheckInterval);
+ return this;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys older than this age. If
+ * not set, age is not used as a rotation signal.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Group: advanced
+ *
+ * @param keyRotationMaxAge the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationMaxAge(long keyRotationMaxAge) {
+ doSetProperty("keyRotationMaxAge", keyRotationMaxAge);
+ return this;
+ }
+
+ /**
+ * When the scheduler is enabled, rotate keys whose recorded usage count
+ * reaches this value. 0 disables usage-based rotation.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Group: advanced
+ *
+ * @param keyRotationMaxUsage the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationMaxUsage(long keyRotationMaxUsage) {
+ doSetProperty("keyRotationMaxUsage", keyRotationMaxUsage);
+ return this;
+ }
+
+
+ /**
+ * Whether to start an automated background key rotation scheduler for
+ * this component. Requires keyLifecycleManager to be set. The scheduler
+ * periodically rotates keys that exceed the configured age and/or usage
+ * policy.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: advanced
+ *
+ * @param keyRotationSchedulerEnabled the value to set
+ * @return the dsl builder
+ */
+ default PqcComponentBuilder keyRotationSchedulerEnabled(boolean keyRotationSchedulerEnabled) {
+ doSetProperty("keyRotationSchedulerEnabled", keyRotationSchedulerEnabled);
+ return this;
+ }
+
/**
* A KeyStore where we could get Cryptographic material.
*
@@ -538,6 +607,10 @@ public interface PqcComponentBuilderFactory {
case "keyLifecycleManager": getOrCreateConfiguration((PQCComponent) component).setKeyLifecycleManager((org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager) value); return true;
case "keyPair": getOrCreateConfiguration((PQCComponent) component).setKeyPair((java.security.KeyPair) value); return true;
case "keyPairAlias": getOrCreateConfiguration((PQCComponent) component).setKeyPairAlias((java.lang.String) value); return true;
+ case "keyRotationCheckInterval": ((PQCComponent) component).setKeyRotationCheckInterval((long) value); return true;
+ case "keyRotationMaxAge": ((PQCComponent) component).setKeyRotationMaxAge((long) value); return true;
+ case "keyRotationMaxUsage": ((PQCComponent) component).setKeyRotationMaxUsage((long) value); return true;
+ case "keyRotationSchedulerEnabled": ((PQCComponent) component).setKeyRotationSchedulerEnabled((boolean) value); return true;
case "keyStore": getOrCreateConfiguration((PQCComponent) component).setKeyStore((java.security.KeyStore) value); return true;
case "keyStorePassword": getOrCreateConfiguration((PQCComponent) component).setKeyStorePassword((java.lang.String) value); return true;
case "signatureAlgorithm": getOrCreateConfiguration((PQCComponent) component).setSignatureAlgorithm((java.lang.String) value); return true;