This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit d3400edfa59577a068486ef78d893ddf1d94dc91 Author: Andrea Cosentino <[email protected]> AuthorDate: Tue Oct 14 10:23:46 2025 +0200 PQC Examples: Moving examples from Java to YAML with Groovy Beans Signed-off-by: Andrea Cosentino <[email protected]> --- pqc-document-signing/PQCDocumentSigningRoutes.java | 316 ------------------ pqc-document-signing/README.adoc | 103 ++++-- pqc-document-signing/application.properties | 6 +- pqc-document-signing/pqc-document-signing.yaml | 353 +++++++++++++++++++++ 4 files changed, 424 insertions(+), 354 deletions(-) diff --git a/pqc-document-signing/PQCDocumentSigningRoutes.java b/pqc-document-signing/PQCDocumentSigningRoutes.java deleted file mode 100644 index 94f0f70..0000000 --- a/pqc-document-signing/PQCDocumentSigningRoutes.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * 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.example.pqc; - -import java.security.KeyPair; -import java.security.Security; -import java.util.Base64; - -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; -import org.springframework.vault.authentication.TokenAuthentication; -import org.springframework.vault.client.VaultEndpoint; -import org.springframework.vault.core.VaultTemplate; - -/** - * RouteBuilder for PQC Document Signing with HashiCorp Vault integration. - * - * This class defines 12 routes: - * 1. Initialize PQC signing key on startup - * 2. REST API - Sign a document - * 3. REST API - Verify a document signature - * 4. REST API - Get key metadata - * 5. REST API - List all keys - * 6. REST API - Rotate signing key - * 7. Scheduled job - Check key rotation needs - * 8. Helper - Update key usage metadata - * 9. Helper - Get key metadata - * 10. Helper - Check key expiration and auto-rotate - * 11. Helper - Automatic key rotation on expiration - */ -public class PQCDocumentSigningRoutes extends RouteBuilder { - - @Override - public void configure() throws Exception { - - // Register BouncyCastle providers for PQC algorithms - if (Security.getProvider("BC") == null) { - Security.addProvider(new BouncyCastleProvider()); - } - if (Security.getProvider("BCPQC") == null) { - Security.addProvider(new BouncyCastlePQCProvider()); - } - - // Register beans for HashiCorp Vault integration - registerVaultBeans(); - - // Route 1: Initialize PQC signing key on startup - from("timer:init?repeatCount=1") - .routeId("initialize-signing-key") - .log("Initializing PQC signing key in Vault...") - .bean("keyLifecycleManager", "generateKeyPair('DILITHIUM', 'document-signing-key')") - .log("PQC signing key initialized successfully") - .process(exchange -> { - // Register the KeyPair bean directly for autowiring - HashicorpVaultKeyLifecycleManager keyManager = exchange.getContext().getRegistry() - .lookupByNameAndType("keyLifecycleManager", HashicorpVaultKeyLifecycleManager.class); - KeyPair keyPair = keyManager.getKey("document-signing-key"); - - // Register as KeyPair type so PQC component can autowire it - bindToRegistry("signingKey", keyPair); - }) - .to("direct:get-key-metadata"); - - // Route 2: REST API - Sign a document - from("platform-http:/api/sign") - .routeId("sign-document-api") - .log("Received document signing request: ${body}") - .setHeader("originalBody", simple("${body}")) - // Sign the document using PQC - .toD("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM&keyPair=#signingKey") - // Convert binary signature to base64 - .process(exchange -> { - byte[] signature = exchange.getMessage().getHeader("CamelPQCSignature", byte[].class); - if (signature != null) { - String base64Signature = Base64.getEncoder().encodeToString(signature); - exchange.getMessage().setHeader("CamelPQCSignature", base64Signature); - } - }) - .log("Document signed with quantum-resistant signature") - // Update key usage metadata - .to("direct:update-key-usage") - // Check if key needs rotation or has expired - .to("direct:check-key-expiration") - // Prepare response with signature and key metadata - .setBody(simple("{\n" + - " \"status\": \"signed\",\n" + - " \"document\": \"${header.originalBody}\",\n" + - " \"signature\": \"${header.CamelPQCSignature}\",\n" + - " \"signatureAlgorithm\": \"DILITHIUM\",\n" + - " \"keyId\": \"document-signing-key\",\n" + - " \"keyMetadata\": ${body}\n" + - "}")) - .setHeader("Content-Type", constant("application/json")) - .log("Response: ${body}"); - - // Route 3: REST API - Verify a document signature - from("platform-http:/api/verify") - .routeId("verify-document-api") - .log("Received document verification request: ${body}") - // Get signature from X-Signature header (workaround for header filtering) - .process(exchange -> { - String base64Signature = exchange.getIn().getHeader("X-Signature", String.class); - if (base64Signature == null || base64Signature.isEmpty()) { - throw new IllegalArgumentException("X-Signature header is missing or empty"); - } - byte[] signature = Base64.getDecoder().decode(base64Signature); - exchange.getIn().setHeader("CamelPQCSignature", signature); - exchange.getIn().setHeader("signatureLength", signature.length); - }) - .log("Verifying signature of length: ${header.signatureLength} bytes") - // Verify the document signature using PQC - .doTry() - .toD("pqc:verify?operation=verify&signatureAlgorithm=DILITHIUM&keyPair=#signingKey") - .log("Verification completed. Result: ${header.CamelPQCVerify}") - .doCatch(Exception.class) - .log("ERROR during verification: ${exception.message}") - .setHeader("CamelPQCVerify", constant(false)) - .end() - .choice() - .when(simple("${header.CamelPQCVerify} == true")) - .setBody(simple("{\n" + - " \"status\": \"verified\",\n" + - " \"valid\": true,\n" + - " \"message\": \"Document signature is valid\",\n" + - " \"signatureAlgorithm\": \"DILITHIUM\"\n" + - "}")) - .when(simple("${header.CamelPQCVerify} == false")) - .setBody(simple("{\n" + - " \"status\": \"verified\",\n" + - " \"valid\": false,\n" + - " \"message\": \"Document signature is invalid\",\n" + - " \"signatureAlgorithm\": \"DILITHIUM\"\n" + - "}")) - .end() - .setHeader("Content-Type", constant("application/json")) - .log("Verification result: ${body}"); - - // Route 4: REST API - Get key metadata - from("platform-http:/api/key/metadata") - .routeId("get-key-metadata-api") - .log("Fetching key metadata...") - .to("direct:get-key-metadata"); - - // Route 5: REST API - List all keys - from("platform-http:/api/keys") - .routeId("list-keys-api") - .log("Listing all PQC keys...") - .bean("keyLifecycleManager", "listKeys") - .setBody(simple("{\n" + - " \"keys\": ${body}\n" + - "}")) - .setHeader("Content-Type", constant("application/json")) - .log("Keys listed: ${body}"); - - // Route 6: REST API - Rotate signing key - from("platform-http:/api/key/rotate") - .routeId("rotate-key-api") - .log("Rotating signing key...") - .bean("keyLifecycleManager", "rotateKey('document-signing-key', 'document-signing-key-v2', 'DILITHIUM')") - .log("Key rotated successfully: old key deprecated, new key active") - .setBody(simple("{\n" + - " \"status\": \"rotated\",\n" + - " \"oldKey\": \"document-signing-key\",\n" + - " \"newKey\": \"document-signing-key-v2\",\n" + - " \"message\": \"Key rotation completed successfully\"\n" + - "}")) - .setHeader("Content-Type", constant("application/json")); - - // Route 7: Scheduled job - Check key rotation needs - from("timer:checkRotation?period={{key.rotation.check.period}}") - .routeId("check-rotation-schedule") - .log("Checking if key needs rotation...") - .bean("keyLifecycleManager", "needsRotation('document-signing-key', 'P{{key.max.age.days}}D', {{key.max.usage.count}})") - .choice() - .when(simple("${body} == true")) - .log("WARNING: Key 'document-signing-key' needs rotation!") - .to("direct:get-key-metadata") - .log("Current key metadata: ${body}") - .otherwise() - .log("Key rotation not needed yet") - .end(); - - // Route 8: Helper - Update key usage metadata - from("direct:update-key-usage") - .routeId("update-key-usage") - .bean("keyLifecycleManager", "getKeyMetadata('document-signing-key')") - .setProperty("metadata", simple("${body}")) - .process(exchange -> { - // Update last used timestamp and increment usage count - Object metadata = exchange.getProperty("metadata"); - if (metadata != null) { - // Call updateLastUsed on the metadata object - metadata.getClass().getMethod("updateLastUsed").invoke(metadata); - exchange.getMessage().setBody(metadata); - } - }) - .bean("keyLifecycleManager", "updateKeyMetadata('document-signing-key', ${body})") - .log("Key usage updated. Usage count: ${body.usageCount}"); - - // Route 9: Helper - Get key metadata - from("direct:get-key-metadata") - .routeId("get-key-metadata-helper") - .bean("keyLifecycleManager", "getKeyMetadata('document-signing-key')") - .setBody(simple("{\n" + - " \"keyId\": \"${body.keyId}\",\n" + - " \"algorithm\": \"${body.algorithm}\",\n" + - " \"status\": \"${body.status}\",\n" + - " \"createdAt\": \"${body.createdAt}\",\n" + - " \"lastUsedAt\": \"${body.lastUsedAt}\",\n" + - " \"usageCount\": ${body.usageCount},\n" + - " \"ageInDays\": ${body.ageInDays},\n" + - " \"expiresAt\": \"${body.expiresAt}\",\n" + - " \"nextRotationAt\": \"${body.nextRotationAt}\",\n" + - " \"expired\": ${body.expired},\n" + - " \"needsRotation\": ${body.needsRotation}\n" + - "}")) - .setHeader("Content-Type", constant("application/json")) - .log("Key metadata: ${body}"); - - // Route 10: Helper - Check key expiration and auto-rotate - from("direct:check-key-expiration") - .routeId("check-key-expiration") - .bean("keyLifecycleManager", "getKeyMetadata('document-signing-key')") - .choice() - .when(simple("${body.usageCount} >= {{key.max.usage.count}}")) - .log("WARNING: Key has reached maximum usage count (${body.usageCount} >= {{key.max.usage.count}})") - .setProperty("expirationReason", simple("usage count (${body.usageCount} uses)")) - .to("direct:auto-rotate-key") - .when(simple("${body.ageInDays} >= {{key.max.age.days}}")) - .log("WARNING: Key has reached maximum age (${body.ageInDays} >= {{key.max.age.days}} days)") - .setProperty("expirationReason", simple("age (${body.ageInDays} days)")) - .to("direct:auto-rotate-key") - .otherwise() - .setBody(simple("Key is valid (usage: ${body.usageCount}/{{key.max.usage.count}}, age: ${body.ageInDays}/{{key.max.age.days}} days)")) - .end(); - - // Route 11: Helper - Automatic key rotation on expiration - from("direct:auto-rotate-key") - .routeId("auto-rotate-key") - .log(">>> AUTOMATIC KEY ROTATION TRIGGERED <<<") - .log("Reason: Key expired due to ${exchangeProperty.expirationReason}") - // Generate new key ID with timestamp - .setProperty("timestamp", simple("${date:now:yyyyMMdd-HHmmss}")) - .setProperty("newKeyId", simple("document-signing-key")) - .log("Rotating key from 'document-signing-key' to '${exchangeProperty.newKeyId}'") - // Perform rotation - .bean("keyLifecycleManager", "rotateKey('document-signing-key', '${exchangeProperty.newKeyId}', 'DILITHIUM')") - .log(">>> KEY ROTATION COMPLETED <<<") - .log("Old key 'document-signing-key' status: DEPRECATED") - .log("New key '${exchangeProperty.newKeyId}' status: ACTIVE") - // Update the active key reference for future operations - .setProperty("activeKeyId", simple("${exchangeProperty.newKeyId}")) - // Get metadata of new key - .bean("keyLifecycleManager", "getKeyMetadata('${exchangeProperty.newKeyId}')") - .setBody(simple("{\n" + - " \"rotationTriggered\": true,\n" + - " \"reason\": \"${exchangeProperty.expirationReason}\",\n" + - " \"oldKey\": \"document-signing-key\",\n" + - " \"oldKeyStatus\": \"DEPRECATED\",\n" + - " \"newKey\": \"${exchangeProperty.newKeyId}\",\n" + - " \"newKeyStatus\": \"ACTIVE\",\n" + - " \"newKeyCreatedAt\": \"${body.createdAt}\",\n" + - " \"message\": \"Key automatically rotated due to ${exchangeProperty.expirationReason}\"\n" + - "}")) - .log("Rotation details: ${body}"); - } - - /** - * Register beans for HashiCorp Vault integration - */ - private void registerVaultBeans() throws Exception { - // Get configuration properties - String vaultHost = getContext().resolvePropertyPlaceholders("{{vault.host}}"); - int vaultPort = Integer.parseInt(getContext().resolvePropertyPlaceholders("{{vault.port}}")); - String vaultScheme = getContext().resolvePropertyPlaceholders("{{vault.scheme}}"); - String vaultToken = getContext().resolvePropertyPlaceholders("{{vault.token}}"); - String secretsEngine = getContext().resolvePropertyPlaceholders("{{vault.secrets.engine}}"); - String keyPrefix = getContext().resolvePropertyPlaceholders("{{vault.keys.prefix}}"); - - // Create VaultEndpoint - VaultEndpoint vaultEndpoint = new VaultEndpoint(); - vaultEndpoint.setHost(vaultHost); - vaultEndpoint.setPort(vaultPort); - vaultEndpoint.setScheme(vaultScheme); - bindToRegistry("vaultEndpoint", vaultEndpoint); - - // Create TokenAuthentication - TokenAuthentication tokenAuthentication = new TokenAuthentication(vaultToken); - bindToRegistry("tokenAuthentication", tokenAuthentication); - - // Create VaultTemplate - VaultTemplate vaultTemplate = new VaultTemplate(vaultEndpoint, tokenAuthentication); - bindToRegistry("vaultTemplate", vaultTemplate); - - // Create HashicorpVaultKeyLifecycleManager using constructor with all parameters - HashicorpVaultKeyLifecycleManager keyLifecycleManager = - new HashicorpVaultKeyLifecycleManager(vaultHost, vaultPort, vaultScheme, vaultToken, secretsEngine, keyPrefix); - bindToRegistry("keyLifecycleManager", keyLifecycleManager); - } -} diff --git a/pqc-document-signing/README.adoc b/pqc-document-signing/README.adoc index 09b313e..5774afd 100644 --- a/pqc-document-signing/README.adoc +++ b/pqc-document-signing/README.adoc @@ -48,7 +48,7 @@ The example consists of the following files: [source,text] ---- pqc-document-signing/ -├── PQCDocumentSigningRoutes.java # Main RouteBuilder with 12 routes +├── pqc-document-signing.yaml # YAML configuration (beans + routes) ├── application.properties # Configuration file └── README.adoc # This file ---- @@ -59,23 +59,17 @@ After Vault is configured and running, start the Camel application: [source,sh] ---- -$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run PQCDocumentSigningRoutes.java ----- - -Or using Maven: - -[source,sh] ----- -$ mvn compile exec:java +$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run pqc-document-signing.yaml ---- The application will: -1. Connect to HashiCorp Vault -2. Generate a DILITHIUM Post-Quantum Cryptographic key pair -3. Store the key securely in Vault at path: `secret/data/pqc/keys/document-signing-key` -4. Start REST API endpoints on port 8080 -5. Begin monitoring key lifecycle (usage count, age, rotation needs) +1. Register BouncyCastle security providers (BC, BCPQC) +2. Connect to HashiCorp Vault +3. Generate a DILITHIUM Post-Quantum Cryptographic key pair +4. Store the key securely in Vault at path: `secret/data/pqc/keys/document-signing-key` +5. Start REST API endpoints on port 8080 +6. Begin monitoring key lifecycle (usage count, age, rotation needs) == API Endpoints @@ -301,32 +295,71 @@ $ docker stop vault $ docker rm vault ---- -== Architecture +== Implementation Details + +The `pqc-document-signing.yaml` file contains: + +=== Bean Configuration + +**1. Security Initialization Bean** - Registers BouncyCastle providers at startup: + +[source,yaml] +---- +- beans: + - name: initSecurity + type: java.lang.Object + scriptLanguage: groovy + script: | + // Register BouncyCastle providers + if (java.security.Security.getProvider("BC") == null) { + java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()) + } + if (java.security.Security.getProvider("BCPQC") == null) { + java.security.Security.addProvider(new org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider()) + } + return new Object() +---- + +**2. HashicorpVaultKeyLifecycleManager** - Created using Groovy script with 6-parameter constructor: + +[source,yaml] +---- + - name: keyLifecycleManager + type: org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager + scriptLanguage: groovy + script: | + new org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager( + '{{vault.host}}', // Vault server hostname + {{vault.port}}, // Vault server port + '{{vault.scheme}}', // Connection scheme (http/https) + '{{vault.token}}', // Vault authentication token + '{{vault.secrets.engine}}', // KV secrets engine name + '{{vault.keys.prefix}}' // Prefix for key paths in Vault + ) +---- -This example demonstrates: +=== Route Definitions -1. **Post-Quantum Cryptography (PQC)** - Quantum-resistant digital signatures using DILITHIUM -2. **HashiCorp Vault Integration** - Enterprise secret management for PQC keys -3. **Key Lifecycle Management** - Automated tracking of key usage, rotation, and expiration -4. **RESTful API** - Multiple endpoints for signing, verification, and key management -5. **Platform HTTP Component** - Lightweight HTTP server for REST APIs -6. **Java-based RouteBuilder** - Routes defined using Camel Java DSL -7. **Bean Configuration** - Vault and key manager beans configured programmatically in Java -8. **Scheduled Jobs** - Periodic key rotation checks -9. **Dynamic Routing** - Uses `.toD()` for runtime endpoint creation with autowired KeyPair -10. **Base64 Encoding** - Binary signatures converted to base64 for JSON transport +The YAML file defines 12 routes using Camel YAML DSL: -=== Implementation Details +1. **initialize-signing-key** - Generates DILITHIUM key pair and registers it as a bean +2. **sign-document-api** - POST `/api/sign` - Signs documents with PQC signature +3. **verify-document-api** - POST `/api/verify` - Verifies document signatures (uses `X-Signature` header) +4. **get-key-metadata-api** - GET `/api/key/metadata` - Returns key metadata +5. **list-keys-api** - GET `/api/keys` - Lists all PQC keys in Vault +6. **rotate-key-api** - POST `/api/key/rotate` - Manual key rotation +7. **check-rotation-schedule** - Timer-based rotation checks +8. **update-key-usage** - Helper route for updating key usage metadata +9. **get-key-metadata-helper** - Helper route for retrieving key metadata +10. **check-key-expiration** - Helper route checking if key expired +11. **auto-rotate-key** - Automatic key rotation when key expires -The `PQCDocumentSigningRoutes.java` RouteBuilder: +=== Key Features -- Registers BouncyCastle security providers (BC, BCPQC) -- Creates and binds Vault-related beans to the registry -- Defines 11 Camel routes using Java DSL -- Handles KeyPair registration after key generation -- Converts binary signatures to/from base64 for JSON transport -- Uses dynamic routing (`.toD()`) to create PQC endpoints at runtime -- Implements automatic key rotation based on usage count and age +- **Declarative Configuration** - All beans and routes defined in YAML +- **Groovy Scripts** - Used for bean instantiation and signature conversion +- **Property Placeholders** - All configuration values from `application.properties` +- **No Compilation Required** - Routes can be modified without recompilation == Security Notice diff --git a/pqc-document-signing/application.properties b/pqc-document-signing/application.properties index 74b80cc..4bbc048 100644 --- a/pqc-document-signing/application.properties +++ b/pqc-document-signing/application.properties @@ -19,12 +19,12 @@ camel.main.name = PQCDocumentSigningExample # HashiCorp Vault Configuration -# Port 8200 is used when running Vault via: camel infra run hashicorp-vault +# Port 8200 is used when running Vault via: camel infra run hashicorp vault # Port 8201 is used when running Vault manually via Docker (see README.adoc) vault.host=localhost -vault.port=8201 +vault.port=8200 vault.scheme=http -vault.token=myroot +vault.token=myToken vault.secrets.engine=secret vault.keys.prefix=pqc/keys diff --git a/pqc-document-signing/pqc-document-signing.yaml b/pqc-document-signing/pqc-document-signing.yaml new file mode 100644 index 0000000..c47813c --- /dev/null +++ b/pqc-document-signing/pqc-document-signing.yaml @@ -0,0 +1,353 @@ +# 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. + +# Post-Quantum Cryptography Document Signing with HashiCorp Vault +# Single YAML file containing beans and routes + +# Bean Definitions +- beans: + # Register BouncyCastle providers at startup using Groovy + - name: initSecurity + type: java.lang.Object + scriptLanguage: groovy + script: | + // Register BouncyCastle providers + if (java.security.Security.getProvider("BC") == null) { + java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()) + } + if (java.security.Security.getProvider("BCPQC") == null) { + java.security.Security.addProvider(new org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider()) + } + return new Object() + + # HashicorpVaultKeyLifecycleManager using Groovy script + - name: keyLifecycleManager + type: org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager + scriptLanguage: groovy + script: | + new org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager( + '{{vault.host}}', + {{vault.port}}, + '{{vault.scheme}}', + '{{vault.token}}', + '{{vault.secrets.engine}}', + '{{vault.keys.prefix}}' + ) + +# Route Definitions +- route: + id: initialize-signing-key + from: + uri: timer:init + parameters: + repeatCount: 1 + steps: + - log: "Initializing PQC signing key in Vault..." + - bean: + ref: keyLifecycleManager + method: "generateKeyPair('DILITHIUM', 'document-signing-key')" + - log: "PQC signing key initialized successfully" + - script: + groovy: | + // Register the KeyPair bean + def keyManager = camelContext.registry.lookupByNameAndType('keyLifecycleManager', + org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager.class) + def keyPair = keyManager.getKey('document-signing-key') + camelContext.registry.bind('signingKey', keyPair) + - to: direct:get-key-metadata + +- route: + id: sign-document-api + from: + uri: platform-http:/api/sign + steps: + - log: "Received document signing request: ${body}" + - setHeader: + name: originalBody + simple: "${body}" + - toD: "pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM&keyPair=#signingKey" + - script: + groovy: | + // Convert binary signature to base64 + def signature = exchange.message.getHeader('CamelPQCSignature', byte[].class) + if (signature != null) { + def base64Signature = java.util.Base64.encoder.encodeToString(signature) + exchange.message.setHeader('CamelPQCSignature', base64Signature) + } + - log: "Document signed with quantum-resistant signature" + - to: direct:update-key-usage + - to: direct:check-key-expiration + - setBody: + simple: | + { + "status": "signed", + "document": "${header.originalBody}", + "signature": "${header.CamelPQCSignature}", + "signatureAlgorithm": "DILITHIUM", + "keyId": "document-signing-key", + "keyMetadata": ${body} + } + - setHeader: + name: Content-Type + constant: "application/json" + - log: "Response: ${body}" + +- route: + id: verify-document-api + from: + uri: platform-http:/api/verify + steps: + - log: "Received document verification request: ${body}" + - script: + groovy: | + // Get signature from X-Signature header + def base64Signature = exchange.getIn().getHeader('X-Signature', String.class) + if (base64Signature == null || base64Signature.isEmpty()) { + throw new IllegalArgumentException('X-Signature header is missing or empty') + } + def signature = java.util.Base64.decoder.decode(base64Signature) + exchange.getIn().setHeader('CamelPQCSignature', signature) + exchange.getIn().setHeader('signatureLength', signature.length) + - log: "Verifying signature of length: ${header.signatureLength} bytes" + - doTry: + steps: + - toD: "pqc:verify?operation=verify&signatureAlgorithm=DILITHIUM&keyPair=#signingKey" + - log: "Verification completed. Result: ${header.CamelPQCVerification}" + doCatch: + - exception: + - "java.lang.Exception" + steps: + - log: "ERROR during verification: ${exception.message}" + - setHeader: + name: CamelPQCVerification + constant: false + - choice: + when: + - simple: "${header.CamelPQCVerification} == true" + steps: + - setBody: + simple: | + { + "status": "verified", + "valid": true, + "message": "Document signature is valid", + "signatureAlgorithm": "DILITHIUM" + } + - simple: "${header.CamelPQCVerification} == false" + steps: + - setBody: + simple: | + { + "status": "verified", + "valid": false, + "message": "Document signature is invalid", + "signatureAlgorithm": "DILITHIUM" + } + - setHeader: + name: Content-Type + constant: "application/json" + - log: "Verification result: ${body}" + +- route: + id: get-key-metadata-api + from: + uri: platform-http:/api/key/metadata + steps: + - log: "Fetching key metadata..." + - to: direct:get-key-metadata + +- route: + id: list-keys-api + from: + uri: platform-http:/api/keys + steps: + - log: "Listing all PQC keys..." + - bean: + ref: keyLifecycleManager + method: "listKeys" + - setBody: + simple: | + { + "keys": ${body} + } + - setHeader: + name: Content-Type + constant: "application/json" + - log: "Keys listed: ${body}" + +- route: + id: rotate-key-api + from: + uri: platform-http:/api/key/rotate + steps: + - log: "Rotating signing key..." + - bean: + ref: keyLifecycleManager + method: "rotateKey('document-signing-key', 'document-signing-key', 'DILITHIUM')" + - log: "Key rotated successfully: old key deprecated, new key active" + - setBody: + simple: | + { + "status": "rotated", + "oldKey": "document-signing-key", + "newKey": "document-signing-key", + "message": "Key rotation completed successfully" + } + - setHeader: + name: Content-Type + constant: "application/json" + +- route: + id: check-rotation-schedule + from: + uri: timer:checkRotation + parameters: + period: "{{key.rotation.check.period}}" + steps: + - log: "Checking if key needs rotation..." + - bean: + ref: keyLifecycleManager + method: "needsRotation('document-signing-key', 'P{{key.max.age.days}}D', {{key.max.usage.count}})" + - choice: + when: + - simple: "${body} == true" + steps: + - log: "WARNING: Key 'document-signing-key' needs rotation!" + - to: direct:get-key-metadata + - log: "Current key metadata: ${body}" + otherwise: + steps: + - log: "Key rotation not needed yet" + +- route: + id: update-key-usage + from: + uri: direct:update-key-usage + steps: + - bean: + ref: keyLifecycleManager + method: "getKeyMetadata('document-signing-key')" + - setProperty: + name: metadata + simple: "${body}" + - script: + groovy: | + // Update last used timestamp and increment usage count + def metadata = exchange.getProperty('metadata') + if (metadata != null) { + metadata.getClass().getMethod('updateLastUsed').invoke(metadata) + exchange.message.setBody(metadata) + } + - bean: + ref: keyLifecycleManager + method: "updateKeyMetadata('document-signing-key', ${body})" + - log: "Key usage updated. Usage count: ${body.usageCount}" + +- route: + id: get-key-metadata-helper + from: + uri: direct:get-key-metadata + steps: + - bean: + ref: keyLifecycleManager + method: "getKeyMetadata('document-signing-key')" + - setBody: + simple: | + { + "keyId": "${body.keyId}", + "algorithm": "${body.algorithm}", + "status": "${body.status}", + "createdAt": "${body.createdAt}", + "lastUsedAt": "${body.lastUsedAt}", + "usageCount": ${body.usageCount}, + "ageInDays": ${body.ageInDays}, + "expiresAt": "${body.expiresAt}", + "nextRotationAt": "${body.nextRotationAt}", + "expired": ${body.expired}, + "needsRotation": ${body.needsRotation} + } + - setHeader: + name: Content-Type + constant: "application/json" + - log: "Key metadata: ${body}" + +- route: + id: check-key-expiration + from: + uri: direct:check-key-expiration + steps: + - bean: + ref: keyLifecycleManager + method: "getKeyMetadata('document-signing-key')" + - choice: + when: + - simple: "${body.usageCount} >= {{key.max.usage.count}}" + steps: + - log: "WARNING: Key has reached maximum usage count (${body.usageCount} >= {{key.max.usage.count}})" + - setProperty: + name: expirationReason + simple: "usage count (${body.usageCount} uses)" + - to: direct:auto-rotate-key + - simple: "${body.ageInDays} >= {{key.max.age.days}}" + steps: + - log: "WARNING: Key has reached maximum age (${body.ageInDays} >= {{key.max.age.days}} days)" + - setProperty: + name: expirationReason + simple: "age (${body.ageInDays} days)" + - to: direct:auto-rotate-key + otherwise: + steps: + - setBody: + simple: "Key is valid (usage: ${body.usageCount}/{{key.max.usage.count}}, age: ${body.ageInDays}/{{key.max.age.days}} days)" + +- route: + id: auto-rotate-key + from: + uri: direct:auto-rotate-key + steps: + - log: ">>> AUTOMATIC KEY ROTATION TRIGGERED <<<" + - log: "Reason: Key expired due to ${exchangeProperty.expirationReason}" + - setProperty: + name: timestamp + simple: "${date:now:yyyyMMdd-HHmmss}" + - setProperty: + name: newKeyId + simple: "document-signing-key" + - log: "Rotating key from 'document-signing-key' to '${exchangeProperty.newKeyId}'" + - bean: + ref: keyLifecycleManager + method: "rotateKey('document-signing-key', '${exchangeProperty.newKeyId}', 'DILITHIUM')" + - log: ">>> KEY ROTATION COMPLETED <<<" + - log: "Old key 'document-signing-key' status: DEPRECATED" + - log: "New key '${exchangeProperty.newKeyId}' status: ACTIVE" + - setProperty: + name: activeKeyId + simple: "${exchangeProperty.newKeyId}" + - bean: + ref: keyLifecycleManager + method: "getKeyMetadata('${exchangeProperty.newKeyId}')" + - setBody: + simple: | + { + "rotationTriggered": true, + "reason": "${exchangeProperty.expirationReason}", + "oldKey": "document-signing-key", + "oldKeyStatus": "DEPRECATED", + "newKey": "${exchangeProperty.newKeyId}", + "newKeyStatus": "ACTIVE", + "newKeyCreatedAt": "${body.createdAt}", + "message": "Key automatically rotated due to ${exchangeProperty.expirationReason}" + } + - log: "Rotation details: ${body}"
