This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch pqc-examples
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git

commit 7b9d005dda5ee2e0951c5bcce8ceac7ba05366b6
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Oct 13 13:56:58 2025 +0200

    Added Java example for PQC Signing with Hashicorp Vault Key Lifecycle 
Management
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 pqc-document-signing/PQCDocumentSigningRoutes.java | 310 ++++++++++++++++++++
 pqc-document-signing/README.adoc                   | 324 +++++++++++++++++++++
 pqc-document-signing/application.properties        |  40 +++
 3 files changed, 674 insertions(+)

diff --git a/pqc-document-signing/PQCDocumentSigningRoutes.java 
b/pqc-document-signing/PQCDocumentSigningRoutes.java
new file mode 100644
index 0000000..6b5c72e
--- /dev/null
+++ b/pqc-document-signing/PQCDocumentSigningRoutes.java
@@ -0,0 +1,310 @@
+/*
+ * 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 11 routes:
+ * 1. Initialize PQC signing key on startup
+ * 2. REST API - Sign and verify a document
+ * 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}")
+            // Decode base64 signature to binary
+            .process(exchange -> {
+                String base64Signature = 
exchange.getMessage().getHeader("CamelPQCSignature", String.class);
+                if (base64Signature == null || base64Signature.isEmpty()) {
+                    throw new IllegalArgumentException("CamelPQCSignature 
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
new file mode 100644
index 0000000..d8fe80b
--- /dev/null
+++ b/pqc-document-signing/README.adoc
@@ -0,0 +1,324 @@
+= Post-Quantum Cryptography Document Signing with HashiCorp Vault
+
+This example demonstrates how to build a secure document signing service using 
Apache Camel with Post-Quantum Cryptography (PQC) and HashiCorp Vault for key 
lifecycle management.
+
+== Features
+
+* **Quantum-Resistant Signatures** - Signs documents using DILITHIUM, a 
NIST-standardized Post-Quantum Cryptographic algorithm
+* **HashiCorp Vault Integration** - Stores and manages PQC keys in HashiCorp 
Vault for enterprise-grade security
+* **Key Lifecycle Management** - Automatic tracking of key usage, age, 
rotation, and expiration
+* **Key Usage Tracking** - Demonstrates key expiration after a predefined 
number of usages (default: 10 signatures)
+* **Automatic Key Rotation** - When a key expires, a new key is 
**automatically generated and rotated**
+* **Key Metadata Tracking** - Real-time visibility into key status, usage 
count, age, and lifecycle events
+* **REST API** - HTTP endpoints for signing, verifying documents, and managing 
keys
+* **Scheduled Monitoring** - Periodic checks for key rotation needs based on 
age and usage thresholds
+* **Multi-Key Support** - Deprecated keys are retained for signature 
verification of old documents
+
+== Prerequisites
+
+* JBang installed (https://www.jbang.dev)
+* Docker installed for running HashiCorp Vault
+
+== Running HashiCorp Vault
+
+Run HashiCorp Vault using Docker:
+
+[source,sh]
+----
+$ docker run -d \
+  --name vault \
+  -p 8201:8200 \
+  -e VAULT_DEV_ROOT_TOKEN_ID=myroot \
+  -e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 \
+  hashicorp/vault:latest
+----
+
+This will start HashiCorp Vault configured with:
+
+* Root token: `myroot`
+* Port: `8201` (mapped from container port 8200)
+* Secrets engine: `secret` (KV v2)
+
+Wait a few seconds for Vault to fully start before proceeding.
+
+== Project Structure
+
+The example consists of the following files:
+
+[source,text]
+----
+pqc-document-signing/
+├── PQCDocumentSigningRoutes.java  # Main RouteBuilder with 11 routes
+├── application.properties          # Configuration file
+└── README.adoc                    # This file
+----
+
+== Running the Example
+
+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
+----
+
+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)
+
+== API Endpoints
+
+The application exposes the following REST endpoints:
+
+=== 1. Sign a Document
+
+Signs a document with a quantum-resistant signature.
+
+[source,sh]
+----
+$ curl -X POST http://localhost:8080/api/sign \
+  -H "Content-Type: text/plain" \
+  -d "This is a confidential document that needs quantum-resistant protection"
+----
+
+**Response:**
+[source,json]
+----
+{
+  "status": "signed",
+  "document": "This is a confidential document...",
+  "signature": "UMyKJt21oIYWNq0jDe6v63Xw8bs7JZ9hg9KEYXdFPIMxXS3AHwOa...",
+  "signatureAlgorithm": "DILITHIUM",
+  "keyId": "document-signing-key",
+  "keyMetadata": {
+    "keyId": "document-signing-key",
+    "algorithm": "DILITHIUM",
+    "status": "ACTIVE",
+    "usageCount": 1,
+    "ageInDays": 0
+  }
+}
+----
+
+Note: The `signature` field contains a base64-encoded quantum-resistant 
signature.
+
+=== 2. Get Key Metadata
+
+Retrieves detailed metadata about the signing key, including usage count and 
lifecycle status.
+
+[source,sh]
+----
+$ curl http://localhost:8080/api/key/metadata
+----
+
+**Response:**
+[source,json]
+----
+{
+  "keyId": "document-signing-key",
+  "algorithm": "DILITHIUM",
+  "status": "ACTIVE",
+  "createdAt": "2024-10-13T10:30:00Z",
+  "lastUsedAt": "2024-10-13T10:35:00Z",
+  "usageCount": 5,
+  "ageInDays": 0,
+  "expiresAt": null,
+  "nextRotationAt": null,
+  "expired": false,
+  "needsRotation": false
+}
+----
+
+=== 3. List All Keys
+
+Lists all PQC keys stored in Vault.
+
+[source,sh]
+----
+$ curl http://localhost:8080/api/keys
+----
+
+**Response:**
+[source,json]
+----
+{
+  "keys": [
+    {
+      "keyId": "document-signing-key",
+      "algorithm": "DILITHIUM",
+      "status": "ACTIVE",
+      "usageCount": 5
+    }
+  ]
+}
+----
+
+=== 4. Rotate Signing Key
+
+Manually rotates the signing key (creates a new key and deprecates the old 
one).
+
+[source,sh]
+----
+$ curl -X POST http://localhost:8080/api/key/rotate
+----
+
+**Response:**
+[source,json]
+----
+{
+  "status": "rotated",
+  "oldKey": "document-signing-key",
+  "newKey": "document-signing-key",
+  "message": "Key rotation completed successfully"
+}
+----
+
+== Automatic Key Rotation
+
+The example demonstrates automatic key rotation when keys reach expiration 
criteria:
+
+* **Usage-based**: After 10 signatures (configurable via `key.max.usage.count`)
+* **Age-based**: After 90 days (configurable via `key.max.age.days`)
+
+When a key expires, the system automatically rotates to a new key, marking the 
old key as `DEPRECATED` for verification of previously signed documents.
+
+**Test rotation:**
+[source,sh]
+----
+# Sign document 10 times to trigger automatic rotation
+$ for i in {1..10}; do
+  curl -X POST http://localhost:8080/api/sign \
+    -H "Content-Type: text/plain" \
+    -d "Document number $i"
+done
+
+# View both old and new keys
+$ curl http://localhost:8080/api/keys
+----
+
+== Key Storage in HashiCorp Vault
+
+The PQC keys are stored in Vault's KV v2 secrets engine with the following 
structure:
+
+[source,text]
+----
+secret/
+└── data/
+    └── pqc/
+        └── keys/
+            └── document-signing-key/
+                ├── private/       # PKCS#8 private key
+                ├── public/        # X.509 public key
+                └── metadata/      # Key metadata
+----
+
+This separation enables fine-grained access control where applications can 
access public keys for verification without having access to private keys for 
signing.
+
+== Configuration
+
+Configuration is managed in `application.properties`:
+
+[source,properties]
+----
+# Vault connection
+vault.host=localhost
+vault.port=8201
+vault.token=myroot
+
+# Key rotation policy
+key.max.usage.count=10        # Max signatures before rotation
+key.max.age.days=90            # Max key age in days
+key.rotation.check.period=60000 # Check interval (ms)
+----
+
+== Troubleshooting
+
+=== Connection to Vault Failed
+
+* Ensure Vault is running: `docker ps`
+* Verify the port in `application.properties` is set to `8201`
+* Check the Vault token is correct (default: `myroot`)
+
+=== Key Not Found in Vault
+
+* Check Vault UI at http://localhost:8201 (token: `myroot`)
+* Verify the secrets engine is enabled: `vault secrets list`
+* Check the key path: `secret/data/pqc/keys/document-signing-key`
+
+=== Signature Verification Failed
+
+* Ensure you're using the same document content for both signing and 
verification
+* Include the `CamelPQCSignature` header with the base64-encoded signature 
from the sign response
+* The signature must be passed exactly as received from the sign endpoint 
(base64 string)
+* Verify the key hasn't been rotated or revoked
+* Check logs for "ERROR during verification" messages which indicate signature 
format issues
+
+== Stopping
+
+To stop the Camel application, press `Ctrl+C`.
+
+To stop HashiCorp Vault:
+
+[source,sh]
+----
+$ docker stop vault
+$ docker rm vault
+----
+
+== Architecture
+
+This example demonstrates:
+
+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
+
+=== Implementation Details
+
+The `PQCDocumentSigningRoutes.java` RouteBuilder:
+
+- 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
+
+== Security Notice
+
+This example uses **development settings**. For production:
+
+* Use HTTPS for Vault (`vault.scheme=https`)
+* Use AppRole or similar authentication instead of root tokens
+* Configure Vault access policies
+* Increase `key.max.usage.count` to realistic values (e.g., 100,000)
+* Enable Vault audit logging
+
+== Help and Contributions
+
+If you hit any problem using Camel or have some feedback, then please
+https://camel.apache.org/community/support/[let us know].
+
+We also love contributors, so
+https://camel.apache.org/community/contributing/[get involved] :-)
+
+The Camel riders!
diff --git a/pqc-document-signing/application.properties 
b/pqc-document-signing/application.properties
new file mode 100644
index 0000000..74b80cc
--- /dev/null
+++ b/pqc-document-signing/application.properties
@@ -0,0 +1,40 @@
+# 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.
+
+# Application Configuration
+camel.main.name = PQCDocumentSigningExample
+
+# HashiCorp Vault Configuration
+# 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.scheme=http
+vault.token=myroot
+vault.secrets.engine=secret
+vault.keys.prefix=pqc/keys
+
+# PQC Key Lifecycle Configuration
+# Maximum number of times a key can be used before expiration
+key.max.usage.count=10
+
+# Maximum age of a key in days before rotation is needed
+key.max.age.days=90
+
+# How often to check if key rotation is needed (in milliseconds)
+# 60000 = 1 minute (for demo purposes, use longer intervals in production)
+key.rotation.check.period=60000

Reply via email to