You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2025/10/14 08:29:14 UTC
(camel-jbang-examples) branch main updated (02fe96c -> 92069f7)
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
from 02fe96c Added Java example for PQC Signing with Hashicorp Vault Key Lifecycle Management (#41)
new d3400ed PQC Examples: Moving examples from Java to YAML with Groovy Beans
new 92069f7 PQC Examples: Added more docs
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
pqc-document-signing/PQCDocumentSigningRoutes.java | 316 ------------------
pqc-document-signing/README.adoc | 159 +++++++---
pqc-document-signing/application.properties | 6 +-
pqc-document-signing/pqc-document-signing.yaml | 353 +++++++++++++++++++++
4 files changed, 474 insertions(+), 360 deletions(-)
delete mode 100644 pqc-document-signing/PQCDocumentSigningRoutes.java
create mode 100644 pqc-document-signing/pqc-document-signing.yaml
(camel-jbang-examples) 01/02: PQC Examples: Moving examples from Java to YAML with Groovy Beans
Posted by ac...@apache.org.
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 <an...@gmail.com>
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 <an...@gmail.com>
---
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}"
(camel-jbang-examples) 02/02: PQC Examples: Added more docs
Posted by ac...@apache.org.
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 92069f73ed6fc52773b78da23a4a5f2e9f7aaefc
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Tue Oct 14 10:27:24 2025 +0200
PQC Examples: Added more docs
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
pqc-document-signing/README.adoc | 58 +++++++++++++++++++++++++++++++++++-----
1 file changed, 51 insertions(+), 7 deletions(-)
diff --git a/pqc-document-signing/README.adoc b/pqc-document-signing/README.adoc
index 5774afd..feb17e8 100644
--- a/pqc-document-signing/README.adoc
+++ b/pqc-document-signing/README.adoc
@@ -21,7 +21,42 @@ This example demonstrates how to build a secure document signing service using A
== Running HashiCorp Vault
-Run HashiCorp Vault using Docker:
+You can run HashiCorp Vault using either Camel JBang infrastructure support or Docker.
+
+=== Option 1: Using Camel JBang Infra (Recommended)
+
+Use Camel JBang's built-in infrastructure support to easily run HashiCorp Vault:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel infra run hashicorp vault
+----
+
+This will automatically start HashiCorp Vault configured with:
+
+* Root token: `myroot`
+* Port: `8200`
+* Secrets engine: `secret` (KV v2)
+
+Wait a few seconds for Vault to fully start before proceeding.
+
+To stop Vault later:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel infra stop hashicorp vault
+----
+
+**Note:** When using Camel infra, update `application.properties` to use port `8200`:
+
+[source,properties]
+----
+vault.port=8200
+----
+
+=== Option 2: Using Docker
+
+Alternatively, run HashiCorp Vault manually with Docker:
[source,sh]
----
@@ -59,7 +94,7 @@ After Vault is configured and running, start the Camel application:
[source,sh]
----
-$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run pqc-document-signing.yaml
+$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run --dep=mvn:org.springframework.vault:spring-vault-core:3.2.0 --dep=camel:pqc --properties=application.properties pqc-document-signing.yaml
----
The application will:
@@ -252,8 +287,10 @@ Configuration is managed in `application.properties`:
----
# Vault connection
vault.host=localhost
-vault.port=8201
-vault.token=myroot
+vault.port=8200 # Use 8200 for camel infra, 8201 for Docker
+vault.token=myToken # Vault authentication token
+vault.secrets.engine=secret
+vault.keys.prefix=pqc/keys
# Key rotation policy
key.max.usage.count=10 # Max signatures before rotation
@@ -265,13 +302,13 @@ key.rotation.check.period=60000 # Check interval (ms)
=== Connection to Vault Failed
-* Ensure Vault is running: `docker ps`
-* Verify the port in `application.properties` is set to `8201`
+* Ensure Vault is running: `docker ps` or check Camel infra status
+* Verify the port in `application.properties` matches Vault (8200 for camel infra, 8201 for Docker)
* Check the Vault token is correct (default: `myroot`)
=== Key Not Found in Vault
-* Check Vault UI at http://localhost:8201 (token: `myroot`)
+* Check Vault UI at http://localhost:8200 (camel infra) or http://localhost:8201 (Docker) (token: `myroot`)
* Verify the secrets engine is enabled: `vault secrets list`
* Check the key path: `secret/data/pqc/keys/document-signing-key`
@@ -289,6 +326,13 @@ To stop the Camel application, press `Ctrl+C`.
To stop HashiCorp Vault:
+If using Camel JBang infra:
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel infra stop hashicorp-vault
+----
+
+If using Docker:
[source,sh]
----
$ docker stop vault