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 <[email protected]> AuthorDate: Wed Oct 8 14:20:38 2025 +0200 CAMEL-22512 - Camel-PQC: Add Key Lifecycle Management Signed-off-by: Andrea Cosentino <[email protected]> --- .../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 {
