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 04323a8b95c0b..c1e44735e9ae8 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 @@ -43,12 +43,13 @@ "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeyStore Parameter" }, "signatureAlgorithm": { "index": 17, "kind": "property", "displayName": "Signature Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", "SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", "SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case there is no signer, we specify an algorithm to build the KeyPair or the Signer" }, "signer": { "index": 18, "kind": "property", "displayName": "Signer", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.Signature", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Signer to be used" }, - "storeExtractedSecretKeyAsHeader": { "index": 19, "kind": "property", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, - "strictKeyLifecycle": { "index": 20, "kind": "property", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, - "symmetricKeyAlgorithm": { "index": 21, "kind": "property", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, - "symmetricKeyLength": { "index": 22, "kind": "property", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" }, - "healthCheckConsumerEnabled": { "index": 23, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, - "healthCheckProducerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } + "statefulKeyWarningThreshold": { "index": 19, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). When the remaining signatures for a stateful key (XMSS, XMSSMT, LMS\/HSS) drop below this fraction of the total capacity, a WARN log is emitted. When remaining signatures reach zero, an exception is thrown to prevent key reuse. Set to 0 to disable warnings." }, + "storeExtractedSecretKeyAsHeader": { "index": 20, "kind": "property", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, + "strictKeyLifecycle": { "index": 21, "kind": "property", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, + "symmetricKeyAlgorithm": { "index": 22, "kind": "property", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, + "symmetricKeyLength": { "index": 23, "kind": "property", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" }, + "healthCheckConsumerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, + "healthCheckProducerEnabled": { "index": 25, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } }, "headers": { "CamelPQCOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation we want to perform", "constantName": "org.apache.camel.component.pqc.PQCConstants#OPERATION" }, @@ -94,9 +95,10 @@ "keyStorePassword": { "index": 15, "kind": "parameter", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeyStore Parameter" }, "signatureAlgorithm": { "index": 16, "kind": "parameter", "displayName": "Signature Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", "SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", "SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case there is no signer, we specify an algorithm to build the KeyPair or the Signer" }, "signer": { "index": 17, "kind": "parameter", "displayName": "Signer", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.Signature", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Signer to be used" }, - "storeExtractedSecretKeyAsHeader": { "index": 18, "kind": "parameter", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, - "strictKeyLifecycle": { "index": 19, "kind": "parameter", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, - "symmetricKeyAlgorithm": { "index": 20, "kind": "parameter", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, - "symmetricKeyLength": { "index": 21, "kind": "parameter", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" } + "statefulKeyWarningThreshold": { "index": 18, "kind": "parameter", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). When the remaining signatures for a stateful key (XMSS, XMSSMT, LMS\/HSS) drop below this fraction of the total capacity, a WARN log is emitted. When remaining signatures reach zero, an exception is thrown to prevent key reuse. Set to 0 to disable warnings." }, + "storeExtractedSecretKeyAsHeader": { "index": 19, "kind": "parameter", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, + "strictKeyLifecycle": { "index": 20, "kind": "parameter", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, + "symmetricKeyAlgorithm": { "index": 21, "kind": "parameter", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, + "symmetricKeyLength": { "index": 22, "kind": "parameter", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" } } } diff --git a/components/camel-pqc/pom.xml b/components/camel-pqc/pom.xml index fcc695c27b7fc..e641acefda96a 100644 --- a/components/camel-pqc/pom.xml +++ b/components/camel-pqc/pom.xml @@ -42,6 +42,10 @@ org.apache.camel camel-support + + org.apache.camel + camel-health + org.bouncycastle bcprov-jdk18on diff --git a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java index 9044003da1827..39e46f037f723 100644 --- a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java +++ b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCComponentConfigurer.java @@ -69,6 +69,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "signaturealgorithm": case "signatureAlgorithm": getOrCreateConfiguration(target).setSignatureAlgorithm(property(camelContext, java.lang.String.class, value)); return true; case "signer": getOrCreateConfiguration(target).setSigner(property(camelContext, java.security.Signature.class, value)); return true; + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": getOrCreateConfiguration(target).setStatefulKeyWarningThreshold(property(camelContext, double.class, value)); return true; case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": getOrCreateConfiguration(target).setStoreExtractedSecretKeyAsHeader(property(camelContext, boolean.class, value)); return true; case "strictkeylifecycle": @@ -128,6 +130,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "signaturealgorithm": case "signatureAlgorithm": return java.lang.String.class; case "signer": return java.security.Signature.class; + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": return double.class; case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": return boolean.class; case "strictkeylifecycle": @@ -183,6 +187,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "signaturealgorithm": case "signatureAlgorithm": return getOrCreateConfiguration(target).getSignatureAlgorithm(); case "signer": return getOrCreateConfiguration(target).getSigner(); + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": return getOrCreateConfiguration(target).getStatefulKeyWarningThreshold(); case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": return getOrCreateConfiguration(target).isStoreExtractedSecretKeyAsHeader(); case "strictkeylifecycle": diff --git a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java index fe426f7b55ddf..97181137be258 100644 --- a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java +++ b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointConfigurer.java @@ -55,6 +55,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "signaturealgorithm": case "signatureAlgorithm": target.getConfiguration().setSignatureAlgorithm(property(camelContext, java.lang.String.class, value)); return true; case "signer": target.getConfiguration().setSigner(property(camelContext, java.security.Signature.class, value)); return true; + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": target.getConfiguration().setStatefulKeyWarningThreshold(property(camelContext, double.class, value)); return true; case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": target.getConfiguration().setStoreExtractedSecretKeyAsHeader(property(camelContext, boolean.class, value)); return true; case "strictkeylifecycle": @@ -107,6 +109,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "signaturealgorithm": case "signatureAlgorithm": return java.lang.String.class; case "signer": return java.security.Signature.class; + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": return double.class; case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": return boolean.class; case "strictkeylifecycle": @@ -155,6 +159,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "signaturealgorithm": case "signatureAlgorithm": return target.getConfiguration().getSignatureAlgorithm(); case "signer": return target.getConfiguration().getSigner(); + case "statefulkeywarningthreshold": + case "statefulKeyWarningThreshold": return target.getConfiguration().getStatefulKeyWarningThreshold(); case "storeextractedsecretkeyasheader": case "storeExtractedSecretKeyAsHeader": return target.getConfiguration().isStoreExtractedSecretKeyAsHeader(); case "strictkeylifecycle": diff --git a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java index 1f1bfe4747fe1..c151e1f446915 100644 --- a/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java +++ b/components/camel-pqc/src/generated/java/org/apache/camel/component/pqc/PQCEndpointUriFactory.java @@ -23,7 +23,7 @@ public class PQCEndpointUriFactory extends org.apache.camel.support.component.En private static final Set SECRET_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(22); + Set props = new HashSet<>(23); props.add("classicalKEMAlgorithm"); props.add("classicalKeyAgreement"); props.add("classicalKeyPair"); @@ -42,6 +42,7 @@ public class PQCEndpointUriFactory extends org.apache.camel.support.component.En props.add("operation"); props.add("signatureAlgorithm"); props.add("signer"); + props.add("statefulKeyWarningThreshold"); props.add("storeExtractedSecretKeyAsHeader"); props.add("strictKeyLifecycle"); props.add("symmetricKeyAlgorithm"); 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 04323a8b95c0b..c1e44735e9ae8 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 @@ -43,12 +43,13 @@ "keyStorePassword": { "index": 16, "kind": "property", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeyStore Parameter" }, "signatureAlgorithm": { "index": 17, "kind": "property", "displayName": "Signature Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", "SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", "SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case there is no signer, we specify an algorithm to build the KeyPair or the Signer" }, "signer": { "index": 18, "kind": "property", "displayName": "Signer", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.Signature", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Signer to be used" }, - "storeExtractedSecretKeyAsHeader": { "index": 19, "kind": "property", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, - "strictKeyLifecycle": { "index": 20, "kind": "property", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, - "symmetricKeyAlgorithm": { "index": 21, "kind": "property", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, - "symmetricKeyLength": { "index": 22, "kind": "property", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" }, - "healthCheckConsumerEnabled": { "index": 23, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, - "healthCheckProducerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } + "statefulKeyWarningThreshold": { "index": 19, "kind": "property", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). When the remaining signatures for a stateful key (XMSS, XMSSMT, LMS\/HSS) drop below this fraction of the total capacity, a WARN log is emitted. When remaining signatures reach zero, an exception is thrown to prevent key reuse. Set to 0 to disable warnings." }, + "storeExtractedSecretKeyAsHeader": { "index": 20, "kind": "property", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, + "strictKeyLifecycle": { "index": 21, "kind": "property", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, + "symmetricKeyAlgorithm": { "index": 22, "kind": "property", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, + "symmetricKeyLength": { "index": 23, "kind": "property", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" }, + "healthCheckConsumerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, + "healthCheckProducerEnabled": { "index": 25, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } }, "headers": { "CamelPQCOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation we want to perform", "constantName": "org.apache.camel.component.pqc.PQCConstants#OPERATION" }, @@ -94,9 +95,10 @@ "keyStorePassword": { "index": 15, "kind": "parameter", "displayName": "Key Store Password", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The KeyStore password to use in combination with KeyStore Parameter" }, "signatureAlgorithm": { "index": 16, "kind": "parameter", "displayName": "Signature Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "MLDSA", "SLHDSA", "LMS", "HSS", "XMSS", "XMSSMT", "DILITHIUM", "FALCON", "PICNIC", "SNOVA", "MAYO", "SPHINCSPLUS" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case there is no signer, we specify an algorithm to build the KeyPair or the Signer" }, "signer": { "index": 17, "kind": "parameter", "displayName": "Signer", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.security.Signature", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The Signer to be used" }, - "storeExtractedSecretKeyAsHeader": { "index": 18, "kind": "parameter", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, - "strictKeyLifecycle": { "index": 19, "kind": "parameter", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, - "symmetricKeyAlgorithm": { "index": 20, "kind": "parameter", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, - "symmetricKeyLength": { "index": 21, "kind": "parameter", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" } + "statefulKeyWarningThreshold": { "index": 18, "kind": "parameter", "displayName": "Stateful Key Warning Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 0.1, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). When the remaining signatures for a stateful key (XMSS, XMSSMT, LMS\/HSS) drop below this fraction of the total capacity, a WARN log is emitted. When remaining signatures reach zero, an exception is thrown to prevent key reuse. Set to 0 to disable warnings." }, + "storeExtractedSecretKeyAsHeader": { "index": 19, "kind": "parameter", "displayName": "Store Extracted Secret Key As Header", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In the context of extractSecretKeyFromEncapsulation operation, this option define if we want to have the key set as header" }, + "strictKeyLifecycle": { "index": 20, "kind": "parameter", "displayName": "Strict Key Lifecycle", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "Whether to enforce key status checks before cryptographic operations. When enabled, REVOKED keys are rejected for all operations, EXPIRED keys are rejected for signing\/encapsulation but allowed for verification\/extraction, and DEPRECATED keys produce a warning but still function. Requires a KeyLifecycleManager and a CamelPQCKeyId header to be set." }, + "symmetricKeyAlgorithm": { "index": 21, "kind": "parameter", "displayName": "Symmetric Key Algorithm", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "AES", "ARIA", "RC2", "RC5", "CAMELLIA", "CAST5", "CAST6", "CHACHA7539", "DSTU7624", "GOST28147", "GOST3412_2015", "GRAIN128", "HC128", "HC256", "SALSA20", "SEED", "SM4", "DESEDE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "In case we are using KEM operations, we need a Symmetric algorithm to be defined for the flow to work." }, + "symmetricKeyLength": { "index": 22, "kind": "parameter", "displayName": "Symmetric Key Length", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 128, "configurationClass": "org.apache.camel.component.pqc.PQCConfiguration", "configurationField": "configuration", "description": "The required length of the symmetric key used" } } } diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java index 938e2f7068660..bfac40af1cb2b 100644 --- a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java +++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCConfiguration.java @@ -114,6 +114,14 @@ public class PQCConfiguration implements Cloneable { @Metadata(label = "advanced") private boolean strictKeyLifecycle = true; + @UriParam(defaultValue = "0.1", + description = "The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). " + + "When the remaining signatures for a stateful key (XMSS, XMSSMT, LMS/HSS) drop below this " + + "fraction of the total capacity, a WARN log is emitted. When remaining signatures reach zero, " + + "an exception is thrown to prevent key reuse. Set to 0 to disable warnings.") + @Metadata(label = "advanced") + private double statefulKeyWarningThreshold = 0.1; + public PQCOperations getOperation() { return operation; } @@ -339,6 +347,20 @@ public void setStrictKeyLifecycle(boolean strictKeyLifecycle) { this.strictKeyLifecycle = strictKeyLifecycle; } + public double getStatefulKeyWarningThreshold() { + return statefulKeyWarningThreshold; + } + + /** + * The warning threshold for stateful key exhaustion as a fraction of total signatures (0.0 to 1.0). When the + * remaining signatures for a stateful key (XMSS, XMSSMT, LMS/HSS) drop below this fraction of the total capacity, a + * WARN log is emitted. When remaining signatures reach zero, an exception is thrown to prevent key reuse. Set to 0 + * to disable warnings. + */ + public void setStatefulKeyWarningThreshold(double statefulKeyWarningThreshold) { + this.statefulKeyWarningThreshold = statefulKeyWarningThreshold; + } + // ************************************************* // // ************************************************* diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java index d3e7ef4c029af..ed169ae65e7ff 100644 --- a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java +++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java @@ -36,6 +36,9 @@ import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager; import org.apache.camel.component.pqc.lifecycle.KeyMetadata; import org.apache.camel.component.pqc.stateful.StatefulKeyState; +import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheckHelper; +import org.apache.camel.health.WritableHealthCheckRepository; import org.apache.camel.support.DefaultProducer; import org.apache.camel.util.ObjectHelper; import org.bouncycastle.jcajce.SecretKeyWithEncapsulation; @@ -108,6 +111,10 @@ public class PQCProducer extends DefaultProducer { private KeyAgreement classicalKeyAgreement; private KeyPair classicalKeyPair; + // Health check fields + private HealthCheck producerHealthCheck; + private WritableHealthCheckRepository healthCheckRepository; + public PQCProducer(Endpoint endpoint) { super(endpoint); } @@ -381,10 +388,34 @@ protected void doStart() throws Exception { // Initialize classical key pair classicalKeyPair = getConfiguration().getClassicalKeyPair(); } + + // Register health check for stateful key monitoring + healthCheckRepository = HealthCheckHelper.getHealthCheckRepository( + getEndpoint().getCamelContext(), + "producers", + WritableHealthCheckRepository.class); + + if (ObjectHelper.isNotEmpty(healthCheckRepository)) { + String id = getEndpoint().getId(); + producerHealthCheck = new PQCStatefulKeyHealthCheck(getEndpoint(), id); + producerHealthCheck.setEnabled(getEndpoint().getComponent().isHealthCheckProducerEnabled()); + healthCheckRepository.addHealthCheck(producerHealthCheck); + } + } + + @Override + protected void doStop() throws Exception { + if (ObjectHelper.isNotEmpty(healthCheckRepository) && ObjectHelper.isNotEmpty(producerHealthCheck)) { + healthCheckRepository.removeHealthCheck(producerHealthCheck); + producerHealthCheck = null; + } + super.doStop(); } private void signature(Exchange exchange) - throws InvalidPayloadException, InvalidKeyException, SignatureException { + throws Exception { + checkStatefulKeyBeforeSign(); + String payload = exchange.getMessage().getMandatoryBody(String.class); signer.initSign(keyPair.getPrivate()); @@ -392,6 +423,8 @@ private void signature(Exchange exchange) byte[] signature = signer.sign(); exchange.getMessage().setHeader(PQCConstants.SIGNATURE, signature); + + persistStatefulKeyStateAfterSign(exchange); } private void verification(Exchange exchange) @@ -812,6 +845,112 @@ private void statefulDeleteKeyState(Exchange exchange) throws Exception { } } + /** + * Checks whether the current key is a stateful signature key (XMSS, XMSSMT, LMS/HSS) and if so, validates that it + * has remaining signatures available. Logs a warning when remaining signatures fall below the configured threshold. + * + * @throws IllegalStateException if the key has zero remaining signatures + */ + private void checkStatefulKeyBeforeSign() { + if (keyPair == null || keyPair.getPrivate() == null) { + return; + } + + PrivateKey privateKey = keyPair.getPrivate(); + long remaining = getStatefulKeyRemaining(privateKey); + if (remaining < 0) { + // Not a stateful key + return; + } + + if (remaining <= 0) { + throw new IllegalStateException( + "Stateful key (" + privateKey.getAlgorithm() + ") is exhausted with 0 remaining signatures. " + + "The key must not be reused — generate a new key pair."); + } + + double threshold = getConfiguration().getStatefulKeyWarningThreshold(); + if (threshold > 0) { + long totalCapacity = getStatefulKeyIndex(privateKey) + remaining; + if (totalCapacity > 0) { + double fractionRemaining = (double) remaining / totalCapacity; + if (fractionRemaining <= threshold) { + LOG.warn( + "Stateful key ({}) is approaching exhaustion: {} signatures remaining out of {} total ({} remaining). " + + "Consider generating a new key pair.", + privateKey.getAlgorithm(), remaining, totalCapacity, + String.format("%.1f%%", fractionRemaining * 100)); + } + } + } + } + + /** + * Persists stateful key state after a signing operation through the KeyLifecycleManager, if configured. This + * ensures the key index is tracked across restarts. + */ + private void persistStatefulKeyStateAfterSign(Exchange exchange) throws Exception { + if (keyPair == null || keyPair.getPrivate() == null) { + return; + } + + PrivateKey privateKey = keyPair.getPrivate(); + long remaining = getStatefulKeyRemaining(privateKey); + if (remaining < 0) { + // Not a stateful key + return; + } + + KeyLifecycleManager klm = getConfiguration().getKeyLifecycleManager(); + if (klm == null) { + return; + } + + String keyId = exchange.getMessage().getHeader(PQCConstants.KEY_ID, String.class); + if (ObjectHelper.isEmpty(keyId)) { + return; + } + + // Update metadata with current usage + KeyMetadata metadata = klm.getKeyMetadata(keyId); + if (metadata != null) { + metadata.updateLastUsed(); + klm.updateKeyMetadata(keyId, metadata); + } + + // Persist the updated key (with new index) so state survives restarts + klm.storeKey(keyId, keyPair, metadata); + } + + /** + * Returns the remaining signatures for a stateful private key, or -1 if the key is not stateful. + */ + private long getStatefulKeyRemaining(PrivateKey privateKey) { + if (privateKey instanceof XMSSPrivateKey) { + return ((XMSSPrivateKey) privateKey).getUsagesRemaining(); + } else if (privateKey instanceof XMSSMTPrivateKey) { + return ((XMSSMTPrivateKey) privateKey).getUsagesRemaining(); + } else if (privateKey instanceof LMSPrivateKey) { + return ((LMSPrivateKey) privateKey).getUsagesRemaining(); + } + return -1; + } + + /** + * Returns the current index (number of signatures already produced) for a stateful private key, or 0 if the key is + * not stateful. + */ + private long getStatefulKeyIndex(PrivateKey privateKey) { + if (privateKey instanceof XMSSPrivateKey) { + return ((XMSSPrivateKey) privateKey).getIndex(); + } else if (privateKey instanceof XMSSMTPrivateKey) { + return ((XMSSMTPrivateKey) privateKey).getIndex(); + } else if (privateKey instanceof LMSPrivateKey) { + return ((LMSPrivateKey) privateKey).getIndex(); + } + return 0; + } + // ========== Configuration Validation ========== /** diff --git a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCStatefulKeyHealthCheck.java b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCStatefulKeyHealthCheck.java new file mode 100644 index 0000000000000..23acc4e43a6c4 --- /dev/null +++ b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCStatefulKeyHealthCheck.java @@ -0,0 +1,103 @@ +/* + * 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.security.KeyPair; +import java.security.PrivateKey; +import java.util.Map; + +import org.apache.camel.health.HealthCheckResultBuilder; +import org.apache.camel.impl.health.AbstractHealthCheck; +import org.bouncycastle.pqc.jcajce.interfaces.LMSPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.XMSSMTPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey; + +/** + * Health check that reports the state of stateful PQC signature keys (XMSS, XMSSMT, LMS/HSS). These hash-based + * signature schemes have a finite number of signatures. This health check reports DOWN when a key is exhausted and + * includes remaining signature capacity as a detail. + */ +public class PQCStatefulKeyHealthCheck extends AbstractHealthCheck { + + private final PQCEndpoint endpoint; + + public PQCStatefulKeyHealthCheck(PQCEndpoint endpoint, String clientId) { + super("camel", "producer:pqc-stateful-key-" + clientId); + this.endpoint = endpoint; + } + + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + PQCConfiguration configuration = endpoint.getConfiguration(); + KeyPair keyPair = configuration.getKeyPair(); + + if (keyPair == null || keyPair.getPrivate() == null) { + builder.detail("stateful_key", false); + builder.up(); + return; + } + + PrivateKey privateKey = keyPair.getPrivate(); + long remaining = -1; + long index = 0; + String algorithm = privateKey.getAlgorithm(); + + if (privateKey instanceof XMSSPrivateKey) { + XMSSPrivateKey xmssKey = (XMSSPrivateKey) privateKey; + remaining = xmssKey.getUsagesRemaining(); + index = xmssKey.getIndex(); + } else if (privateKey instanceof XMSSMTPrivateKey) { + XMSSMTPrivateKey xmssmtKey = (XMSSMTPrivateKey) privateKey; + remaining = xmssmtKey.getUsagesRemaining(); + index = xmssmtKey.getIndex(); + } else if (privateKey instanceof LMSPrivateKey) { + LMSPrivateKey lmsKey = (LMSPrivateKey) privateKey; + remaining = lmsKey.getUsagesRemaining(); + index = lmsKey.getIndex(); + } + + if (remaining < 0) { + // Not a stateful key - always healthy + builder.detail("stateful_key", false); + builder.detail("algorithm", algorithm); + builder.up(); + return; + } + + builder.detail("stateful_key", true); + builder.detail("algorithm", algorithm); + builder.detail("remaining_signatures", remaining); + builder.detail("signatures_used", index); + builder.detail("total_capacity", index + remaining); + + if (remaining <= 0) { + builder.message("Stateful key (" + algorithm + ") is exhausted with 0 remaining signatures"); + builder.down(); + return; + } + + double threshold = configuration.getStatefulKeyWarningThreshold(); + long totalCapacity = index + remaining; + if (threshold > 0 && totalCapacity > 0) { + double fractionRemaining = (double) remaining / totalCapacity; + builder.detail("fraction_remaining", String.format("%.4f", fractionRemaining)); + builder.detail("warning_threshold", String.valueOf(threshold)); + } + + builder.up(); + } +} diff --git a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCStatefulKeyTrackingTest.java b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCStatefulKeyTrackingTest.java new file mode 100644 index 0000000000000..fc7c133e96ddd --- /dev/null +++ b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCStatefulKeyTrackingTest.java @@ -0,0 +1,194 @@ +/* + * 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.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +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.stateful.StatefulKeyState; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for stateful key usage tracking with XMSS. Uses a very small tree height (2) so the key has only 4 total + * signatures, making it feasible to test exhaustion and threshold warnings. + */ +public class PQCStatefulKeyTrackingTest extends CamelTestSupport { + + @EndpointInject("mock:signed") + protected MockEndpoint resultSigned; + + @EndpointInject("mock:state") + protected MockEndpoint resultState; + + @Produce("direct:sign") + protected ProducerTemplate templateSign; + + @Produce("direct:getState") + protected ProducerTemplate templateGetState; + + public PQCStatefulKeyTrackingTest() throws NoSuchAlgorithmException { + } + + private static void ensureProviders() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastlePQCProvider()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:sign") + .to("pqc:sign?operation=sign&statefulKeyWarningThreshold=0.5") + .to("mock:signed"); + + from("direct:getState") + .to("pqc:state?operation=getKeyState") + .to("mock:state"); + } + }; + } + + @BindToRegistry("Keypair") + public KeyPair setKeyPair() throws Exception { + ensureProviders(); + KeyPairGenerator kpGen = KeyPairGenerator.getInstance( + PQCSignatureAlgorithms.XMSS.getAlgorithm(), + PQCSignatureAlgorithms.XMSS.getBcProvider()); + kpGen.initialize(new XMSSParameterSpec(2, XMSSParameterSpec.SHA256), new SecureRandom()); + return kpGen.generateKeyPair(); + } + + @BindToRegistry("Signer") + public Signature getSigner() throws NoSuchAlgorithmException, NoSuchProviderException { + ensureProviders(); + return Signature.getInstance( + PQCSignatureAlgorithms.XMSS.getAlgorithm(), + PQCSignatureAlgorithms.XMSS.getBcProvider()); + } + + @Test + void testSignDecreasesRemainingSignatures() throws Exception { + // Get initial state + resultState.expectedMessageCount(1); + templateGetState.sendBody("check"); + resultState.assertIsSatisfied(); + + StatefulKeyState initialState = resultState.getExchanges().get(0) + .getMessage().getHeader(PQCConstants.KEY_STATE, StatefulKeyState.class); + assertNotNull(initialState); + long initialRemaining = initialState.getUsagesRemaining(); + assertTrue(initialRemaining > 0, "Initial key should have remaining signatures"); + + // Sign once + resultSigned.expectedMessageCount(1); + templateSign.sendBody("Hello"); + resultSigned.assertIsSatisfied(); + + // Get state after signing + resultState.reset(); + resultState.expectedMessageCount(1); + templateGetState.sendBody("check"); + resultState.assertIsSatisfied(); + + StatefulKeyState afterState = resultState.getExchanges().get(0) + .getMessage().getHeader(PQCConstants.KEY_STATE, StatefulKeyState.class); + assertNotNull(afterState); + assertEquals(initialRemaining - 1, afterState.getUsagesRemaining(), + "Remaining signatures should decrease by 1 after signing"); + } + + @Test + void testKeyExhaustion() throws Exception { + ensureProviders(); + + // Create a fresh key with height=2 (4 signatures) + KeyPairGenerator kpGen = KeyPairGenerator.getInstance( + PQCSignatureAlgorithms.XMSS.getAlgorithm(), + PQCSignatureAlgorithms.XMSS.getBcProvider()); + kpGen.initialize(new XMSSParameterSpec(2, XMSSParameterSpec.SHA256), new SecureRandom()); + KeyPair exhaustionKeyPair = kpGen.generateKeyPair(); + + // Sign 4 times to exhaust the key + Signature xmssSigner = Signature.getInstance( + PQCSignatureAlgorithms.XMSS.getAlgorithm(), + PQCSignatureAlgorithms.XMSS.getBcProvider()); + + for (int i = 0; i < 4; i++) { + xmssSigner.initSign(exhaustionKeyPair.getPrivate()); + xmssSigner.update(("message" + i).getBytes()); + xmssSigner.sign(); + } + + // Now the key should be exhausted + org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey xmssPriv + = (org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey) exhaustionKeyPair.getPrivate(); + assertEquals(0, xmssPriv.getUsagesRemaining(), "Key should be exhausted after 4 signatures with height=2"); + } + + @Test + void testStatefulKeyStateNotExhausted() throws Exception { + ensureProviders(); + + KeyPairGenerator kpGen = KeyPairGenerator.getInstance( + PQCSignatureAlgorithms.XMSS.getAlgorithm(), + PQCSignatureAlgorithms.XMSS.getBcProvider()); + kpGen.initialize(new XMSSParameterSpec(2, XMSSParameterSpec.SHA256), new SecureRandom()); + KeyPair freshKeyPair = kpGen.generateKeyPair(); + + org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey xmssPriv + = (org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey) freshKeyPair.getPrivate(); + + StatefulKeyState state = new StatefulKeyState( + xmssPriv.getAlgorithm(), xmssPriv.getIndex(), xmssPriv.getUsagesRemaining()); + + assertFalse(state.isExhausted(), "Fresh key should not be exhausted"); + assertEquals(4, state.getUsagesRemaining(), "Fresh XMSS key with height=2 should have 4 remaining"); + assertEquals(0, state.getIndex(), "Fresh key should have index 0"); + } + + @Test + void testStatefulKeyStateExhausted() { + StatefulKeyState state = new StatefulKeyState("XMSS", 4, 0); + + assertTrue(state.isExhausted(), "Key with 0 remaining should be exhausted"); + assertEquals(0, state.getUsagesRemaining()); + assertEquals(4, state.getIndex()); + } +} diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java index ef5b2cd6758c8..6d9dbb4f60036 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/PqcComponentBuilderFactory.java @@ -371,6 +371,28 @@ default PqcComponentBuilder signer(java.security.Signature signer) { } + /** + * The warning threshold for stateful key exhaustion as a fraction of + * total signatures (0.0 to 1.0). When the remaining signatures for a + * stateful key (XMSS, XMSSMT, LMS/HSS) drop below this fraction of the + * total capacity, a WARN log is emitted. When remaining signatures + * reach zero, an exception is thrown to prevent key reuse. Set to 0 to + * disable warnings. + * + * The option is a: <code>double</code> type. + * + * Default: 0.1 + * Group: advanced + * + * @param statefulKeyWarningThreshold the value to set + * @return the dsl builder + */ + default PqcComponentBuilder statefulKeyWarningThreshold(double statefulKeyWarningThreshold) { + doSetProperty("statefulKeyWarningThreshold", statefulKeyWarningThreshold); + return this; + } + + /** * In the context of extractSecretKeyFromEncapsulation operation, this * option define if we want to have the key set as header. @@ -520,6 +542,7 @@ protected boolean setPropertyOnComponent( case "keyStorePassword": getOrCreateConfiguration((PQCComponent) component).setKeyStorePassword((java.lang.String) value); return true; case "signatureAlgorithm": getOrCreateConfiguration((PQCComponent) component).setSignatureAlgorithm((java.lang.String) value); return true; case "signer": getOrCreateConfiguration((PQCComponent) component).setSigner((java.security.Signature) value); return true; + case "statefulKeyWarningThreshold": getOrCreateConfiguration((PQCComponent) component).setStatefulKeyWarningThreshold((double) value); return true; case "storeExtractedSecretKeyAsHeader": getOrCreateConfiguration((PQCComponent) component).setStoreExtractedSecretKeyAsHeader((boolean) value); return true; case "strictKeyLifecycle": getOrCreateConfiguration((PQCComponent) component).setStrictKeyLifecycle((boolean) value); return true; case "symmetricKeyAlgorithm": getOrCreateConfiguration((PQCComponent) component).setSymmetricKeyAlgorithm((java.lang.String) value); return true; 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 c88f80bcbc4cc..b7381219cf051 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 @@ -476,6 +476,46 @@ default AdvancedPQCEndpointBuilder signer(String signer) { doSetProperty("signer", signer); return this; } + /** + * The warning threshold for stateful key exhaustion as a fraction of + * total signatures (0.0 to 1.0). When the remaining signatures for a + * stateful key (XMSS, XMSSMT, LMS/HSS) drop below this fraction of the + * total capacity, a WARN log is emitted. When remaining signatures + * reach zero, an exception is thrown to prevent key reuse. Set to 0 to + * disable warnings. + * + * The option is a: double type. + * + * Default: 0.1 + * Group: advanced + * + * @param statefulKeyWarningThreshold the value to set + * @return the dsl builder + */ + default AdvancedPQCEndpointBuilder statefulKeyWarningThreshold(double statefulKeyWarningThreshold) { + doSetProperty("statefulKeyWarningThreshold", statefulKeyWarningThreshold); + return this; + } + /** + * The warning threshold for stateful key exhaustion as a fraction of + * total signatures (0.0 to 1.0). When the remaining signatures for a + * stateful key (XMSS, XMSSMT, LMS/HSS) drop below this fraction of the + * total capacity, a WARN log is emitted. When remaining signatures + * reach zero, an exception is thrown to prevent key reuse. Set to 0 to + * disable warnings. + * + * The option will be converted to a double type. + * + * Default: 0.1 + * Group: advanced + * + * @param statefulKeyWarningThreshold the value to set + * @return the dsl builder + */ + default AdvancedPQCEndpointBuilder statefulKeyWarningThreshold(String statefulKeyWarningThreshold) { + doSetProperty("statefulKeyWarningThreshold", statefulKeyWarningThreshold); + return this; + } /** * In the context of extractSecretKeyFromEncapsulation operation, this * option define if we want to have the key set as header.