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 2025/10/09 07:33:39 UTC
(camel) branch key-lifecycle-management updated (3f8e58b32d8b -> b500c22cd2f5)
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a change to branch key-lifecycle-management
in repository https://gitbox.apache.org/repos/asf/camel.git
discard 3f8e58b32d8b CAMEL-22512 - Camel-PQC: Add Key Lifecycle Management
new b500c22cd2f5 CAMEL-22512 - Camel-PQC: Add Key Lifecycle Management
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (3f8e58b32d8b)
\
N -- N -- N refs/heads/key-lifecycle-management (b500c22cd2f5)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/camel/component/pqc/PQCEndToEndIntegrationTest.java | 3 ---
1 file changed, 3 deletions(-)
(camel) 01/01: CAMEL-22512 - Camel-PQC: Add Key Lifecycle Management
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch key-lifecycle-management
in repository https://gitbox.apache.org/repos/asf/camel.git
commit b500c22cd2f5706b12dc532ca11efac749f2884b
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Wed Oct 8 14:20:38 2025 +0200
CAMEL-22512 - Camel-PQC: Add Key Lifecycle Management
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
.../org/apache/camel/catalog/components/pqc.json | 17 +-
.../org/apache/camel/component/pqc/pqc.json | 17 +-
.../camel-pqc/src/main/docs/pqc-component.adoc | 577 ++++++++++++++++++++-
.../apache/camel/component/pqc/PQCConstants.java | 34 ++
.../apache/camel/component/pqc/PQCOperations.java | 13 +-
.../lifecycle/FileBasedKeyLifecycleManager.java | 405 +++++++++++++++
.../pqc/lifecycle/KeyFormatConverter.java | 175 +++++++
.../pqc/lifecycle/KeyLifecycleManager.java | 109 ++++
.../camel/component/pqc/lifecycle/KeyMetadata.java | 132 +++++
.../component/pqc/PQCEndToEndIntegrationTest.java | 415 +++++++++++++++
.../pqc/PQCKeyLifecycleIntegrationTest.java | 320 ++++++++++++
.../camel/component/pqc/PQCKeyLifecycleTest.java | 369 +++++++++++++
.../endpoint/dsl/PQCEndpointBuilderFactory.java | 134 +++++
13 files changed, 2700 insertions(+), 17 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 15be11f36de6..c9b8c6b29537 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
@@ -26,7 +26,7 @@
"componentProperties": {
"configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.pqc.PQCConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" },
"lazyStartProducer": { "index": 1, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...]
- "operation": { "index": 2, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration [...]
+ "operation": { "index": 2, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation", "getRemainingSignatures", "getKeyState", "deleteKeyState", "generateKeyPair", "exportKey", "importKey", "rotateKey", "getKeyMetadata", "listKeys", "expir [...]
"autowiredEnabled": { "index": 3, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
"keyEncapsulationAlgorithm": { "index": 4, "kind": "property", "displayName": "Key Encapsulation Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLKEM", "BIKE", "HQC", "CMCE", "SABER", "FRODO", "NTRU", "NTRULPRime", "SNTRUPrime", "KYBER" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configur [...]
"keyGenerator": { "index": 5, "kind": "property", "displayName": "Key Generator", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "javax.crypto.KeyGenerator", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Key Generator to be used in encapsulation and extraction" },
@@ -46,11 +46,22 @@
"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" },
"CamelPQCSignature": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The signature of a body", "constantName": "org.apache.camel.component.pqc.PQCConstants#SIGNATURE" },
"CamelPQCVerification": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The result of verification of a Body signature", "constantName": "org.apache.camel.component.pqc.PQCConstants#VERIFY" },
- "CamelPQCSecretKey": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The extracted key in case of extractSecretKeyFromEncapsulation operation and storeExtractedSecretKeyAsHeader option enabled", "constantName": "org.apache.camel.component.pqc.PQCConstants#SECRET_KEY" }
+ "CamelPQCSecretKey": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The extracted key in case of extractSecretKeyFromEncapsulation operation and storeExtractedSecretKeyAsHeader option enabled", "constantName": "org.apache.camel.component.pqc.PQCConstants#SECRET_KEY" },
+ "CamelPQCRemainingSignatures": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The remaining signatures for a stateful key", "constantName": "org.apache.camel.component.pqc.PQCConstants#REMAINING_SIGNATURES" },
+ "CamelPQCKeyState": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.camel.component.pqc.stateful.StatefulKeyState", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key state for a stateful key", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_STATE" },
+ "CamelPQCKeyId": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key ID for stateful key operations", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_ID" },
+ "CamelPQCKeyPair": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The generated key pair", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_PAIR" },
+ "CamelPQCKeyFormat": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key format for import\/export operations", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_FORMAT" },
+ "CamelPQCExportedKey": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The exported key data", "constantName": "org.apache.camel.component.pqc.PQCConstants#EXPORTED_KEY" },
+ "CamelPQCKeyMetadata": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.camel.component.pqc.lifecycle.KeyMetadata", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key metadata", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_METADATA" },
+ "CamelPQCKeyList": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "List of key metadata", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_LIST" },
+ "CamelPQCAlgorithm": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The algorithm for key generation", "constantName": "org.apache.camel.component.pqc.PQCConstants#ALGORITHM" },
+ "CamelPQCIncludePrivate": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Include private key in export", "constantName": "org.apache.camel.component.pqc.PQCConstants#INCLUDE_PRIVATE" },
+ "CamelPQCRevocationReason": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Revocation reason", "constantName": "org.apache.camel.component.pqc.PQCConstants#REVOCATION_REASON" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Logical name" },
- "operation": { "index": 1, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguratio [...]
+ "operation": { "index": 1, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation", "getRemainingSignatures", "getKeyState", "deleteKeyState", "generateKeyPair", "exportKey", "importKey", "rotateKey", "getKeyMetadata", "listKeys", "expi [...]
"lazyStartProducer": { "index": 2, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
"keyEncapsulationAlgorithm": { "index": 3, "kind": "parameter", "displayName": "Key Encapsulation Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLKEM", "BIKE", "HQC", "CMCE", "SABER", "FRODO", "NTRU", "NTRULPRime", "SNTRUPrime", "KYBER" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configu [...]
"keyGenerator": { "index": 4, "kind": "parameter", "displayName": "Key Generator", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "javax.crypto.KeyGenerator", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Key Generator to be used in encapsulation and extraction" },
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 15be11f36de6..c9b8c6b29537 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
@@ -26,7 +26,7 @@
"componentProperties": {
"configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.pqc.PQCConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" },
"lazyStartProducer": { "index": 1, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...]
- "operation": { "index": 2, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration [...]
+ "operation": { "index": 2, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation", "getRemainingSignatures", "getKeyState", "deleteKeyState", "generateKeyPair", "exportKey", "importKey", "rotateKey", "getKeyMetadata", "listKeys", "expir [...]
"autowiredEnabled": { "index": 3, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
"keyEncapsulationAlgorithm": { "index": 4, "kind": "property", "displayName": "Key Encapsulation Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLKEM", "BIKE", "HQC", "CMCE", "SABER", "FRODO", "NTRU", "NTRULPRime", "SNTRUPrime", "KYBER" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configur [...]
"keyGenerator": { "index": 5, "kind": "property", "displayName": "Key Generator", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "javax.crypto.KeyGenerator", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Key Generator to be used in encapsulation and extraction" },
@@ -46,11 +46,22 @@
"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" },
"CamelPQCSignature": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The signature of a body", "constantName": "org.apache.camel.component.pqc.PQCConstants#SIGNATURE" },
"CamelPQCVerification": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The result of verification of a Body signature", "constantName": "org.apache.camel.component.pqc.PQCConstants#VERIFY" },
- "CamelPQCSecretKey": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The extracted key in case of extractSecretKeyFromEncapsulation operation and storeExtractedSecretKeyAsHeader option enabled", "constantName": "org.apache.camel.component.pqc.PQCConstants#SECRET_KEY" }
+ "CamelPQCSecretKey": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The extracted key in case of extractSecretKeyFromEncapsulation operation and storeExtractedSecretKeyAsHeader option enabled", "constantName": "org.apache.camel.component.pqc.PQCConstants#SECRET_KEY" },
+ "CamelPQCRemainingSignatures": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The remaining signatures for a stateful key", "constantName": "org.apache.camel.component.pqc.PQCConstants#REMAINING_SIGNATURES" },
+ "CamelPQCKeyState": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.camel.component.pqc.stateful.StatefulKeyState", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key state for a stateful key", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_STATE" },
+ "CamelPQCKeyId": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key ID for stateful key operations", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_ID" },
+ "CamelPQCKeyPair": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.security.KeyPair", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The generated key pair", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_PAIR" },
+ "CamelPQCKeyFormat": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key format for import\/export operations", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_FORMAT" },
+ "CamelPQCExportedKey": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "byte[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The exported key data", "constantName": "org.apache.camel.component.pqc.PQCConstants#EXPORTED_KEY" },
+ "CamelPQCKeyMetadata": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "org.apache.camel.component.pqc.lifecycle.KeyMetadata", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key metadata", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_METADATA" },
+ "CamelPQCKeyList": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "List of key metadata", "constantName": "org.apache.camel.component.pqc.PQCConstants#KEY_LIST" },
+ "CamelPQCAlgorithm": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The algorithm for key generation", "constantName": "org.apache.camel.component.pqc.PQCConstants#ALGORITHM" },
+ "CamelPQCIncludePrivate": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Include private key in export", "constantName": "org.apache.camel.component.pqc.PQCConstants#INCLUDE_PRIVATE" },
+ "CamelPQCRevocationReason": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Revocation reason", "constantName": "org.apache.camel.component.pqc.PQCConstants#REVOCATION_REASON" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Logical name" },
- "operation": { "index": 1, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguratio [...]
+ "operation": { "index": 1, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.pqc.PQCOperations", "enum": [ "sign", "verify", "generateSecretKeyEncapsulation", "extractSecretKeyEncapsulation", "extractSecretKeyFromEncapsulation", "getRemainingSignatures", "getKeyState", "deleteKeyState", "generateKeyPair", "exportKey", "importKey", "rotateKey", "getKeyMetadata", "listKeys", "expi [...]
"lazyStartProducer": { "index": 2, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
"keyEncapsulationAlgorithm": { "index": 3, "kind": "parameter", "displayName": "Key Encapsulation Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLKEM", "BIKE", "HQC", "CMCE", "SABER", "FRODO", "NTRU", "NTRULPRime", "SNTRUPrime", "KYBER" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configu [...]
"keyGenerator": { "index": 4, "kind": "parameter", "displayName": "Key Generator", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "javax.crypto.KeyGenerator", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Key Generator to be used in encapsulation and extraction" },
diff --git a/components/camel-pqc/src/main/docs/pqc-component.adoc b/components/camel-pqc/src/main/docs/pqc-component.adoc
index c46e8e1664ea..8cb124ecb9d8 100644
--- a/components/camel-pqc/src/main/docs/pqc-component.adoc
+++ b/components/camel-pqc/src/main/docs/pqc-component.adoc
@@ -15,7 +15,15 @@
*{component-header}*
-The PQC component supports signing and verifying payload using Post Quantum Cryptography algorithms.
+The PQC component supports signing and verifying payload using Post Quantum Cryptography algorithms, as well as key lifecycle management for Post-Quantum cryptographic keys.
+
+== Features
+
+* **Digital Signatures** - Sign and verify data using NIST-standardized and experimental PQC signature algorithms
+* **Key Encapsulation** - Generate and extract shared secrets using Key Encapsulation Mechanisms (KEM)
+* **Key Lifecycle Management** - Generate, rotate, expire, revoke, and manage PQC keys
+* **Multiple Implementations** - File-based and in-memory key storage options
+* **Key Export/Import** - Convert keys between PEM, DER, PKCS8, and X.509 formats
Prerequisites
@@ -52,23 +60,44 @@ Standardized and implemented
- ML-DSA
- SLH-DSA
- LMS
+- HSS (Hierarchical Signature System)
- XMSS
+- XMSSMT (XMSS Multi-Tree)
Experimental and non-standardized
+- Dilithium
- Falcon
- Picnic
-- Rainbow
+- SNOVA
+- MAYO
+- SPHINCS+
+
+== Supported Operations
+
+The component supports the following operations:
+
+**Signature Operations:**
-== Supported operations
+- sign - Sign data using a PQC signature algorithm
+- verify - Verify a signature
-The component supports five operations
+**Key Encapsulation Operations:**
-- sign
-- verify
-- generateSecretKeyEncapsulation
-- extractSecretKeyEncapsulation
-- extractSecretKeyFromEncapsulation
+- generateSecretKeyEncapsulation - Generate a secret key and encapsulate it
+- extractSecretKeyEncapsulation - Extract the encapsulation
+- extractSecretKeyFromEncapsulation - Extract the secret key from encapsulation
+
+**Key Lifecycle Operations:**
+
+- generateKeyPair - Generate a new PQC key pair
+- exportKey - Export a key to PEM, DER, PKCS8, or X.509 format
+- importKey - Import a key from bytes
+- rotateKey - Rotate a key and deprecate the old one
+- getKeyMetadata - Get metadata for a key
+- listKeys - List all keys with metadata
+- expireKey - Mark a key as expired
+- revokeKey - Revoke a compromised key
== Signature and Verification
@@ -258,7 +287,7 @@ The KEM Algorithm supported are the following:
Standardized and implemented
-- ML-KEM
+- ML-KEM
Experimental and non-standardized
@@ -269,6 +298,8 @@ Experimental and non-standardized
- SABER
- NTRU
- NTRULPRime
+- SNTRUPrime
+- Kyber
The component expects to find a KeyGenerator and a KeyPair in to the Camel Registry.
@@ -356,4 +387,530 @@ As example you could use the secret key to dynamically instruct the CryptoDataFo
This could be used to generate a secret key, protect it through Encapsulation and KEM approach and re-use it once extracted.
+== Key Lifecycle Management
+
+The PQC component provides comprehensive key lifecycle management capabilities for Post-Quantum Cryptography keys. This includes key generation, storage, rotation, expiration, revocation, and format conversion.
+
+Key lifecycle management is critical for maintaining security in production environments, especially with stateful hash-based signature algorithms (XMSS, XMSSMT, LMS, HSS) that have limited signature capacity.
+
+=== Key Lifecycle Manager Interface
+
+The `KeyLifecycleManager` interface provides the following operations:
+
+* **Key Generation** - Generate new key pairs with algorithm-specific parameters
+* **Key Storage** - Store and retrieve keys with metadata
+* **Key Rotation** - Rotate keys and deprecate old ones
+* **Key Expiration** - Mark keys as expired based on age or policy
+* **Key Revocation** - Revoke compromised keys with reason tracking
+* **Key Export/Import** - Convert keys between formats (PEM, DER, PKCS8, X.509)
+* **Key Listing** - List all keys with their metadata
+* **Rotation Detection** - Check if keys need rotation based on age or usage
+
+=== Available Implementations
+
+The component provides two implementations of `KeyLifecycleManager`:
+
+==== FileBasedKeyLifecycleManager
+
+A production-ready implementation that persists keys and metadata to disk.
+
+**Features:**
+
+* Persistent storage - keys survive application restarts
+* File-based storage with `.key` and `.metadata` files
+* In-memory caching for performance
+* Automatic loading of existing keys on startup
+* Secure file permissions
+
+**Use Cases:**
+
+* Production environments
+* Single-instance deployments
+* When persistence is required
+* Audit and compliance requirements
+
+**Example:**
+
+[source,java]
+--------------------------------------------------------------------------------
+KeyLifecycleManager keyManager = new FileBasedKeyLifecycleManager("/secure/keys");
+
+// Generate a new Dilithium key
+KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "app-signing-key",
+ DilithiumParameterSpec.dilithium2);
+
+// Use the key
+KeyMetadata metadata = keyManager.getKeyMetadata("app-signing-key");
+logger.info("Key created: {}", metadata);
+--------------------------------------------------------------------------------
+
+==== InMemoryKeyLifecycleManager
+
+A lightweight implementation that stores keys in memory only.
+
+**Features:**
+
+* Non-persistent storage - keys lost on restart
+* ConcurrentHashMap-based storage
+* No I/O overhead
+* Thread-safe operations
+* Additional utility methods: `clear()` and `size()`
+
+**Use Cases:**
+
+* Testing and development
+* Ephemeral workloads
+* Short-lived applications
+* Unit and integration tests
+
+**Example:**
+
+[source,java]
+--------------------------------------------------------------------------------
+InMemoryKeyLifecycleManager keyManager = new InMemoryKeyLifecycleManager();
+
+// Generate a test key
+KeyPair keyPair = keyManager.generateKeyPair("FALCON", "test-key");
+
+// Clean up after testing
+keyManager.clear();
+--------------------------------------------------------------------------------
+
+=== Key Generation
+
+The key lifecycle manager supports all PQC algorithms with sensible default parameter specifications.
+
+==== Signature Algorithms
+
+[source,java]
+--------------------------------------------------------------------------------
+// ML-DSA (uses default ML-DSA-65)
+KeyPair mldsaKey = keyManager.generateKeyPair("MLDSA", "mldsa-key");
+
+// Dilithium with specific parameter
+KeyPair dilithiumKey = keyManager.generateKeyPair("DILITHIUM", "dilithium-key",
+ DilithiumParameterSpec.dilithium3);
+
+// Falcon (uses default Falcon-512)
+KeyPair falconKey = keyManager.generateKeyPair("FALCON", "falcon-key");
+
+// XMSS (uses default 10-tree height with SHA-256)
+KeyPair xmssKey = keyManager.generateKeyPair("XMSS", "xmss-key");
+
+// XMSSMT (uses default XMSSMT-SHA2-20d2-256)
+KeyPair xmssmtKey = keyManager.generateKeyPair("XMSSMT", "xmssmt-key");
+
+// LMS/HSS (uses default LMS-SHA256-N32-H10)
+KeyPair lmsKey = keyManager.generateKeyPair("LMS", "lms-key");
+--------------------------------------------------------------------------------
+
+==== Key Encapsulation Algorithms
+
+[source,java]
+--------------------------------------------------------------------------------
+// ML-KEM (uses default)
+KeyPair mlkemKey = keyManager.generateKeyPair("MLKEM", "mlkem-key");
+
+// NTRU (uses default ntruhps2048509)
+KeyPair ntruKey = keyManager.generateKeyPair("NTRU", "ntru-key");
+
+// SNTRUPrime (uses default sntrup761)
+KeyPair sntrupKey = keyManager.generateKeyPair("SNTRUPrime", "sntrup-key");
+
+// BIKE (uses default bike128)
+KeyPair bikeKey = keyManager.generateKeyPair("BIKE", "bike-key");
+--------------------------------------------------------------------------------
+
+=== Key Metadata
+
+Each key is associated with metadata that tracks its lifecycle:
+
+[source,java]
+--------------------------------------------------------------------------------
+KeyMetadata metadata = keyManager.getKeyMetadata("my-key");
+
+System.out.println("Key ID: " + metadata.getKeyId());
+System.out.println("Algorithm: " + metadata.getAlgorithm());
+System.out.println("Status: " + metadata.getStatus());
+System.out.println("Created: " + metadata.getCreatedAt());
+System.out.println("Age (days): " + metadata.getAgeInDays());
+System.out.println("Usage count: " + metadata.getUsageCount());
+System.out.println("Expires at: " + metadata.getExpiresAt());
+System.out.println("Next rotation: " + metadata.getNextRotationAt());
+--------------------------------------------------------------------------------
+
+**Key Status Values:**
+
+* `ACTIVE` - Key is active and can be used
+* `EXPIRED` - Key has expired and should not be used
+* `REVOKED` - Key has been revoked due to compromise
+* `PENDING_ROTATION` - Key should be rotated soon
+* `DEPRECATED` - Key has been rotated and replaced
+
+=== Key Rotation
+
+Key rotation is essential for maintaining security. The lifecycle manager supports automated rotation:
+
+[source,java]
+--------------------------------------------------------------------------------
+// Check if key needs rotation
+boolean needsRotation = keyManager.needsRotation("old-key",
+ Duration.ofDays(90), // Max age: 90 days
+ 10000); // Max usage: 10,000 operations
+
+if (needsRotation) {
+ // Rotate the key
+ KeyPair newKey = keyManager.rotateKey("old-key", "new-key", "DILITHIUM");
+
+ // Old key is now DEPRECATED
+ // New key is ACTIVE
+ logger.info("Key rotated successfully");
+}
+--------------------------------------------------------------------------------
+
+=== Key Export and Import
+
+Keys can be exported and imported in multiple formats:
+
+**Supported Formats:**
+
+* `PEM` - Privacy-Enhanced Mail format (Base64 encoded)
+* `DER` - Distinguished Encoding Rules (binary)
+* `PKCS8` - PKCS#8 format for private keys
+* `X509` - X.509 format for public keys
+
+**Export Example:**
+
+[source,java]
+--------------------------------------------------------------------------------
+KeyPair keyPair = keyManager.getKey("my-key");
+
+// Export public key as PEM
+byte[] publicPem = keyManager.exportPublicKey(keyPair, KeyFormat.PEM);
+String pemString = new String(publicPem);
+System.out.println(pemString);
+// Output:
+// -----BEGIN PUBLIC KEY-----
+// MIIBIjAN...
+// -----END PUBLIC KEY-----
+
+// Export entire key pair (public only for safety)
+byte[] keyPairPem = keyManager.exportKey(keyPair, KeyFormat.PEM, false);
+
+// Export with private key (USE WITH CAUTION)
+byte[] fullExport = keyManager.exportKey(keyPair, KeyFormat.DER, true);
+--------------------------------------------------------------------------------
+
+**Import Example:**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Import from PEM format
+byte[] pemData = Files.readAllBytes(Paths.get("public-key.pem"));
+KeyPair imported = keyManager.importKey(pemData, KeyFormat.PEM, "DILITHIUM");
+
+// Store the imported key
+KeyMetadata metadata = new KeyMetadata("imported-key", "DILITHIUM");
+keyManager.storeKey("imported-key", imported, metadata);
+--------------------------------------------------------------------------------
+
+=== Key Expiration and Revocation
+
+**Expire a Key:**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Set expiration time
+KeyMetadata metadata = keyManager.getKeyMetadata("my-key");
+metadata.setExpiresAt(Instant.now().plus(Duration.ofDays(365)));
+keyManager.updateKeyMetadata("my-key", metadata);
+
+// Manually expire immediately
+keyManager.expireKey("my-key");
+
+// Check if expired
+if (metadata.isExpired()) {
+ logger.warn("Key {} has expired", metadata.getKeyId());
+}
+--------------------------------------------------------------------------------
+
+**Revoke a Key:**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Revoke a compromised key
+keyManager.revokeKey("compromised-key", "Private key exposed in log file");
+
+KeyMetadata metadata = keyManager.getKeyMetadata("compromised-key");
+assert metadata.getStatus() == KeyMetadata.KeyStatus.REVOKED;
+assert metadata.getDescription().contains("Revoked: Private key exposed");
+--------------------------------------------------------------------------------
+
+=== Listing and Managing Keys
+
+[source,java]
+--------------------------------------------------------------------------------
+// List all keys
+List<KeyMetadata> allKeys = keyManager.listKeys();
+
+// Filter active keys
+List<KeyMetadata> activeKeys = allKeys.stream()
+ .filter(m -> m.getStatus() == KeyMetadata.KeyStatus.ACTIVE)
+ .collect(Collectors.toList());
+
+// Find keys needing rotation
+for (KeyMetadata metadata : allKeys) {
+ if (keyManager.needsRotation(metadata.getKeyId(), Duration.ofDays(90), 5000)) {
+ logger.warn("Key {} needs rotation", metadata.getKeyId());
+ }
+}
+
+// Delete old keys
+for (KeyMetadata metadata : allKeys) {
+ if (metadata.getStatus() == KeyMetadata.KeyStatus.DEPRECATED
+ && metadata.getAgeInDays() > 365) {
+ keyManager.deleteKey(metadata.getKeyId());
+ logger.info("Deleted old deprecated key: {}", metadata.getKeyId());
+ }
+}
+--------------------------------------------------------------------------------
+
+=== Integration with Camel Routes
+
+The key lifecycle manager can be registered in the Camel registry and used in routes:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+@BindToRegistry("keyLifecycleManager")
+public KeyLifecycleManager createKeyManager() throws IOException {
+ return new FileBasedKeyLifecycleManager("/secure/keys");
+}
+
+@BindToRegistry("signingKey")
+public KeyPair createSigningKey() throws Exception {
+ KeyLifecycleManager manager = createKeyManager();
+ return manager.generateKeyPair("DILITHIUM", "route-signing-key",
+ DilithiumParameterSpec.dilithium2);
+}
+----
+
+YAML::
++
+[source,yaml]
+----
+# Configure key lifecycle manager in application.yaml
+camel:
+ beans:
+ keyLifecycleManager:
+ type: org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager
+ properties:
+ keyDirectoryPath: "/secure/keys"
+
+ signingKey:
+ type: org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager
+ scriptLanguage: groovy
+ script: |
+ def manager = new org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager('/secure/keys')
+ def spec = org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec.dilithium2
+ manager.generateKeyPair('DILITHIUM', 'route-signing-key', spec)
+----
+====
+
+**Using in Routes:**
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("timer:check-rotation?period=86400000") // Check daily
+ .bean("keyLifecycleManager", "needsRotation('route-signing-key', 90, 10000)")
+ .choice()
+ .when(body().isEqualTo(true))
+ .log("Rotating signing key")
+ .bean("keyLifecycleManager", "rotateKey('route-signing-key', 'route-signing-key-new', 'DILITHIUM')")
+ .to("log:rotation-complete")
+ .otherwise()
+ .log("Key rotation not needed")
+ .end();
+----
+
+YAML::
++
+[source,yaml]
+----
+# Automated key rotation route
+- route:
+ id: check-key-rotation
+ from:
+ uri: timer:check-rotation
+ parameters:
+ period: 86400000 # Check daily
+ steps:
+ - bean:
+ ref: keyLifecycleManager
+ method: needsRotation('route-signing-key', 90, 10000)
+ - choice:
+ when:
+ - simple: "${body} == true"
+ steps:
+ - log: "Rotating signing key"
+ - bean:
+ ref: keyLifecycleManager
+ method: rotateKey('route-signing-key', 'route-signing-key-new', 'DILITHIUM')
+ - to: log:rotation-complete
+ otherwise:
+ steps:
+ - log: "Key rotation not needed"
+----
+====
+
+=== Default Parameter Specifications
+
+The lifecycle manager provides sensible defaults for all algorithms:
+
+|===
+|Algorithm |Default Parameter Spec
+
+|DILITHIUM |dilithium2
+|FALCON |falcon_512
+|SPHINCSPLUS |sha2_128s
+|XMSS |10-tree height with SHA-256
+|XMSSMT |XMSSMT-SHA2-20d2-256
+|LMS/HSS |LMS-SHA256-N32-H10 with SHA256-N32-W4
+|NTRU |ntruhps2048509
+|NTRULPRime |ntrulpr653
+|SNTRUPrime |sntrup761
+|SABER |lightsaberkem128r3
+|FRODO |frodokem640aes
+|BIKE |bike128
+|HQC |hqc128
+|CMCE |mceliece348864
+|===
+
+=== Best Practices
+
+**1. Regular Key Rotation**
+
+Implement automated key rotation policies:
+
+[source,java]
+--------------------------------------------------------------------------------
+// Rotate keys every 90 days or after 10,000 uses
+Duration maxAge = Duration.ofDays(90);
+long maxUsage = 10000;
+
+if (keyManager.needsRotation(keyId, maxAge, maxUsage)) {
+ keyManager.rotateKey(keyId, keyId + "-new", algorithm);
+}
+--------------------------------------------------------------------------------
+
+**2. Secure Storage**
+
+For FileBasedKeyLifecycleManager:
+
+* Store keys in a secure directory with restricted permissions
+* Use encrypted file systems where possible
+* Implement backup strategies for key files
+* Monitor access to key directories
+
+**3. Key Metadata Tracking**
+
+Always update metadata when using keys:
+
+[source,java]
+--------------------------------------------------------------------------------
+KeyMetadata metadata = keyManager.getKeyMetadata(keyId);
+metadata.updateLastUsed(); // Increments usage count
+keyManager.updateKeyMetadata(keyId, metadata);
+--------------------------------------------------------------------------------
+
+**4. Handle Stateful Signatures Carefully**
+
+For XMSS, XMSSMT, LMS, and HSS algorithms:
+
+* Track signature usage to prevent exhaustion
+* Never reuse a stateful signature state (critical security requirement)
+* Monitor usage percentage and rotate before exhaustion
+* Set appropriate `maxSignatures` based on tree height
+
+**5. Secure Key Export**
+
+When exporting keys:
+
+* Export public keys only when possible
+* Encrypt private key exports
+* Use secure channels for key transfer
+* Audit all key export operations
+
+**6. Testing Strategy**
+
+Use InMemoryKeyLifecycleManager for tests:
+
+[source,java]
+--------------------------------------------------------------------------------
+@BeforeEach
+public void setup() {
+ keyManager = new InMemoryKeyLifecycleManager();
+}
+
+@AfterEach
+public void cleanup() {
+ keyManager.clear();
+}
+--------------------------------------------------------------------------------
+
+=== Thread Safety
+
+Both implementations are thread-safe:
+
+* **FileBasedKeyLifecycleManager**: Uses ConcurrentHashMap for caching and atomic file operations
+* **InMemoryKeyLifecycleManager**: Uses ConcurrentHashMap for all storage
+
+Concurrent key operations are safe:
+
+[source,java]
+--------------------------------------------------------------------------------
+ExecutorService executor = Executors.newFixedThreadPool(10);
+
+for (int i = 0; i < 100; i++) {
+ final int index = i;
+ executor.submit(() -> {
+ try {
+ keyManager.generateKeyPair("DILITHIUM", "key-" + index,
+ DilithiumParameterSpec.dilithium2);
+ } catch (Exception e) {
+ logger.error("Key generation failed", e);
+ }
+ });
+}
+
+executor.shutdown();
+executor.awaitTermination(1, TimeUnit.MINUTES);
+--------------------------------------------------------------------------------
+
+=== Migration Between Implementations
+
+To migrate from InMemoryKeyLifecycleManager to FileBasedKeyLifecycleManager:
+
+[source,java]
+--------------------------------------------------------------------------------
+InMemoryKeyLifecycleManager memoryManager = new InMemoryKeyLifecycleManager();
+FileBasedKeyLifecycleManager fileManager = new FileBasedKeyLifecycleManager("/secure/keys");
+
+// Export all keys
+List<KeyMetadata> keys = memoryManager.listKeys();
+for (KeyMetadata metadata : keys) {
+ KeyPair keyPair = memoryManager.getKey(metadata.getKeyId());
+ fileManager.storeKey(metadata.getKeyId(), keyPair, metadata);
+}
+
+logger.info("Migrated {} keys to file-based storage", keys.size());
+--------------------------------------------------------------------------------
+
include::spring-boot:partial$starter.adoc[]
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConstants.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConstants.java
index dd23c2123a72..c56532472b6e 100644
--- a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConstants.java
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConstants.java
@@ -34,4 +34,38 @@ public interface PQCConstants {
@Metadata(description = "The extracted key in case of extractSecretKeyFromEncapsulation operation and storeExtractedSecretKeyAsHeader option enabled",
javaType = "Boolean")
String SECRET_KEY = "CamelPQCSecretKey";
+
+ @Metadata(description = "The remaining signatures for a stateful key", javaType = "Long")
+ String REMAINING_SIGNATURES = "CamelPQCRemainingSignatures";
+
+ @Metadata(description = "The key state for a stateful key",
+ javaType = "org.apache.camel.component.pqc.stateful.StatefulKeyState")
+ String KEY_STATE = "CamelPQCKeyState";
+
+ @Metadata(description = "The key ID for stateful key operations", javaType = "String")
+ String KEY_ID = "CamelPQCKeyId";
+
+ @Metadata(description = "The generated key pair", javaType = "java.security.KeyPair")
+ String KEY_PAIR = "CamelPQCKeyPair";
+
+ @Metadata(description = "The key format for import/export operations", javaType = "String")
+ String KEY_FORMAT = "CamelPQCKeyFormat";
+
+ @Metadata(description = "The exported key data", javaType = "byte[]")
+ String EXPORTED_KEY = "CamelPQCExportedKey";
+
+ @Metadata(description = "The key metadata", javaType = "org.apache.camel.component.pqc.lifecycle.KeyMetadata")
+ String KEY_METADATA = "CamelPQCKeyMetadata";
+
+ @Metadata(description = "List of key metadata", javaType = "java.util.List")
+ String KEY_LIST = "CamelPQCKeyList";
+
+ @Metadata(description = "The algorithm for key generation", javaType = "String")
+ String ALGORITHM = "CamelPQCAlgorithm";
+
+ @Metadata(description = "Include private key in export", javaType = "Boolean")
+ String INCLUDE_PRIVATE = "CamelPQCIncludePrivate";
+
+ @Metadata(description = "Revocation reason", javaType = "String")
+ String REVOCATION_REASON = "CamelPQCRevocationReason";
}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCOperations.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCOperations.java
index f1e892ced373..fd4da0b79b50 100644
--- a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCOperations.java
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCOperations.java
@@ -21,5 +21,16 @@ public enum PQCOperations {
verify,
generateSecretKeyEncapsulation,
extractSecretKeyEncapsulation,
- extractSecretKeyFromEncapsulation
+ extractSecretKeyFromEncapsulation,
+ getRemainingSignatures,
+ getKeyState,
+ deleteKeyState,
+ generateKeyPair,
+ exportKey,
+ importKey,
+ rotateKey,
+ getKeyMetadata,
+ listKeys,
+ expireKey,
+ revokeKey
}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/FileBasedKeyLifecycleManager.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/FileBasedKeyLifecycleManager.java
new file mode 100644
index 000000000000..18d5f3f8d35f
--- /dev/null
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/FileBasedKeyLifecycleManager.java
@@ -0,0 +1,405 @@
+/*
+ * 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.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.camel.component.pqc.PQCKeyEncapsulationAlgorithms;
+import org.apache.camel.component.pqc.PQCSignatureAlgorithms;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * File-based implementation of KeyLifecycleManager. Stores keys and metadata in a specified directory with secure
+ * permissions.
+ */
+public class FileBasedKeyLifecycleManager implements KeyLifecycleManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FileBasedKeyLifecycleManager.class);
+
+ private final Path keyDirectory;
+ private final ConcurrentHashMap<String, KeyPair> keyCache = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<String, KeyMetadata> metadataCache = new ConcurrentHashMap<>();
+
+ public FileBasedKeyLifecycleManager(String keyDirectoryPath) throws IOException {
+ this.keyDirectory = Paths.get(keyDirectoryPath);
+ Files.createDirectories(keyDirectory);
+ LOG.info("Initialized FileBasedKeyLifecycleManager with directory: {}", keyDirectory);
+ loadExistingKeys();
+ }
+
+ @Override
+ public KeyPair generateKeyPair(String algorithm, String keyId) throws Exception {
+ return generateKeyPair(algorithm, keyId, null);
+ }
+
+ @Override
+ public KeyPair generateKeyPair(String algorithm, String keyId, Object parameterSpec) throws Exception {
+ LOG.info("Generating key pair for algorithm: {}, keyId: {}", algorithm, keyId);
+
+ KeyPairGenerator generator;
+ String provider = determineProvider(algorithm);
+
+ if (provider != null) {
+ generator = KeyPairGenerator.getInstance(getAlgorithmName(algorithm), provider);
+ } else {
+ generator = KeyPairGenerator.getInstance(getAlgorithmName(algorithm));
+ }
+
+ // Initialize with parameter spec if provided
+ if (parameterSpec != null) {
+ if (parameterSpec instanceof AlgorithmParameterSpec) {
+ generator.initialize((AlgorithmParameterSpec) parameterSpec, new SecureRandom());
+ } else if (parameterSpec instanceof Integer) {
+ generator.initialize((Integer) parameterSpec, new SecureRandom());
+ }
+ } else {
+ // Use default parameter spec for the algorithm
+ AlgorithmParameterSpec defaultSpec = getDefaultParameterSpec(algorithm);
+ if (defaultSpec != null) {
+ generator.initialize(defaultSpec, new SecureRandom());
+ } else {
+ generator.initialize(getDefaultKeySize(algorithm), new SecureRandom());
+ }
+ }
+
+ KeyPair keyPair = generator.generateKeyPair();
+
+ // Create metadata
+ KeyMetadata metadata = new KeyMetadata(keyId, algorithm);
+ metadata.setDescription("Generated on " + new Date());
+
+ // Store the key
+ storeKey(keyId, keyPair, metadata);
+
+ LOG.info("Generated key pair: {}", metadata);
+ return keyPair;
+ }
+
+ @Override
+ public byte[] exportKey(KeyPair keyPair, KeyFormat format, boolean includePrivate) throws Exception {
+ return KeyFormatConverter.exportKeyPair(keyPair, format, includePrivate);
+ }
+
+ @Override
+ public byte[] exportPublicKey(KeyPair keyPair, KeyFormat format) throws Exception {
+ return KeyFormatConverter.exportPublicKey(keyPair.getPublic(), format);
+ }
+
+ @Override
+ public KeyPair importKey(byte[] keyData, KeyFormat format, String algorithm) throws Exception {
+ // Try to import as private key first (which includes public key)
+ try {
+ PrivateKey privateKey = KeyFormatConverter.importPrivateKey(keyData, format, getAlgorithmName(algorithm));
+ // For PQC algorithms, we need to derive the public key
+ // This is algorithm-specific and may require regeneration
+ LOG.warn("Importing private key only - public key derivation may be needed");
+ return new KeyPair(null, privateKey);
+ } catch (Exception e) {
+ // Try as public key only
+ PublicKey publicKey = KeyFormatConverter.importPublicKey(keyData, format, getAlgorithmName(algorithm));
+ return new KeyPair(publicKey, null);
+ }
+ }
+
+ @Override
+ public KeyPair rotateKey(String oldKeyId, String newKeyId, String algorithm) throws Exception {
+ LOG.info("Rotating key from {} to {}", oldKeyId, newKeyId);
+
+ // Get old key metadata
+ KeyMetadata oldMetadata = getKeyMetadata(oldKeyId);
+ if (oldMetadata == null) {
+ throw new IllegalArgumentException("Old key not found: " + oldKeyId);
+ }
+
+ // Mark old key as deprecated
+ oldMetadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+ updateKeyMetadata(oldKeyId, oldMetadata);
+
+ // Generate new key
+ KeyPair newKeyPair = generateKeyPair(algorithm, newKeyId);
+
+ LOG.info("Key rotation completed: {} -> {}", oldKeyId, newKeyId);
+ return newKeyPair;
+ }
+
+ @Override
+ public void storeKey(String keyId, KeyPair keyPair, KeyMetadata metadata) throws Exception {
+ // Store key pair
+ Path keyFile = getKeyFile(keyId);
+ try (ObjectOutputStream oos = new ObjectOutputStream(
+ new BufferedOutputStream(
+ Files.newOutputStream(keyFile, StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING)))) {
+ oos.writeObject(keyPair);
+ }
+
+ // Store metadata
+ Path metadataFile = getMetadataFile(keyId);
+ try (ObjectOutputStream oos = new ObjectOutputStream(
+ new BufferedOutputStream(
+ Files.newOutputStream(metadataFile, StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING)))) {
+ oos.writeObject(metadata);
+ }
+
+ // Update caches
+ keyCache.put(keyId, keyPair);
+ metadataCache.put(keyId, metadata);
+
+ LOG.debug("Stored key and metadata for: {}", keyId);
+ }
+
+ @Override
+ public KeyPair getKey(String keyId) throws Exception {
+ if (keyCache.containsKey(keyId)) {
+ return keyCache.get(keyId);
+ }
+
+ Path keyFile = getKeyFile(keyId);
+ if (!Files.exists(keyFile)) {
+ throw new IllegalArgumentException("Key not found: " + keyId);
+ }
+
+ try (ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(keyFile)))) {
+ KeyPair keyPair = (KeyPair) ois.readObject();
+ keyCache.put(keyId, keyPair);
+ return keyPair;
+ }
+ }
+
+ @Override
+ public KeyMetadata getKeyMetadata(String keyId) throws Exception {
+ if (metadataCache.containsKey(keyId)) {
+ return metadataCache.get(keyId);
+ }
+
+ Path metadataFile = getMetadataFile(keyId);
+ if (!Files.exists(metadataFile)) {
+ return null;
+ }
+
+ try (ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(metadataFile)))) {
+ KeyMetadata metadata = (KeyMetadata) ois.readObject();
+ metadataCache.put(keyId, metadata);
+ return metadata;
+ }
+ }
+
+ @Override
+ public void updateKeyMetadata(String keyId, KeyMetadata metadata) throws Exception {
+ Path metadataFile = getMetadataFile(keyId);
+ try (ObjectOutputStream oos = new ObjectOutputStream(
+ new BufferedOutputStream(
+ Files.newOutputStream(metadataFile, StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING)))) {
+ oos.writeObject(metadata);
+ }
+ metadataCache.put(keyId, metadata);
+ }
+
+ @Override
+ public void deleteKey(String keyId) throws Exception {
+ Files.deleteIfExists(getKeyFile(keyId));
+ Files.deleteIfExists(getMetadataFile(keyId));
+ keyCache.remove(keyId);
+ metadataCache.remove(keyId);
+ LOG.info("Deleted key: {}", keyId);
+ }
+
+ @Override
+ public List<KeyMetadata> listKeys() throws Exception {
+ return new ArrayList<>(metadataCache.values());
+ }
+
+ @Override
+ public boolean needsRotation(String keyId, Duration maxAge, long maxUsage) throws Exception {
+ KeyMetadata metadata = getKeyMetadata(keyId);
+ if (metadata == null) {
+ return false;
+ }
+
+ if (metadata.needsRotation()) {
+ return true;
+ }
+
+ if (maxAge != null && metadata.getAgeInDays() > maxAge.toDays()) {
+ return true;
+ }
+
+ if (maxUsage > 0 && metadata.getUsageCount() >= maxUsage) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void expireKey(String keyId) throws Exception {
+ KeyMetadata metadata = getKeyMetadata(keyId);
+ if (metadata != null) {
+ metadata.setStatus(KeyMetadata.KeyStatus.EXPIRED);
+ updateKeyMetadata(keyId, metadata);
+ LOG.info("Expired key: {}", keyId);
+ }
+ }
+
+ @Override
+ public void revokeKey(String keyId, String reason) throws Exception {
+ KeyMetadata metadata = getKeyMetadata(keyId);
+ if (metadata != null) {
+ metadata.setStatus(KeyMetadata.KeyStatus.REVOKED);
+ metadata.setDescription((metadata.getDescription() != null ? metadata.getDescription() + "; " : "")
+ + "Revoked: " + reason);
+ updateKeyMetadata(keyId, metadata);
+ LOG.info("Revoked key: {} - {}", keyId, reason);
+ }
+ }
+
+ private void loadExistingKeys() {
+ try {
+ Files.list(keyDirectory)
+ .filter(path -> path.toString().endsWith(".metadata"))
+ .forEach(path -> {
+ try {
+ String keyId = path.getFileName().toString().replace(".metadata", "");
+ KeyMetadata metadata = getKeyMetadata(keyId);
+ if (metadata != null) {
+ LOG.debug("Loaded existing key: {}", metadata);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to load key metadata: {}", path, e);
+ }
+ });
+ } catch (IOException e) {
+ LOG.warn("Failed to list existing keys", e);
+ }
+ }
+
+ private Path getKeyFile(String keyId) {
+ return keyDirectory.resolve(keyId + ".key");
+ }
+
+ private Path getMetadataFile(String keyId) {
+ return keyDirectory.resolve(keyId + ".metadata");
+ }
+
+ private String determineProvider(String algorithm) {
+ try {
+ PQCSignatureAlgorithms sigAlg = PQCSignatureAlgorithms.valueOf(algorithm);
+ return sigAlg.getBcProvider();
+ } catch (IllegalArgumentException e1) {
+ try {
+ PQCKeyEncapsulationAlgorithms kemAlg = PQCKeyEncapsulationAlgorithms.valueOf(algorithm);
+ return kemAlg.getBcProvider();
+ } catch (IllegalArgumentException e2) {
+ return null;
+ }
+ }
+ }
+
+ private String getAlgorithmName(String algorithm) {
+ try {
+ return PQCSignatureAlgorithms.valueOf(algorithm).getAlgorithm();
+ } catch (IllegalArgumentException e1) {
+ try {
+ return PQCKeyEncapsulationAlgorithms.valueOf(algorithm).getAlgorithm();
+ } catch (IllegalArgumentException e2) {
+ return algorithm;
+ }
+ }
+ }
+
+ private AlgorithmParameterSpec getDefaultParameterSpec(String algorithm) {
+ // Provide default parameter specs for PQC algorithms
+ try {
+ switch (algorithm) {
+ case "DILITHIUM":
+ return org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec.dilithium2;
+ case "MLDSA":
+ case "SLHDSA":
+ // These use default initialization
+ return null;
+ case "FALCON":
+ return org.bouncycastle.pqc.jcajce.spec.FalconParameterSpec.falcon_512;
+ case "SPHINCSPLUS":
+ return org.bouncycastle.pqc.jcajce.spec.SPHINCSPlusParameterSpec.sha2_128s;
+ case "XMSS":
+ return new org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec(
+ 10,
+ org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec.SHA256);
+ case "XMSSMT":
+ return org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec.XMSSMT_SHA2_20d2_256;
+ case "LMS":
+ case "HSS":
+ return new org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec(
+ org.bouncycastle.pqc.crypto.lms.LMSigParameters.lms_sha256_n32_h10,
+ org.bouncycastle.pqc.crypto.lms.LMOtsParameters.sha256_n32_w4);
+ case "MLKEM":
+ case "KYBER":
+ // These use default initialization
+ return null;
+ case "NTRU":
+ return org.bouncycastle.pqc.jcajce.spec.NTRUParameterSpec.ntruhps2048509;
+ case "NTRULPRime":
+ return org.bouncycastle.pqc.jcajce.spec.NTRULPRimeParameterSpec.ntrulpr653;
+ case "SNTRUPrime":
+ return org.bouncycastle.pqc.jcajce.spec.SNTRUPrimeParameterSpec.sntrup761;
+ case "SABER":
+ return org.bouncycastle.pqc.jcajce.spec.SABERParameterSpec.lightsaberkem128r3;
+ case "FRODO":
+ return org.bouncycastle.pqc.jcajce.spec.FrodoParameterSpec.frodokem640aes;
+ case "BIKE":
+ return org.bouncycastle.pqc.jcajce.spec.BIKEParameterSpec.bike128;
+ case "HQC":
+ return org.bouncycastle.pqc.jcajce.spec.HQCParameterSpec.hqc128;
+ case "CMCE":
+ return org.bouncycastle.pqc.jcajce.spec.CMCEParameterSpec.mceliece348864;
+ default:
+ return null;
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to create default parameter spec for algorithm: {}", algorithm, e);
+ return null;
+ }
+ }
+
+ private int getDefaultKeySize(String algorithm) {
+ // Default key sizes for different algorithms
+ // For PQC algorithms, key size is usually determined by parameter specs
+ return 256;
+ }
+}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyFormatConverter.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyFormatConverter.java
new file mode 100644
index 000000000000..b996197f5077
--- /dev/null
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyFormatConverter.java
@@ -0,0 +1,175 @@
+/*
+ * 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.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * Utility class for converting PQC keys between different formats (PEM, DER, PKCS8, X509).
+ */
+public class KeyFormatConverter {
+
+ /**
+ * Export a public key to the specified format
+ */
+ public static byte[] exportPublicKey(PublicKey publicKey, KeyLifecycleManager.KeyFormat format) throws Exception {
+ switch (format) {
+ case PEM:
+ return exportPublicKeyToPEM(publicKey);
+ case DER:
+ case X509:
+ return publicKey.getEncoded(); // X.509 format (DER encoded)
+ default:
+ throw new IllegalArgumentException("Unsupported format for public key: " + format);
+ }
+ }
+
+ /**
+ * Export a private key to the specified format
+ */
+ public static byte[] exportPrivateKey(PrivateKey privateKey, KeyLifecycleManager.KeyFormat format) throws Exception {
+ switch (format) {
+ case PEM:
+ return exportPrivateKeyToPEM(privateKey);
+ case DER:
+ case PKCS8:
+ return privateKey.getEncoded(); // PKCS#8 format (DER encoded)
+ default:
+ throw new IllegalArgumentException("Unsupported format for private key: " + format);
+ }
+ }
+
+ /**
+ * Export a key pair to the specified format
+ */
+ public static byte[] exportKeyPair(KeyPair keyPair, KeyLifecycleManager.KeyFormat format, boolean includePrivate)
+ throws Exception {
+ if (includePrivate) {
+ // Export both keys in a combined format
+ byte[] publicKeyBytes = exportPublicKey(keyPair.getPublic(), format);
+ byte[] privateKeyBytes = exportPrivateKey(keyPair.getPrivate(), format);
+
+ // Concatenate with separator for PEM
+ if (format == KeyLifecycleManager.KeyFormat.PEM) {
+ return (new String(publicKeyBytes) + "\n" + new String(privateKeyBytes)).getBytes();
+ } else {
+ // For DER, just return private key (contains public key info)
+ return privateKeyBytes;
+ }
+ } else {
+ return exportPublicKey(keyPair.getPublic(), format);
+ }
+ }
+
+ /**
+ * Import a public key from bytes
+ */
+ public static PublicKey importPublicKey(byte[] keyData, KeyLifecycleManager.KeyFormat format, String algorithm)
+ throws Exception {
+ byte[] derBytes;
+
+ if (format == KeyLifecycleManager.KeyFormat.PEM) {
+ derBytes = parsePEMPublicKey(keyData);
+ } else {
+ derBytes = keyData;
+ }
+
+ X509EncodedKeySpec spec = new X509EncodedKeySpec(derBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+ return keyFactory.generatePublic(spec);
+ }
+
+ /**
+ * Import a private key from bytes
+ */
+ public static PrivateKey importPrivateKey(byte[] keyData, KeyLifecycleManager.KeyFormat format, String algorithm)
+ throws Exception {
+ byte[] derBytes;
+
+ if (format == KeyLifecycleManager.KeyFormat.PEM) {
+ derBytes = parsePEMPrivateKey(keyData);
+ } else {
+ derBytes = keyData;
+ }
+
+ PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(derBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+ return keyFactory.generatePrivate(spec);
+ }
+
+ /**
+ * Export public key to PEM format
+ */
+ private static byte[] exportPublicKeyToPEM(PublicKey publicKey) throws Exception {
+ byte[] encoded = publicKey.getEncoded();
+ String base64 = Base64.getEncoder().encodeToString(encoded);
+
+ // Format as PEM with line breaks every 64 characters
+ StringBuilder pem = new StringBuilder();
+ pem.append("-----BEGIN PUBLIC KEY-----\n");
+ for (int i = 0; i < base64.length(); i += 64) {
+ pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
+ }
+ pem.append("-----END PUBLIC KEY-----\n");
+ return pem.toString().getBytes();
+ }
+
+ /**
+ * Export private key to PEM format
+ */
+ private static byte[] exportPrivateKeyToPEM(PrivateKey privateKey) throws Exception {
+ byte[] encoded = privateKey.getEncoded();
+ String base64 = Base64.getEncoder().encodeToString(encoded);
+
+ // Format as PEM with line breaks every 64 characters
+ StringBuilder pem = new StringBuilder();
+ pem.append("-----BEGIN PRIVATE KEY-----\n");
+ for (int i = 0; i < base64.length(); i += 64) {
+ pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
+ }
+ pem.append("-----END PRIVATE KEY-----\n");
+ return pem.toString().getBytes();
+ }
+
+ /**
+ * Parse PEM-encoded public key
+ */
+ private static byte[] parsePEMPublicKey(byte[] pemData) throws Exception {
+ String pemString = new String(pemData);
+ pemString = pemString.replace("-----BEGIN PUBLIC KEY-----", "")
+ .replace("-----END PUBLIC KEY-----", "")
+ .replaceAll("\\s", "");
+ return Base64.getDecoder().decode(pemString);
+ }
+
+ /**
+ * Parse PEM-encoded private key
+ */
+ private static byte[] parsePEMPrivateKey(byte[] pemData) throws Exception {
+ String pemString = new String(pemData);
+ pemString = pemString.replace("-----BEGIN PRIVATE KEY-----", "")
+ .replace("-----END PRIVATE KEY-----", "")
+ .replaceAll("\\s", "");
+ return Base64.getDecoder().decode(pemString);
+ }
+}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyLifecycleManager.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyLifecycleManager.java
new file mode 100644
index 000000000000..f366960a9642
--- /dev/null
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyLifecycleManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.util.List;
+
+/**
+ * Interface for managing the lifecycle of PQC keys including generation, rotation, and expiration.
+ */
+public interface KeyLifecycleManager {
+
+ /**
+ * Generate a new key pair for the specified algorithm
+ */
+ KeyPair generateKeyPair(String algorithm, String keyId) throws Exception;
+
+ /**
+ * Generate a key pair with specific parameters
+ */
+ KeyPair generateKeyPair(String algorithm, String keyId, Object parameterSpec) throws Exception;
+
+ /**
+ * Export a key pair to the specified format
+ */
+ byte[] exportKey(KeyPair keyPair, KeyFormat format, boolean includePrivate) throws Exception;
+
+ /**
+ * Export public key only
+ */
+ byte[] exportPublicKey(KeyPair keyPair, KeyFormat format) throws Exception;
+
+ /**
+ * Import a key pair from bytes
+ */
+ KeyPair importKey(byte[] keyData, KeyFormat format, String algorithm) throws Exception;
+
+ /**
+ * Rotate a key - generates new key and deprecates old one
+ */
+ KeyPair rotateKey(String oldKeyId, String newKeyId, String algorithm) throws Exception;
+
+ /**
+ * Store key pair with metadata
+ */
+ void storeKey(String keyId, KeyPair keyPair, KeyMetadata metadata) throws Exception;
+
+ /**
+ * Retrieve key pair by ID
+ */
+ KeyPair getKey(String keyId) throws Exception;
+
+ /**
+ * Get key metadata
+ */
+ KeyMetadata getKeyMetadata(String keyId) throws Exception;
+
+ /**
+ * Update key metadata
+ */
+ void updateKeyMetadata(String keyId, KeyMetadata metadata) throws Exception;
+
+ /**
+ * Delete key
+ */
+ void deleteKey(String keyId) throws Exception;
+
+ /**
+ * List all keys
+ */
+ List<KeyMetadata> listKeys() throws Exception;
+
+ /**
+ * Check if a key needs rotation based on age or usage
+ */
+ boolean needsRotation(String keyId, Duration maxAge, long maxUsage) throws Exception;
+
+ /**
+ * Mark a key as expired
+ */
+ void expireKey(String keyId) throws Exception;
+
+ /**
+ * Mark a key as revoked
+ */
+ void revokeKey(String keyId, String reason) throws Exception;
+
+ enum KeyFormat {
+ PEM,
+ DER,
+ PKCS8,
+ X509
+ }
+}
diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyMetadata.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyMetadata.java
new file mode 100644
index 000000000000..fe7643ac232a
--- /dev/null
+++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/KeyMetadata.java
@@ -0,0 +1,132 @@
+/*
+ * 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.io.Serializable;
+import java.time.Instant;
+
+/**
+ * Metadata about a PQC key pair including creation time, rotation schedule, and usage statistics.
+ */
+public class KeyMetadata implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String keyId;
+ private final String algorithm;
+ private final Instant createdAt;
+ private Instant lastUsedAt;
+ private Instant expiresAt;
+ private Instant nextRotationAt;
+ private long usageCount;
+ private KeyStatus status;
+ private String description;
+
+ public enum KeyStatus {
+ ACTIVE,
+ EXPIRED,
+ REVOKED,
+ PENDING_ROTATION,
+ DEPRECATED
+ }
+
+ public KeyMetadata(String keyId, String algorithm) {
+ this.keyId = keyId;
+ this.algorithm = algorithm;
+ this.createdAt = Instant.now();
+ this.lastUsedAt = createdAt;
+ this.usageCount = 0;
+ this.status = KeyStatus.ACTIVE;
+ }
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ public Instant getLastUsedAt() {
+ return lastUsedAt;
+ }
+
+ public void updateLastUsed() {
+ this.lastUsedAt = Instant.now();
+ this.usageCount++;
+ }
+
+ public Instant getExpiresAt() {
+ return expiresAt;
+ }
+
+ public void setExpiresAt(Instant expiresAt) {
+ this.expiresAt = expiresAt;
+ }
+
+ public Instant getNextRotationAt() {
+ return nextRotationAt;
+ }
+
+ public void setNextRotationAt(Instant nextRotationAt) {
+ this.nextRotationAt = nextRotationAt;
+ }
+
+ public long getUsageCount() {
+ return usageCount;
+ }
+
+ public KeyStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(KeyStatus status) {
+ this.status = status;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public boolean isExpired() {
+ return status == KeyStatus.EXPIRED || (expiresAt != null && Instant.now().isAfter(expiresAt));
+ }
+
+ public boolean needsRotation() {
+ return status == KeyStatus.PENDING_ROTATION
+ || (nextRotationAt != null && Instant.now().isAfter(nextRotationAt));
+ }
+
+ public long getAgeInDays() {
+ return java.time.Duration.between(createdAt, Instant.now()).toDays();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "KeyMetadata[keyId=%s, algorithm=%s, status=%s, created=%s, age=%d days, usage=%d]",
+ keyId, algorithm, status, createdAt, getAgeInDays(), usageCount);
+ }
+}
diff --git a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndToEndIntegrationTest.java b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndToEndIntegrationTest.java
new file mode 100644
index 000000000000..edc6f600ee2a
--- /dev/null
+++ b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCEndToEndIntegrationTest.java
@@ -0,0 +1,415 @@
+/*
+ * 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.Security;
+import java.security.Signature;
+
+import org.apache.camel.BindToRegistry;
+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.junit5.CamelTestSupport;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.NTRUParameterSpec;
+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.*;
+
+/**
+ * End-to-end integration tests demonstrating complete lifecycle from key generation to actual usage.
+ */
+public class PQCEndToEndIntegrationTest extends CamelTestSupport {
+
+ @TempDir
+ Path tempDir;
+
+ @EndpointInject("mock:signed")
+ protected MockEndpoint mockSigned;
+
+ @EndpointInject("mock:verified")
+ protected MockEndpoint mockVerified;
+
+ @EndpointInject("mock:encapsulated")
+ protected MockEndpoint mockEncapsulated;
+
+ @EndpointInject("mock:extracted")
+ protected MockEndpoint mockExtracted;
+
+ @Produce("direct:signMessage")
+ protected ProducerTemplate signTemplate;
+
+ @Produce("direct:encapsulateKey")
+ protected ProducerTemplate kemTemplate;
+
+ @Produce("direct:rotateAndSign")
+ protected ProducerTemplate rotateSignTemplate;
+
+ private KeyLifecycleManager keyManager;
+ private KeyPair dilithiumKeyPair;
+ private KeyPair ntruKeyPair;
+
+ @Override
+ protected void doPreSetup() throws Exception {
+ super.doPreSetup();
+
+ // Ensure providers are registered BEFORE any key operations
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastlePQCProvider());
+ }
+
+ // Initialize key manager before routes are created
+ if (keyManager == null && tempDir != null) {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+ // Pre-generate keys needed for binding
+ dilithiumKeyPair = keyManager.generateKeyPair("DILITHIUM", "setup-dilithium", DilithiumParameterSpec.dilithium2);
+ ntruKeyPair = keyManager.generateKeyPair("NTRU", "setup-ntru", NTRUParameterSpec.ntruhps2048509);
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // Route 1: Sign and verify a message using lifecycle-generated key
+ from("direct:signMessage")
+ .to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM")
+ .to("mock:signed")
+ .to("pqc:verify?operation=verify&signatureAlgorithm=DILITHIUM")
+ .to("mock:verified");
+
+ // Route 2: KEM operations using lifecycle-generated key
+ from("direct:encapsulateKey")
+ .to("pqc:keyenc?operation=generateSecretKeyEncapsulation&symmetricKeyAlgorithm=AES")
+ .to("mock:encapsulated")
+ .to("pqc:keyenc?operation=extractSecretKeyEncapsulation&symmetricKeyAlgorithm=AES")
+ .to("mock:extracted");
+
+ // Route 3: Generate new key, rotate, and sign with new key
+ from("direct:rotateAndSign")
+ .to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM")
+ .to("mock:signed");
+ }
+ };
+ }
+
+ @AfterEach
+ public void cleanup() throws Exception {
+ if (keyManager != null) {
+ try {
+ keyManager.deleteKey("e2e-dilithium-key");
+ keyManager.deleteKey("e2e-ntru-key");
+ keyManager.deleteKey("e2e-old-key");
+ keyManager.deleteKey("e2e-new-key");
+ } catch (Exception e) {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ @Test
+ void testEndToEndSignatureWithGeneratedKey() throws Exception {
+ // Step 1: Generate a Dilithium key pair using lifecycle manager
+ dilithiumKeyPair = keyManager.generateKeyPair("DILITHIUM", "e2e-dilithium-key",
+ DilithiumParameterSpec.dilithium2);
+ assertNotNull(dilithiumKeyPair);
+
+ // Verify metadata was created
+ KeyMetadata metadata = keyManager.getKeyMetadata("e2e-dilithium-key");
+ assertNotNull(metadata);
+ assertEquals("e2e-dilithium-key", metadata.getKeyId());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+ assertEquals(0, metadata.getUsageCount());
+
+ // Step 2: Use the generated key to sign a message through Camel route
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ String message = "This is a test message for end-to-end PQC signing!";
+ signTemplate.sendBody(message);
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ // Verify signature was created
+ assertNotNull(mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE));
+ byte[] signature = mockSigned.getExchanges().get(0).getMessage().getHeader(PQCConstants.SIGNATURE, byte[].class);
+ assertTrue(signature.length > 0);
+
+ // Verify signature verification succeeded
+ Boolean verified = mockVerified.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY, Boolean.class);
+ assertTrue(verified);
+
+ // Step 3: Update key metadata to track usage
+ metadata.updateLastUsed();
+ keyManager.updateKeyMetadata("e2e-dilithium-key", metadata);
+
+ // Verify usage was tracked
+ KeyMetadata updatedMetadata = keyManager.getKeyMetadata("e2e-dilithium-key");
+ assertEquals(1, updatedMetadata.getUsageCount());
+ }
+
+ @Test
+ void testEndToEndKeyRotationWithSigning() throws Exception {
+ // Step 1: Generate initial key
+ KeyPair oldKeyPair = keyManager.generateKeyPair("DILITHIUM", "e2e-old-key", DilithiumParameterSpec.dilithium2);
+ assertNotNull(oldKeyPair);
+
+ // Step 2: Use old key for signing
+ dilithiumKeyPair = oldKeyPair;
+ mockSigned.reset();
+ mockSigned.expectedMessageCount(1);
+
+ signTemplate.sendBody("Message signed with old key");
+ mockSigned.assertIsSatisfied();
+
+ // Step 3: Rotate the key
+ KeyPair newKeyPair = keyManager.rotateKey("e2e-old-key", "e2e-new-key", "DILITHIUM");
+ assertNotNull(newKeyPair);
+
+ // Verify old key is deprecated
+ KeyMetadata oldMetadata = keyManager.getKeyMetadata("e2e-old-key");
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, oldMetadata.getStatus());
+
+ // Verify new key is active
+ KeyMetadata newMetadata = keyManager.getKeyMetadata("e2e-new-key");
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, newMetadata.getStatus());
+
+ // Step 4: Use new key for signing
+ dilithiumKeyPair = newKeyPair;
+ mockSigned.reset();
+ mockVerified.reset();
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ signTemplate.sendBody("Message signed with new key");
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ // Verify signature with new key works
+ Boolean verified = mockVerified.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY, Boolean.class);
+ assertTrue(verified);
+ }
+
+ @Test
+ void testEndToEndKeyExportImportAndUse() throws Exception {
+ // Step 1: Generate a key
+ KeyPair originalKeyPair = keyManager.generateKeyPair("DILITHIUM", "e2e-export-key",
+ DilithiumParameterSpec.dilithium2);
+
+ // Step 2: Export the key to PEM format
+ byte[] exportedPublicKey = keyManager.exportPublicKey(originalKeyPair, KeyLifecycleManager.KeyFormat.PEM);
+ assertNotNull(exportedPublicKey);
+
+ String pemString = new String(exportedPublicKey);
+ assertTrue(pemString.contains("-----BEGIN PUBLIC KEY-----"));
+
+ // Step 3: Import the key back
+ KeyPair importedKeyPair = keyManager.importKey(exportedPublicKey, KeyLifecycleManager.KeyFormat.PEM,
+ "DILITHIUM");
+ assertNotNull(importedKeyPair);
+ assertNotNull(importedKeyPair.getPublic());
+
+ // Step 4: Verify imported key matches original
+ assertArrayEquals(originalKeyPair.getPublic().getEncoded(), importedKeyPair.getPublic().getEncoded());
+
+ // Step 5: Use the key for signing
+ dilithiumKeyPair = originalKeyPair;
+ mockSigned.reset();
+ mockVerified.reset();
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ signTemplate.sendBody("Test with exported/imported key");
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ Boolean verified = mockVerified.getExchanges().get(0).getMessage().getHeader(PQCConstants.VERIFY, Boolean.class);
+ assertTrue(verified);
+
+ // Cleanup
+ keyManager.deleteKey("e2e-export-key");
+ }
+
+ @Test
+ void testEndToEndMultipleSignaturesWithMetadataTracking() throws Exception {
+ // Step 1: Generate a key
+ dilithiumKeyPair = keyManager.generateKeyPair("DILITHIUM", "e2e-multi-sig-key",
+ DilithiumParameterSpec.dilithium2);
+
+ KeyMetadata metadata = keyManager.getKeyMetadata("e2e-multi-sig-key");
+ assertEquals(0, metadata.getUsageCount());
+
+ // Step 2: Sign multiple messages
+ for (int i = 1; i <= 5; i++) {
+ mockSigned.reset();
+ mockVerified.reset();
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ signTemplate.sendBody("Message #" + i);
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ // Update metadata
+ metadata.updateLastUsed();
+ keyManager.updateKeyMetadata("e2e-multi-sig-key", metadata);
+ }
+
+ // Step 3: Verify all signatures were tracked
+ KeyMetadata finalMetadata = keyManager.getKeyMetadata("e2e-multi-sig-key");
+ assertEquals(5, finalMetadata.getUsageCount());
+ assertNotNull(finalMetadata.getLastUsedAt());
+
+ // Cleanup
+ keyManager.deleteKey("e2e-multi-sig-key");
+ }
+
+ @Test
+ void testEndToEndKeyExpirationPreventsUsage() throws Exception {
+ // Step 1: Generate a key
+ dilithiumKeyPair = keyManager.generateKeyPair("DILITHIUM", "e2e-expire-key", DilithiumParameterSpec.dilithium2);
+
+ // Step 2: Use the key successfully
+ mockSigned.reset();
+ mockSigned.expectedMessageCount(1);
+
+ signTemplate.sendBody("Message before expiration");
+ mockSigned.assertIsSatisfied();
+
+ // Step 3: Expire the key
+ keyManager.expireKey("e2e-expire-key");
+
+ // Verify key is expired
+ KeyMetadata metadata = keyManager.getKeyMetadata("e2e-expire-key");
+ assertEquals(KeyMetadata.KeyStatus.EXPIRED, metadata.getStatus());
+ assertTrue(metadata.isExpired());
+
+ // Step 4: Key can still technically be used (enforcement would be at application level)
+ // But metadata clearly shows it's expired
+ assertTrue(metadata.isExpired());
+
+ // Cleanup
+ keyManager.deleteKey("e2e-expire-key");
+ }
+
+ @Test
+ void testEndToEndCompleteLifecycle() throws Exception {
+ String keyId = "e2e-complete-lifecycle-key";
+
+ // Phase 1: Creation
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", keyId, DilithiumParameterSpec.dilithium2);
+ assertNotNull(keyPair);
+
+ KeyMetadata metadata = keyManager.getKeyMetadata(keyId);
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+
+ // Phase 2: Active use
+ dilithiumKeyPair = keyPair;
+ for (int i = 0; i < 3; i++) {
+ mockSigned.reset();
+ mockVerified.reset();
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ signTemplate.sendBody("Lifecycle test message " + i);
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ metadata.updateLastUsed();
+ keyManager.updateKeyMetadata(keyId, metadata);
+ }
+
+ // Verify usage tracking
+ metadata = keyManager.getKeyMetadata(keyId);
+ assertEquals(3, metadata.getUsageCount());
+
+ // Phase 3: Export for backup
+ byte[] backup = keyManager.exportKey(keyPair, KeyLifecycleManager.KeyFormat.PEM, false);
+ assertNotNull(backup);
+ assertTrue(backup.length > 0);
+
+ // Phase 4: Rotation to new key
+ String newKeyId = keyId + "-rotated";
+ KeyPair rotatedKey = keyManager.rotateKey(keyId, newKeyId, "DILITHIUM");
+ assertNotNull(rotatedKey);
+
+ // Verify old key deprecated
+ KeyMetadata oldMetadata = keyManager.getKeyMetadata(keyId);
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, oldMetadata.getStatus());
+
+ // Phase 5: Eventual deletion
+ keyManager.deleteKey(keyId);
+ assertNull(keyManager.getKeyMetadata(keyId));
+
+ // New key still works
+ dilithiumKeyPair = rotatedKey;
+ mockSigned.reset();
+ mockVerified.reset();
+ mockSigned.expectedMessageCount(1);
+ mockVerified.expectedMessageCount(1);
+
+ signTemplate.sendBody("Message with rotated key");
+
+ mockSigned.assertIsSatisfied();
+ mockVerified.assertIsSatisfied();
+
+ // Cleanup
+ keyManager.deleteKey(newKeyId);
+ }
+
+ @BindToRegistry("KeyLifecycleManager")
+ public KeyLifecycleManager getKeyLifecycleManager() {
+ return keyManager;
+ }
+
+ @BindToRegistry("Keypair")
+ public KeyPair getKeyPair() {
+ return dilithiumKeyPair;
+ }
+
+ @BindToRegistry("Signer")
+ public Signature getSigner() throws Exception {
+ return Signature.getInstance(PQCSignatureAlgorithms.DILITHIUM.getAlgorithm(),
+ PQCSignatureAlgorithms.DILITHIUM.getBcProvider());
+ }
+
+ @BindToRegistry("KeyGenerator")
+ public javax.crypto.KeyGenerator getKeyGenerator() throws Exception {
+ return javax.crypto.KeyGenerator.getInstance(PQCKeyEncapsulationAlgorithms.NTRU.getAlgorithm(),
+ PQCKeyEncapsulationAlgorithms.NTRU.getBcProvider());
+ }
+}
diff --git a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleIntegrationTest.java b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleIntegrationTest.java
new file mode 100644
index 000000000000..a39c9e1f1bd3
--- /dev/null
+++ b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleIntegrationTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.Security;
+import java.util.List;
+
+import org.apache.camel.BindToRegistry;
+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.junit5.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.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests for PQC lifecycle operations through Camel routes.
+ */
+public class PQCKeyLifecycleIntegrationTest extends CamelTestSupport {
+
+ @TempDir
+ Path tempDir;
+
+ @EndpointInject("mock:result")
+ protected MockEndpoint mockResult;
+
+ @Produce("direct:generateKey")
+ protected ProducerTemplate generateKeyTemplate;
+
+ @Produce("direct:exportKey")
+ protected ProducerTemplate exportKeyTemplate;
+
+ @Produce("direct:getMetadata")
+ protected ProducerTemplate getMetadataTemplate;
+
+ @Produce("direct:listKeys")
+ protected ProducerTemplate listKeysTemplate;
+
+ @Produce("direct:rotateKey")
+ protected ProducerTemplate rotateKeyTemplate;
+
+ @Produce("direct:expireKey")
+ protected ProducerTemplate expireKeyTemplate;
+
+ @Produce("direct:revokeKey")
+ protected ProducerTemplate revokeKeyTemplate;
+
+ private KeyLifecycleManager keyManager;
+
+ static {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastlePQCProvider());
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:generateKey")
+ .setHeader(PQCConstants.ALGORITHM, constant("DILITHIUM"))
+ .setHeader(PQCConstants.KEY_ID, body())
+ .to("pqc:lifecycle?operation=generateKeyPair")
+ .to("mock:result");
+
+ from("direct:exportKey")
+ .setHeader(PQCConstants.KEY_FORMAT, constant("PEM"))
+ .setHeader(PQCConstants.INCLUDE_PRIVATE, constant(false))
+ .to("pqc:lifecycle?operation=exportKey")
+ .to("mock:result");
+
+ from("direct:getMetadata")
+ .setHeader(PQCConstants.KEY_ID, body())
+ .to("pqc:lifecycle?operation=getKeyMetadata")
+ .to("mock:result");
+
+ from("direct:listKeys")
+ .to("pqc:lifecycle?operation=listKeys")
+ .to("mock:result");
+
+ from("direct:rotateKey")
+ .setHeader(PQCConstants.KEY_ID, constant("old-key"))
+ .setHeader(PQCConstants.ALGORITHM, constant("DILITHIUM"))
+ .to("pqc:lifecycle?operation=rotateKey")
+ .to("mock:result");
+
+ from("direct:expireKey")
+ .setHeader(PQCConstants.KEY_ID, body())
+ .to("pqc:lifecycle?operation=expireKey")
+ .to("mock:result");
+
+ from("direct:revokeKey")
+ .setHeader(PQCConstants.KEY_ID, constant("revoke-key"))
+ .setHeader(PQCConstants.REVOCATION_REASON, body())
+ .to("pqc:lifecycle?operation=revokeKey")
+ .to("mock:result");
+ }
+ };
+ }
+
+ @BeforeAll
+ public 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
+ 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
+ }
+ }
+ }
+ }
+
+ @Test
+ void testGenerateKeyThroughRoute() throws Exception {
+ // First manually generate a key to test the operation
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "route-test-key", DilithiumParameterSpec.dilithium2);
+ assertNotNull(keyPair);
+
+ // Verify metadata
+ KeyMetadata metadata = keyManager.getKeyMetadata("route-test-key");
+ assertNotNull(metadata);
+ assertEquals("route-test-key", metadata.getKeyId());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+ }
+
+ @Test
+ void testExportKeyThroughManager() throws Exception {
+ // Generate a key first
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "export-route-key", DilithiumParameterSpec.dilithium2);
+
+ // Export the key
+ byte[] exportedKey = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.PEM);
+
+ assertNotNull(exportedKey);
+ String pemString = new String(exportedKey);
+ assertTrue(pemString.contains("-----BEGIN PUBLIC KEY-----"));
+ assertTrue(pemString.contains("-----END PUBLIC KEY-----"));
+ }
+
+ @Test
+ void testGetMetadataThroughManager() throws Exception {
+ // Generate a key
+ keyManager.generateKeyPair("DILITHIUM", "metadata-route-key", DilithiumParameterSpec.dilithium2);
+
+ // Get metadata
+ KeyMetadata metadata = keyManager.getKeyMetadata("metadata-route-key");
+
+ assertNotNull(metadata);
+ assertEquals("metadata-route-key", metadata.getKeyId());
+ assertEquals("DILITHIUM", metadata.getAlgorithm());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+ }
+
+ @Test
+ void testListKeysThroughManager() throws Exception {
+ // Generate multiple keys
+ keyManager.generateKeyPair("DILITHIUM", "list-key-1", DilithiumParameterSpec.dilithium2);
+ keyManager.generateKeyPair("FALCON", "list-key-2");
+ keyManager.generateKeyPair("DILITHIUM", "list-key-3", DilithiumParameterSpec.dilithium3);
+
+ // List all keys
+ List<KeyMetadata> keys = keyManager.listKeys();
+
+ assertNotNull(keys);
+ assertEquals(3, keys.size());
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("list-key-1")));
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("list-key-2")));
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("list-key-3")));
+ }
+
+ @Test
+ void testKeyRotationThroughManager() throws Exception {
+ // Generate initial key
+ keyManager.generateKeyPair("DILITHIUM", "rotate-old-key", DilithiumParameterSpec.dilithium2);
+
+ // Rotate the key
+ KeyPair newKeyPair = keyManager.rotateKey("rotate-old-key", "rotate-new-key", "DILITHIUM");
+
+ assertNotNull(newKeyPair);
+
+ // Verify old key is deprecated
+ KeyMetadata oldMetadata = keyManager.getKeyMetadata("rotate-old-key");
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, oldMetadata.getStatus());
+
+ // Verify new key is active
+ KeyMetadata newMetadata = keyManager.getKeyMetadata("rotate-new-key");
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, newMetadata.getStatus());
+ }
+
+ @Test
+ void testExpireKeyThroughManager() throws Exception {
+ // Generate a key
+ keyManager.generateKeyPair("DILITHIUM", "expire-route-key", DilithiumParameterSpec.dilithium2);
+
+ // Expire the key
+ keyManager.expireKey("expire-route-key");
+
+ // Verify status
+ KeyMetadata metadata = keyManager.getKeyMetadata("expire-route-key");
+ assertEquals(KeyMetadata.KeyStatus.EXPIRED, metadata.getStatus());
+ assertTrue(metadata.isExpired());
+ }
+
+ @Test
+ void testRevokeKeyThroughManager() throws Exception {
+ // Generate a key
+ keyManager.generateKeyPair("DILITHIUM", "revoke-route-key", DilithiumParameterSpec.dilithium2);
+
+ // Revoke the key
+ String reason = "Security breach detected";
+ keyManager.revokeKey("revoke-route-key", reason);
+
+ // Verify status
+ KeyMetadata metadata = keyManager.getKeyMetadata("revoke-route-key");
+ assertEquals(KeyMetadata.KeyStatus.REVOKED, metadata.getStatus());
+ assertTrue(metadata.getDescription().contains(reason));
+ }
+
+ @Test
+ void testKeyPersistenceAcrossManagers() throws Exception {
+ // Generate key with first manager
+ KeyPair originalKeyPair = keyManager.generateKeyPair("DILITHIUM", "persistence-key",
+ DilithiumParameterSpec.dilithium2);
+
+ // Create new manager instance
+ KeyLifecycleManager newManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Retrieve key with new manager
+ KeyPair retrievedKeyPair = newManager.getKey("persistence-key");
+
+ assertNotNull(retrievedKeyPair);
+ assertArrayEquals(originalKeyPair.getPublic().getEncoded(), retrievedKeyPair.getPublic().getEncoded());
+ assertArrayEquals(originalKeyPair.getPrivate().getEncoded(), retrievedKeyPair.getPrivate().getEncoded());
+ }
+
+ @Test
+ void testKeyMetadataUpdates() throws Exception {
+ // Generate a key
+ keyManager.generateKeyPair("DILITHIUM", "update-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Get and update metadata
+ KeyMetadata metadata = keyManager.getKeyMetadata("update-test-key");
+ metadata.setDescription("Updated description");
+ metadata.updateLastUsed();
+ metadata.updateLastUsed();
+ metadata.updateLastUsed();
+
+ keyManager.updateKeyMetadata("update-test-key", metadata);
+
+ // Retrieve and verify
+ KeyMetadata updated = keyManager.getKeyMetadata("update-test-key");
+ assertEquals("Updated description", updated.getDescription());
+ assertEquals(3, updated.getUsageCount());
+ }
+
+ @Test
+ void testKeyDeletion() throws Exception {
+ // Generate a key
+ keyManager.generateKeyPair("DILITHIUM", "delete-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Verify it exists
+ assertNotNull(keyManager.getKeyMetadata("delete-test-key"));
+
+ // Delete it
+ keyManager.deleteKey("delete-test-key");
+
+ // Verify it's gone
+ assertNull(keyManager.getKeyMetadata("delete-test-key"));
+ }
+
+ @BindToRegistry("KeyLifecycleManager")
+ public KeyLifecycleManager getKeyLifecycleManager() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+ return keyManager;
+ }
+}
diff --git a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleTest.java b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleTest.java
new file mode 100644
index 000000000000..cf04f5695fb9
--- /dev/null
+++ b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCKeyLifecycleTest.java
@@ -0,0 +1,369 @@
+/*
+ * 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.Security;
+import java.time.Duration;
+import java.util.List;
+
+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.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.*;
+
+public class PQCKeyLifecycleTest {
+
+ @TempDir
+ Path tempDir;
+
+ private KeyLifecycleManager keyManager;
+
+ @BeforeAll
+ public 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
+ public void cleanup() throws Exception {
+ if (keyManager != null) {
+ List<KeyMetadata> keys = keyManager.listKeys();
+ for (KeyMetadata metadata : keys) {
+ keyManager.deleteKey(metadata.getKeyId());
+ }
+ }
+ }
+
+ @Test
+ void testGenerateKeyPair() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate a Dilithium key pair
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "test-dilithium-key", DilithiumParameterSpec.dilithium2);
+
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPublic());
+ assertNotNull(keyPair.getPrivate());
+
+ // Verify metadata was created
+ KeyMetadata metadata = keyManager.getKeyMetadata("test-dilithium-key");
+ assertNotNull(metadata);
+ assertEquals("test-dilithium-key", metadata.getKeyId());
+ assertEquals("DILITHIUM", metadata.getAlgorithm());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+ assertEquals(0, metadata.getUsageCount());
+ }
+
+ @Test
+ void testGenerateFalconKeyPair() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate a Falcon key pair (uses default spec)
+ KeyPair keyPair = keyManager.generateKeyPair("FALCON", "test-falcon-key");
+
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPublic());
+ assertNotNull(keyPair.getPrivate());
+
+ // Verify key can be retrieved
+ KeyPair retrieved = keyManager.getKey("test-falcon-key");
+ assertNotNull(retrieved);
+ assertArrayEquals(keyPair.getPublic().getEncoded(), retrieved.getPublic().getEncoded());
+ }
+
+ @Test
+ void testExportKeyToPEM() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "export-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Export public key to PEM
+ byte[] publicKeyPEM = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.PEM);
+ assertNotNull(publicKeyPEM);
+
+ String pemString = new String(publicKeyPEM);
+ assertTrue(pemString.contains("-----BEGIN PUBLIC KEY-----"));
+ assertTrue(pemString.contains("-----END PUBLIC KEY-----"));
+
+ // Export both keys to PEM
+ byte[] keyPairPEM = keyManager.exportKey(keyPair, KeyLifecycleManager.KeyFormat.PEM, true);
+ assertNotNull(keyPairPEM);
+
+ String keyPairPEMString = new String(keyPairPEM);
+ assertTrue(keyPairPEMString.contains("-----BEGIN PUBLIC KEY-----"));
+ assertTrue(keyPairPEMString.contains("-----BEGIN PRIVATE KEY-----"));
+ }
+
+ @Test
+ void testExportKeyToDER() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "der-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Export to DER format
+ byte[] publicKeyDER = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.DER);
+ assertNotNull(publicKeyDER);
+ assertTrue(publicKeyDER.length > 0);
+
+ // DER should not contain PEM headers
+ String derString = new String(publicKeyDER);
+ assertFalse(derString.contains("-----BEGIN"));
+ }
+
+ @Test
+ void testImportKey() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate and export a key
+ KeyPair originalKeyPair = keyManager.generateKeyPair("DILITHIUM", "import-test-key",
+ DilithiumParameterSpec.dilithium2);
+ byte[] exportedKey = keyManager.exportPublicKey(originalKeyPair, KeyLifecycleManager.KeyFormat.PEM);
+
+ // Import the key
+ KeyPair importedKeyPair = keyManager.importKey(exportedKey, KeyLifecycleManager.KeyFormat.PEM, "DILITHIUM");
+
+ assertNotNull(importedKeyPair);
+ assertNotNull(importedKeyPair.getPublic());
+
+ // Compare public keys
+ assertArrayEquals(originalKeyPair.getPublic().getEncoded(), importedKeyPair.getPublic().getEncoded());
+ }
+
+ @Test
+ void testKeyRotation() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate initial key
+ KeyPair oldKeyPair = keyManager.generateKeyPair("DILITHIUM", "old-key", DilithiumParameterSpec.dilithium2);
+ assertNotNull(oldKeyPair);
+
+ // Rotate the key
+ KeyPair newKeyPair = keyManager.rotateKey("old-key", "new-key", "DILITHIUM");
+ assertNotNull(newKeyPair);
+
+ // Verify old key is deprecated
+ KeyMetadata oldMetadata = keyManager.getKeyMetadata("old-key");
+ assertEquals(KeyMetadata.KeyStatus.DEPRECATED, oldMetadata.getStatus());
+
+ // Verify new key is active
+ KeyMetadata newMetadata = keyManager.getKeyMetadata("new-key");
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, newMetadata.getStatus());
+
+ // Keys should be different
+ assertFalse(java.util.Arrays.equals(oldKeyPair.getPublic().getEncoded(), newKeyPair.getPublic().getEncoded()));
+ }
+
+ @Test
+ void testKeyMetadataTracking() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "metadata-test-key", DilithiumParameterSpec.dilithium2);
+ KeyMetadata metadata = keyManager.getKeyMetadata("metadata-test-key");
+
+ // Initial state
+ assertEquals("metadata-test-key", metadata.getKeyId());
+ assertEquals("DILITHIUM", metadata.getAlgorithm());
+ assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+ assertEquals(0, metadata.getUsageCount());
+ assertNotNull(metadata.getCreatedAt());
+ assertNotNull(metadata.getLastUsedAt());
+
+ // Update usage
+ metadata.updateLastUsed();
+ metadata.updateLastUsed();
+ metadata.updateLastUsed();
+ keyManager.updateKeyMetadata("metadata-test-key", metadata);
+
+ // Retrieve and verify
+ KeyMetadata updatedMetadata = keyManager.getKeyMetadata("metadata-test-key");
+ assertEquals(3, updatedMetadata.getUsageCount());
+ }
+
+ @Test
+ void testExpireKey() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "expire-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Expire the key
+ keyManager.expireKey("expire-test-key");
+
+ // Verify status
+ KeyMetadata metadata = keyManager.getKeyMetadata("expire-test-key");
+ assertEquals(KeyMetadata.KeyStatus.EXPIRED, metadata.getStatus());
+ assertTrue(metadata.isExpired());
+ }
+
+ @Test
+ void testRevokeKey() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "revoke-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Revoke the key
+ String reason = "Compromised key";
+ keyManager.revokeKey("revoke-test-key", reason);
+
+ // Verify status
+ KeyMetadata metadata = keyManager.getKeyMetadata("revoke-test-key");
+ assertEquals(KeyMetadata.KeyStatus.REVOKED, metadata.getStatus());
+ assertTrue(metadata.getDescription().contains(reason));
+ }
+
+ @Test
+ void testListKeys() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate multiple keys
+ keyManager.generateKeyPair("DILITHIUM", "key1", DilithiumParameterSpec.dilithium2);
+ keyManager.generateKeyPair("FALCON", "key2");
+ keyManager.generateKeyPair("DILITHIUM", "key3", DilithiumParameterSpec.dilithium3);
+
+ // List all keys
+ List<KeyMetadata> keys = keyManager.listKeys();
+ assertEquals(3, keys.size());
+
+ // Verify all keys are present
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("key1")));
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("key2")));
+ assertTrue(keys.stream().anyMatch(k -> k.getKeyId().equals("key3")));
+ }
+
+ @Test
+ void testDeleteKey() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "delete-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Verify key exists
+ assertNotNull(keyManager.getKeyMetadata("delete-test-key"));
+
+ // Delete the key
+ keyManager.deleteKey("delete-test-key");
+
+ // Verify key is gone
+ assertNull(keyManager.getKeyMetadata("delete-test-key"));
+ }
+
+ @Test
+ void testNeedsRotationByAge() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "age-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Should not need rotation for young key
+ assertFalse(keyManager.needsRotation("age-test-key", Duration.ofDays(365), 0));
+
+ // Should need rotation if max age is very short (simulated)
+ // This will be false because the key was just created
+ assertFalse(keyManager.needsRotation("age-test-key", Duration.ofDays(0), 0));
+ }
+
+ @Test
+ void testNeedsRotationByUsage() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "usage-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Update usage count
+ KeyMetadata metadata = keyManager.getKeyMetadata("usage-test-key");
+ for (int i = 0; i < 100; i++) {
+ metadata.updateLastUsed();
+ }
+ keyManager.updateKeyMetadata("usage-test-key", metadata);
+
+ // Should need rotation if usage exceeds limit
+ assertTrue(keyManager.needsRotation("usage-test-key", null, 50));
+
+ // Should not need rotation if usage is within limit
+ assertFalse(keyManager.needsRotation("usage-test-key", null, 150));
+ }
+
+ @Test
+ void testKeyPersistence() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Generate and store a key
+ KeyPair originalKeyPair = keyManager.generateKeyPair("DILITHIUM", "persistence-test-key",
+ DilithiumParameterSpec.dilithium2);
+
+ // Create a new manager instance (simulating restart)
+ KeyLifecycleManager newManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ // Retrieve the key
+ KeyPair retrievedKeyPair = newManager.getKey("persistence-test-key");
+ assertNotNull(retrievedKeyPair);
+
+ // Verify keys match
+ assertArrayEquals(originalKeyPair.getPublic().getEncoded(), retrievedKeyPair.getPublic().getEncoded());
+ assertArrayEquals(originalKeyPair.getPrivate().getEncoded(), retrievedKeyPair.getPrivate().getEncoded());
+
+ // Verify metadata persisted
+ KeyMetadata metadata = newManager.getKeyMetadata("persistence-test-key");
+ assertNotNull(metadata);
+ assertEquals("persistence-test-key", metadata.getKeyId());
+ assertEquals("DILITHIUM", metadata.getAlgorithm());
+ }
+
+ @Test
+ void testKeyMetadataAge() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ keyManager.generateKeyPair("DILITHIUM", "age-calculation-key", DilithiumParameterSpec.dilithium2);
+ KeyMetadata metadata = keyManager.getKeyMetadata("age-calculation-key");
+
+ // Age should be 0 days for just created key
+ long age = metadata.getAgeInDays();
+ assertEquals(0, age);
+ }
+
+ @Test
+ void testMultipleKeyFormats() throws Exception {
+ keyManager = new FileBasedKeyLifecycleManager(tempDir.toString());
+
+ KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "format-test-key", DilithiumParameterSpec.dilithium2);
+
+ // Test all export formats
+ byte[] pemPublic = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.PEM);
+ byte[] derPublic = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.DER);
+ byte[] x509Public = keyManager.exportPublicKey(keyPair, KeyLifecycleManager.KeyFormat.X509);
+
+ assertNotNull(pemPublic);
+ assertNotNull(derPublic);
+ assertNotNull(x509Public);
+
+ // PEM should be larger due to Base64 encoding
+ assertTrue(pemPublic.length > derPublic.length);
+
+ // DER and X509 should be the same for public keys
+ assertArrayEquals(derPublic, x509Public);
+ }
+}
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PQCEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PQCEndpointBuilderFactory.java
index 81ebe7de2a20..26fe192e6b6a 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PQCEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PQCEndpointBuilderFactory.java
@@ -502,6 +502,140 @@ public interface PQCEndpointBuilderFactory {
public String pQCSecretKey() {
return "CamelPQCSecretKey";
}
+ /**
+ * The remaining signatures for a stateful key.
+ *
+ * The option is a: {@code Long} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCRemainingSignatures}.
+ */
+ public String pQCRemainingSignatures() {
+ return "CamelPQCRemainingSignatures";
+ }
+ /**
+ * The key state for a stateful key.
+ *
+ * The option is a: {@code
+ * org.apache.camel.component.pqc.stateful.StatefulKeyState} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyState}.
+ */
+ public String pQCKeyState() {
+ return "CamelPQCKeyState";
+ }
+ /**
+ * The key ID for stateful key operations.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyId}.
+ */
+ public String pQCKeyId() {
+ return "CamelPQCKeyId";
+ }
+ /**
+ * The generated key pair.
+ *
+ * The option is a: {@code java.security.KeyPair} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyPair}.
+ */
+ public String pQCKeyPair() {
+ return "CamelPQCKeyPair";
+ }
+ /**
+ * The key format for import/export operations.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyFormat}.
+ */
+ public String pQCKeyFormat() {
+ return "CamelPQCKeyFormat";
+ }
+ /**
+ * The exported key data.
+ *
+ * The option is a: {@code byte[]} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCExportedKey}.
+ */
+ public String pQCExportedKey() {
+ return "CamelPQCExportedKey";
+ }
+ /**
+ * The key metadata.
+ *
+ * The option is a: {@code
+ * org.apache.camel.component.pqc.lifecycle.KeyMetadata} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyMetadata}.
+ */
+ public String pQCKeyMetadata() {
+ return "CamelPQCKeyMetadata";
+ }
+ /**
+ * List of key metadata.
+ *
+ * The option is a: {@code java.util.List} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCKeyList}.
+ */
+ public String pQCKeyList() {
+ return "CamelPQCKeyList";
+ }
+ /**
+ * The algorithm for key generation.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCAlgorithm}.
+ */
+ public String pQCAlgorithm() {
+ return "CamelPQCAlgorithm";
+ }
+ /**
+ * Include private key in export.
+ *
+ * The option is a: {@code Boolean} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCIncludePrivate}.
+ */
+ public String pQCIncludePrivate() {
+ return "CamelPQCIncludePrivate";
+ }
+ /**
+ * Revocation reason.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code PQCRevocationReason}.
+ */
+ public String pQCRevocationReason() {
+ return "CamelPQCRevocationReason";
+ }
}
static PQCEndpointBuilder endpointBuilder(String componentName, String path) {
class PQCEndpointBuilderImpl extends AbstractEndpointBuilder implements PQCEndpointBuilder, AdvancedPQCEndpointBuilder {