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

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

commit 9a8e1062960219539d9e5aa028f1dfc2cd4a3aab
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Oct 10 11:42:53 2025 +0200

    CAMEL-22528 - Camel-PQC: Add AWS Secrets Manager lifecycle manager
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 components/camel-pqc/pom.xml                       |  15 +
 .../camel-pqc/src/main/docs/pqc-component.adoc     | 457 ++++++++++++-
 .../AwsSecretsManagerKeyLifecycleManager.java      | 717 +++++++++++++++++++++
 .../pqc/AwsSecretsManagerKeyLifecycleIT.java       | 274 ++++++++
 4 files changed, 1442 insertions(+), 21 deletions(-)

diff --git a/components/camel-pqc/pom.xml b/components/camel-pqc/pom.xml
index 47a6a4f0602e..ced4cf138cdf 100644
--- a/components/camel-pqc/pom.xml
+++ b/components/camel-pqc/pom.xml
@@ -55,6 +55,14 @@
             <optional>true</optional>
         </dependency>
 
+        <!-- AWS SDK for AwsSecretsManagerKeyLifecycleManager (optional) -->
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>secretsmanager</artifactId>
+            <version>${aws-java-sdk2-version}</version>
+            <optional>true</optional>
+        </dependency>
+
         <!-- for testing -->
         <dependency>
             <groupId>org.apache.camel</groupId>
@@ -79,5 +87,12 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-infra-aws-v2</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/components/camel-pqc/src/main/docs/pqc-component.adoc 
b/components/camel-pqc/src/main/docs/pqc-component.adoc
index 4b8f1ea4f2f0..37f64264d863 100644
--- a/components/camel-pqc/src/main/docs/pqc-component.adoc
+++ b/components/camel-pqc/src/main/docs/pqc-component.adoc
@@ -899,83 +899,498 @@ vault token create -policy=pqc-app          # For 
applications (verification onl
 vault token create -policy=pqc-app-signing-key  # For specific key access
 
--------------------------------------------------------------------------------
 
-**Production Security Recommendations:**
+==== AwsSecretsManagerKeyLifecycleManager
 
-1. **Principle of Least Privilege**: Use the most restrictive policy possible
-   - Applications that only verify signatures: Use `pqc-app` policy (public 
keys only)
-   - Signing services: Use `pqc-signing` policy or specific key policies
-   - Key management: Use `pqc-admin` policy
+An enterprise-grade implementation that integrates with AWS Secrets Manager 
for centralized secret management.
 
-2. **Limit Private Key Access**:
-   - Create specific policies per key ID in production
-   - Use Vault namespaces to isolate different environments
-   - Enable audit logging: `vault audit enable file 
file_path=/var/log/vault-audit.log`
+**Security Implementation:**
+
+This implementation uses industry-standard cryptographic key formats and 
separation of concerns:
+
+* **Private keys**: Stored in PKCS#8 format (RFC 5208) - the standard for 
private key encoding
+* **Public keys**: Stored in X.509/SubjectPublicKeyInfo format (RFC 5280) - 
the standard for public key encoding
+* **Separate storage**: Private keys, public keys, and metadata are stored as 
distinct AWS secrets
+* **Fine-grained IAM policies**: Enables different access policies for private 
keys (restricted) vs public keys (read-only)
+
+**Security Note:** This implementation stores PQC keys in AWS Secrets Manager. 
While this approach uses industry-standard formats and enables fine-grained 
access control, organizations with stringent security requirements should 
consider:
+
+* Using **AWS CloudHSM** for keys that must never be exportable
+* Implementing additional encryption layers using **AWS KMS Customer Master 
Keys (CMKs)**
+* Applying **strict IAM policies** limiting private key access to specific 
services or roles only
+* Regular **key rotation** and **CloudTrail audit log monitoring**
+* For production use, review and customize the IAM policies shown in the setup 
section
+
+**Features:**
+
+* Centralized secret management via AWS Secrets Manager
+* Industry-standard key formats (PKCS#8 for private keys, X.509 for public 
keys)
+* Separate storage for private and public keys (enables different IAM policies)
+* Automatic audit logging through AWS CloudTrail
+* Fine-grained access control with IAM policies
+* Encryption at rest with AWS KMS
+* Multi-region replication support
+* In-memory caching for performance
+* Uses AWS SDK v2 (software.amazon.awssdk) consistent with other Camel AWS 
components
+* LocalStack endpoint override support for testing
+
+**Use Cases:**
+
+* Production environments with existing AWS infrastructure
+* Multi-region deployments
+* Enterprise security and compliance requirements
+* Centralized key management across multiple applications
+* AWS Organizations and cross-account access scenarios
+
+**Dependencies:**
+
+To use AwsSecretsManagerKeyLifecycleManager, add the following optional 
dependency:
+
+[source,xml]
+--------------------------------------------------------------------------------
+<dependency>
+    <groupId>software.amazon.awssdk</groupId>
+    <artifactId>secretsmanager</artifactId>
+    <version>${aws-java-sdk2-version}</version>
+</dependency>
+--------------------------------------------------------------------------------
+
+**Example with SecretsManagerClient:**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Option 1: Using existing SecretsManagerClient (recommended when using 
camel-aws-secrets-manager)
+@BindToRegistry("secretsManagerClient")
+public SecretsManagerClient createSecretsManagerClient() {
+    return SecretsManagerClient.builder()
+        .region(Region.US_EAST_1)
+        .build(); // Uses default AWS credentials chain
+}
+
+@BindToRegistry("keyLifecycleManager")
+public AwsSecretsManagerKeyLifecycleManager createKeyManager() {
+    return new AwsSecretsManagerKeyLifecycleManager(
+        secretsManagerClient,   // Reuse existing client
+        "pqc/keys"             // Key prefix
+    );
+}
+
+// Generate a Dilithium key stored in AWS Secrets Manager
+KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "app-signing-key",
+    DilithiumParameterSpec.dilithium2);
+
+// Keys are stored as: pqc/keys/app-signing-key/private, /public, /metadata
+--------------------------------------------------------------------------------
+
+**Example with Direct Configuration:**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Option 2: Direct configuration with AWS credentials
+AwsSecretsManagerKeyLifecycleManager keyManager =
+    new AwsSecretsManagerKeyLifecycleManager(
+        "us-east-1",          // AWS region
+        "AKIAIOSFODNN7EXAMPLE", // access key (optional, uses default 
credentials if null)
+        "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", // secret key (optional)
+        "pqc/keys"            // key prefix (optional, defaults to "pqc/keys")
+    );
+
+// Generate and store key in AWS Secrets Manager
+KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "aws-key",
+    DilithiumParameterSpec.dilithium2);
+--------------------------------------------------------------------------------
+
+**Example with LocalStack (Testing):**
+
+[source,java]
+--------------------------------------------------------------------------------
+// Option 3: Configuration for LocalStack testing
+AwsSecretsManagerKeyLifecycleManager keyManager =
+    new AwsSecretsManagerKeyLifecycleManager(
+        "us-east-1",                    // region
+        "test",                         // access key for LocalStack
+        "test",                         // secret key for LocalStack
+        "pqc/test-keys",                // key prefix
+        "http://localhost:4566";         // LocalStack endpoint
+    );
+
+// Generate and store key in LocalStack
+KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "test-key",
+    DilithiumParameterSpec.dilithium2);
+--------------------------------------------------------------------------------
+
+**YAML Configuration:**
+
+[source,yaml]
+--------------------------------------------------------------------------------
+camel:
+  beans:
+    # Create SecretsManagerClient using default credentials
+    secretsManagerClient:
+      type: software.amazon.awssdk.services.secretsmanager.SecretsManagerClient
+      factoryMethod: builder
+      factoryBean:
+        type: 
software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder
+        properties:
+          region: "!software.amazon.awssdk.regions.Region#US_EAST_1"
+        factoryMethod: build
+
+    # Create AwsSecretsManagerKeyLifecycleManager
+    keyLifecycleManager:
+      type: 
org.apache.camel.component.pqc.lifecycle.AwsSecretsManagerKeyLifecycleManager
+      constructorArgs:
+        - "#bean:secretsManagerClient"
+        - "pqc/keys"
+--------------------------------------------------------------------------------
+
+**YAML Configuration with Explicit Credentials:**
+
+[source,yaml]
+--------------------------------------------------------------------------------
+camel:
+  beans:
+    # Create AwsSecretsManagerKeyLifecycleManager with explicit configuration
+    keyLifecycleManager:
+      type: 
org.apache.camel.component.pqc.lifecycle.AwsSecretsManagerKeyLifecycleManager
+      constructorArgs:
+        - "us-east-1"           # region
+        - "${AWS_ACCESS_KEY}"   # access key from environment
+        - "${AWS_SECRET_KEY}"   # secret key from environment
+        - "pqc/keys"            # key prefix
+        - null                  # endpoint override (null for production)
+--------------------------------------------------------------------------------
+
+**AWS Secrets Storage Structure:**
+
+Keys are stored in AWS Secrets Manager with separate secrets for private keys, 
public keys, and metadata. This separation enables fine-grained access control 
where applications can access public keys without having access to private keys.
+
+Each key creates three secrets:
+
+[source,text]
+--------------------------------------------------------------------------------
+pqc/keys/app-signing-key/private     # PKCS#8 private key (STRICT IAM POLICY)
+  {
+    "key": "MIIEvQIBADANBg...",        # Base64-encoded PKCS#8 private key
+    "format": "PKCS8",
+    "algorithm": "DILITHIUM"
+  }
+  Tags: ManagedBy=camel-pqc
 
-3. **Monitor and Rotate**:
-   - Monitor Vault audit logs for private key access
-   - Implement automated key rotation policies
-   - Set up alerts for unusual access patterns
+pqc/keys/app-signing-key/public      # X.509 public key (READ-ONLY IAM POLICY)
+  {
+    "key": "MIIBIjANBgkqhk...",        # Base64-encoded X.509 public key
+    "format": "X509",
+    "algorithm": "DILITHIUM"
+  }
+  Tags: ManagedBy=camel-pqc
 
-4. **Additional Security Layers**:
-   - Consider using Vault Transit engine to encrypt private keys before storage
-   - Use AppRole or Kubernetes authentication instead of tokens in production
-   - Enable MFA for administrative operations
+pqc/keys/app-signing-key/metadata    # Key metadata
+  {
+    "metadata": "rO0ABXNyAC9vcm...",  # Serialized KeyMetadata object
+    "keyId": "app-signing-key",
+    "algorithm": "DILITHIUM"
+  }
+  Tags: ManagedBy=camel-pqc
+--------------------------------------------------------------------------------
+
+**AWS Setup and IAM Policies:**
+
+To use AwsSecretsManagerKeyLifecycleManager, configure IAM policies for 
appropriate access control. The implementation stores private keys, public 
keys, and metadata separately to enable fine-grained access control.
+
+**Basic Policy (Full Access - Development/Testing):**
+
+[source,json]
+--------------------------------------------------------------------------------
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "PQCKeysFullAccess",
+      "Effect": "Allow",
+      "Action": [
+        "secretsmanager:CreateSecret",
+        "secretsmanager:GetSecretValue",
+        "secretsmanager:PutSecretValue",
+        "secretsmanager:DeleteSecret",
+        "secretsmanager:ListSecrets",
+        "secretsmanager:DescribeSecret",
+        "secretsmanager:TagResource"
+      ],
+      "Resource": "arn:aws:secretsmanager:*:*:secret:pqc/keys/*"
+    },
+    {
+      "Sid": "KMSAccess",
+      "Effect": "Allow",
+      "Action": [
+        "kms:Decrypt",
+        "kms:Encrypt",
+        "kms:GenerateDataKey"
+      ],
+      "Resource": "*",
+      "Condition": {
+        "StringEquals": {
+          "kms:ViaService": "secretsmanager.*.amazonaws.com"
+        }
+      }
+    }
+  ]
+}
+--------------------------------------------------------------------------------
+
+**Production Policies (Fine-Grained Access Control):**
+
+**POLICY 1: Admin Policy (Key Management Service)**
+
+Full access to generate, rotate, and manage keys:
+
+[source,json]
+--------------------------------------------------------------------------------
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "PQCKeyManagement",
+      "Effect": "Allow",
+      "Action": [
+        "secretsmanager:CreateSecret",
+        "secretsmanager:GetSecretValue",
+        "secretsmanager:PutSecretValue",
+        "secretsmanager:DeleteSecret",
+        "secretsmanager:UpdateSecret",
+        "secretsmanager:ListSecrets",
+        "secretsmanager:DescribeSecret",
+        "secretsmanager:TagResource"
+      ],
+      "Resource": "arn:aws:secretsmanager:*:*:secret:pqc/keys/*"
+    },
+    {
+      "Sid": "KMSFullAccess",
+      "Effect": "Allow",
+      "Action": [
+        "kms:Decrypt",
+        "kms:Encrypt",
+        "kms:GenerateDataKey",
+        "kms:DescribeKey"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+--------------------------------------------------------------------------------
+
+**POLICY 2: Signing Service Policy (Read Private Keys for Signing)**
+
+Read-only access to specific private keys for signing operations:
+
+[source,json]
+--------------------------------------------------------------------------------
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "ReadPrivateKeysForSigning",
+      "Effect": "Allow",
+      "Action": [
+        "secretsmanager:GetSecretValue",
+        "secretsmanager:DescribeSecret"
+      ],
+      "Resource": [
+        "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/private-*",
+        "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/public-*",
+        "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/metadata-*"
+      ]
+    },
+    {
+      "Sid": "ListKeys",
+      "Effect": "Allow",
+      "Action": "secretsmanager:ListSecrets",
+      "Resource": "*",
+      "Condition": {
+        "StringLike": {
+          "secretsmanager:Name": "pqc/keys/*"
+        }
+      }
+    },
+    {
+      "Sid": "KMSDecrypt",
+      "Effect": "Allow",
+      "Action": "kms:Decrypt",
+      "Resource": "*"
+    }
+  ]
+}
+--------------------------------------------------------------------------------
+
+**POLICY 3: Application Policy (Public Keys Only)**
+
+Read-only access to public keys for signature verification:
+
+[source,json]
+--------------------------------------------------------------------------------
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "ReadPublicKeysOnly",
+      "Effect": "Allow",
+      "Action": [
+        "secretsmanager:GetSecretValue",
+        "secretsmanager:DescribeSecret"
+      ],
+      "Resource": [
+        "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/public-*",
+        "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/metadata-*"
+      ]
+    },
+    {
+      "Sid": "DenyPrivateKeyAccess",
+      "Effect": "Deny",
+      "Action": "secretsmanager:GetSecretValue",
+      "Resource": "arn:aws:secretsmanager:*:*:secret:pqc/keys/*/private-*"
+    },
+    {
+      "Sid": "ListKeys",
+      "Effect": "Allow",
+      "Action": "secretsmanager:ListSecrets",
+      "Resource": "*"
+    },
+    {
+      "Sid": "KMSDecrypt",
+      "Effect": "Allow",
+      "Action": "kms:Decrypt",
+      "Resource": "*"
+    }
+  ]
+}
+--------------------------------------------------------------------------------
+
+**POLICY 4: Specific Key Access (Production Best Practice)**
+
+Limit access to specific key IDs only:
+
+[source,json]
+--------------------------------------------------------------------------------
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "AccessSpecificKeyOnly",
+      "Effect": "Allow",
+      "Action": [
+        "secretsmanager:GetSecretValue",
+        "secretsmanager:DescribeSecret"
+      ],
+      "Resource": [
+        
"arn:aws:secretsmanager:us-east-1:123456789012:secret:pqc/keys/app-signing-key/private-*",
+        
"arn:aws:secretsmanager:us-east-1:123456789012:secret:pqc/keys/app-signing-key/public-*",
+        
"arn:aws:secretsmanager:us-east-1:123456789012:secret:pqc/keys/app-signing-key/metadata-*"
+      ]
+    },
+    {
+      "Sid": "KMSDecrypt",
+      "Effect": "Allow",
+      "Action": "kms:Decrypt",
+      "Resource": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
+    }
+  ]
+}
+--------------------------------------------------------------------------------
+
+**Integration with camel-aws-secrets-manager:**
+
+AwsSecretsManagerKeyLifecycleManager can share the same SecretsManagerClient 
with the camel-aws-secrets-manager component:
+
+[source,java]
+--------------------------------------------------------------------------------
+// Reuse SecretsManagerClient from camel-aws-secrets-manager
+@BindToRegistry("keyLifecycleManager")
+public AwsSecretsManagerKeyLifecycleManager createKeyManager() {
+    SecretsManagerClient secretsManagerClient = context.getRegistry()
+        .lookupByNameAndType("secretsManagerClient", 
SecretsManagerClient.class);
+
+    return new AwsSecretsManagerKeyLifecycleManager(
+        secretsManagerClient,
+        "pqc/keys"
+    );
+}
+--------------------------------------------------------------------------------
 
 **Comparison of Implementations:**
 
 [options="header"]
 |===
-|Feature |FileBasedKeyLifecycleManager |InMemoryKeyLifecycleManager 
|HashicorpVaultKeyLifecycleManager
+|Feature |FileBasedKeyLifecycleManager |InMemoryKeyLifecycleManager 
|HashicorpVaultKeyLifecycleManager |AwsSecretsManagerKeyLifecycleManager
 
 |Persistence
 |✅ File system
 |❌ Memory only
 |✅ Vault backend
+|✅ AWS Secrets Manager
 
 |Distributed
 |❌ Single node
 |❌ Single node
 |✅ Multi-node
+|✅ Multi-region
 
 |Audit Logging
 |❌ Manual
 |❌ None
-|✅ Automatic
+|✅ Automatic (Vault)
+|✅ Automatic (CloudTrail)
 
 |Access Control
 |❌ File permissions
 |❌ None
 |✅ Vault policies
+|✅ IAM policies
 
 |Encryption at Rest
 |❌ OS-dependent
 |❌ N/A
-|✅ Always
+|✅ Always (Vault)
+|✅ Always (AWS KMS)
 
 |High Availability
 |❌ No
 |❌ No
 |✅ Yes (Vault HA)
+|✅ Yes (AWS Multi-AZ)
 
 |External Dependencies
 |❌ None
 |❌ None
 |✅ Vault + spring-vault
+|✅ AWS SDK v2
 
 |Caching
 |✅ Yes
 |✅ Yes
 |✅ Yes
+|✅ Yes
 
 |Spring Integration
 |❌ No
 |❌ No
 |✅ Yes
+|❌ No
+
+|Cloud Integration
+|❌ No
+|❌ No
+|✅ HCP Vault
+|✅ AWS Native
+
+|Multi-Region Support
+|❌ No
+|❌ No
+|❌ No
+|✅ Yes
 
 |Use Case
 |Single server
 |Testing/Dev
-|Production/Enterprise
+|Production/Enterprise (Vault)
+|Production/Enterprise (AWS)
 |===
 
 === Key Generation
diff --git 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/AwsSecretsManagerKeyLifecycleManager.java
 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/AwsSecretsManagerKeyLifecycleManager.java
new file mode 100644
index 000000000000..46a37c426630
--- /dev/null
+++ 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/lifecycle/AwsSecretsManagerKeyLifecycleManager.java
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc.lifecycle;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.component.pqc.PQCKeyEncapsulationAlgorithms;
+import org.apache.camel.component.pqc.PQCSignatureAlgorithms;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import 
software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+import 
software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.DeleteSecretRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+import software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.ListSecretsResponse;
+import 
software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.secretsmanager.model.SecretListEntry;
+import software.amazon.awssdk.services.secretsmanager.model.Tag;
+
+/**
+ * AWS Secrets Manager-based implementation of KeyLifecycleManager. Stores 
keys and metadata in AWS Secrets Manager with
+ * centralized secret management, audit logging, and fine-grained access 
control via IAM policies.
+ *
+ * Features: - Centralized secret management via AWS Secrets Manager - 
Automatic audit logging through AWS CloudTrail -
+ * Fine-grained access control with IAM policies - Encryption at rest with AWS 
KMS - Multi-region replication support -
+ * In-memory caching for performance - Industry-standard PKCS#8/X.509 key 
formats - Separate storage for private/public
+ * keys
+ *
+ * Configuration: - region: AWS region (e.g., us-east-1) - accessKey: AWS 
access key (optional, uses default credentials
+ * if not provided) - secretKey: AWS secret key (optional) - keyPrefix: Prefix 
for all secret names (default: pqc/keys)
+ * - endpointOverride: Custom endpoint for testing with LocalStack (optional)
+ *
+ * This implementation uses AWS SDK v2 (software.amazon.awssdk) consistent 
with other Camel AWS components.
+ */
+public class AwsSecretsManagerKeyLifecycleManager implements 
KeyLifecycleManager {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AwsSecretsManagerKeyLifecycleManager.class);
+
+    private final SecretsManagerClient secretsManagerClient;
+    private final String keyPrefix;
+    private final ConcurrentHashMap<String, KeyPair> keyCache = new 
ConcurrentHashMap<>();
+    private final ConcurrentHashMap<String, KeyMetadata> metadataCache = new 
ConcurrentHashMap<>();
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * Create an AwsSecretsManagerKeyLifecycleManager with an existing 
SecretsManagerClient
+     *
+     * @param secretsManagerClient Configured SecretsManagerClient instance
+     * @param keyPrefix            Prefix for secret names in AWS Secrets 
Manager
+     */
+    public AwsSecretsManagerKeyLifecycleManager(SecretsManagerClient 
secretsManagerClient, String keyPrefix) {
+        this.secretsManagerClient = secretsManagerClient;
+        this.keyPrefix = keyPrefix != null ? keyPrefix : "pqc/keys";
+
+        LOG.info("Initialized AwsSecretsManagerKeyLifecycleManager with 
keyPrefix: {}", this.keyPrefix);
+
+        try {
+            loadExistingKeys();
+        } catch (Exception e) {
+            LOG.warn("Failed to load existing keys from AWS Secrets Manager", 
e);
+        }
+    }
+
+    /**
+     * Create an AwsSecretsManagerKeyLifecycleManager with basic configuration
+     *
+     * @param region AWS region (e.g., us-east-1)
+     */
+    public AwsSecretsManagerKeyLifecycleManager(String region) {
+        this(region, null, null, null);
+    }
+
+    /**
+     * Create an AwsSecretsManagerKeyLifecycleManager with custom configuration
+     *
+     * @param region    AWS region (e.g., us-east-1)
+     * @param accessKey AWS access key (optional, uses default credentials if 
null)
+     * @param secretKey AWS secret key (optional)
+     * @param keyPrefix Prefix for secret names
+     */
+    public AwsSecretsManagerKeyLifecycleManager(String region, String 
accessKey, String secretKey, String keyPrefix) {
+        this(region, accessKey, secretKey, keyPrefix, null);
+    }
+
+    /**
+     * Create an AwsSecretsManagerKeyLifecycleManager with full configuration 
including endpoint override
+     *
+     * @param region           AWS region (e.g., us-east-1)
+     * @param accessKey        AWS access key (optional, uses default 
credentials if null)
+     * @param secretKey        AWS secret key (optional)
+     * @param keyPrefix        Prefix for secret names
+     * @param endpointOverride Custom endpoint for testing (optional, e.g., 
http://localhost:4566 for LocalStack)
+     */
+    public AwsSecretsManagerKeyLifecycleManager(String region, String 
accessKey, String secretKey, String keyPrefix,
+                                                String endpointOverride) {
+        this.keyPrefix = keyPrefix != null ? keyPrefix : "pqc/keys";
+
+        // Build SecretsManagerClient
+        SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
+
+        if (region != null) {
+            clientBuilder.region(Region.of(region));
+        }
+
+        if (accessKey != null && secretKey != null) {
+            AwsBasicCredentials credentials = 
AwsBasicCredentials.create(accessKey, secretKey);
+            
clientBuilder.credentialsProvider(StaticCredentialsProvider.create(credentials));
+        }
+
+        if (endpointOverride != null) {
+            clientBuilder.endpointOverride(URI.create(endpointOverride));
+        }
+
+        this.secretsManagerClient = clientBuilder.build();
+
+        LOG.info("Initialized AwsSecretsManagerKeyLifecycleManager with 
region: {}, keyPrefix: {}, endpointOverride: {}",
+                region, this.keyPrefix, endpointOverride);
+
+        try {
+            loadExistingKeys();
+        } catch (Exception e) {
+            LOG.warn("Failed to load existing keys from AWS Secrets Manager", 
e);
+        }
+    }
+
+    @Override
+    public KeyPair generateKeyPair(String algorithm, String keyId) throws 
Exception {
+        return generateKeyPair(algorithm, keyId, null);
+    }
+
+    @Override
+    public KeyPair generateKeyPair(String algorithm, String keyId, Object 
parameterSpec) throws Exception {
+        LOG.info("Generating key pair for algorithm: {}, keyId: {}", 
algorithm, keyId);
+
+        KeyPairGenerator generator;
+        String provider = determineProvider(algorithm);
+
+        if (provider != null) {
+            generator = 
KeyPairGenerator.getInstance(getAlgorithmName(algorithm), provider);
+        } else {
+            generator = 
KeyPairGenerator.getInstance(getAlgorithmName(algorithm));
+        }
+
+        // Initialize with parameter spec if provided
+        if (parameterSpec != null) {
+            if (parameterSpec instanceof AlgorithmParameterSpec) {
+                generator.initialize((AlgorithmParameterSpec) parameterSpec, 
new SecureRandom());
+            } else if (parameterSpec instanceof Integer) {
+                generator.initialize((Integer) parameterSpec, new 
SecureRandom());
+            }
+        } else {
+            // Use default parameter spec for the algorithm
+            AlgorithmParameterSpec defaultSpec = 
getDefaultParameterSpec(algorithm);
+            if (defaultSpec != null) {
+                generator.initialize(defaultSpec, new SecureRandom());
+            } else {
+                generator.initialize(getDefaultKeySize(algorithm), new 
SecureRandom());
+            }
+        }
+
+        KeyPair keyPair = generator.generateKeyPair();
+
+        // Create metadata
+        KeyMetadata metadata = new KeyMetadata(keyId, algorithm);
+        metadata.setDescription("Generated on " + new Date());
+
+        // Store the key
+        storeKey(keyId, keyPair, metadata);
+
+        LOG.info("Generated key pair in AWS Secrets Manager: {}", metadata);
+        return keyPair;
+    }
+
+    @Override
+    public byte[] exportKey(KeyPair keyPair, KeyFormat format, boolean 
includePrivate) throws Exception {
+        return KeyFormatConverter.exportKeyPair(keyPair, format, 
includePrivate);
+    }
+
+    @Override
+    public byte[] exportPublicKey(KeyPair keyPair, KeyFormat format) throws 
Exception {
+        return KeyFormatConverter.exportPublicKey(keyPair.getPublic(), format);
+    }
+
+    @Override
+    public KeyPair importKey(byte[] keyData, KeyFormat format, String 
algorithm) throws Exception {
+        // Try to import as private key first (which includes public key)
+        try {
+            PrivateKey privateKey = 
KeyFormatConverter.importPrivateKey(keyData, format, 
getAlgorithmName(algorithm));
+            LOG.warn("Importing private key only - public key derivation may 
be needed");
+            return new KeyPair(null, privateKey);
+        } catch (Exception e) {
+            // Try as public key only
+            PublicKey publicKey = KeyFormatConverter.importPublicKey(keyData, 
format, getAlgorithmName(algorithm));
+            return new KeyPair(publicKey, null);
+        }
+    }
+
+    @Override
+    public KeyPair rotateKey(String oldKeyId, String newKeyId, String 
algorithm) throws Exception {
+        LOG.info("Rotating key from {} to {}", oldKeyId, newKeyId);
+
+        // Get old key metadata
+        KeyMetadata oldMetadata = getKeyMetadata(oldKeyId);
+        if (oldMetadata == null) {
+            throw new IllegalArgumentException("Old key not found: " + 
oldKeyId);
+        }
+
+        // Mark old key as deprecated
+        oldMetadata.setStatus(KeyMetadata.KeyStatus.DEPRECATED);
+        updateKeyMetadata(oldKeyId, oldMetadata);
+
+        // Generate new key
+        KeyPair newKeyPair = generateKeyPair(algorithm, newKeyId);
+
+        LOG.info("Key rotation completed in AWS Secrets Manager: {} -> {}", 
oldKeyId, newKeyId);
+        return newKeyPair;
+    }
+
+    @Override
+    public void storeKey(String keyId, KeyPair keyPair, KeyMetadata metadata) 
throws Exception {
+        // Use PKCS#8 format for private key and X.509 for public key 
(industry standard)
+        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); // PKCS#8 
format
+        byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); // 
X.509/SubjectPublicKeyInfo format
+        String privateKeyBase64 = 
Base64.getEncoder().encodeToString(privateKeyBytes);
+        String publicKeyBase64 = 
Base64.getEncoder().encodeToString(publicKeyBytes);
+        String metadataBase64 = serializeMetadata(metadata);
+
+        // Store private key separately (strict IAM policy recommended in 
production)
+        String privateSecretName = getSecretName(keyId, "private");
+        String privateSecretValue = objectMapper.writeValueAsString(new 
SecretData(
+                privateKeyBase64,
+                "PKCS8",
+                metadata.getAlgorithm()));
+
+        createOrUpdateSecret(privateSecretName, privateSecretValue, "PQC 
Private Key: " + keyId);
+
+        // Store public key separately (can have read-only IAM policy)
+        String publicSecretName = getSecretName(keyId, "public");
+        String publicSecretValue = objectMapper.writeValueAsString(new 
SecretData(
+                publicKeyBase64,
+                "X509",
+                metadata.getAlgorithm()));
+
+        createOrUpdateSecret(publicSecretName, publicSecretValue, "PQC Public 
Key: " + keyId);
+
+        // Store metadata separately
+        String metadataSecretName = getSecretName(keyId, "metadata");
+        String metadataSecretValue = objectMapper.writeValueAsString(new 
MetadataData(
+                metadataBase64,
+                keyId,
+                metadata.getAlgorithm()));
+
+        createOrUpdateSecret(metadataSecretName, metadataSecretValue, "PQC Key 
Metadata: " + keyId);
+
+        // Update caches
+        keyCache.put(keyId, keyPair);
+        metadataCache.put(keyId, metadata);
+
+        LOG.debug("Stored private key, public key, and metadata separately in 
AWS Secrets Manager for: {}", keyId);
+    }
+
+    @Override
+    public KeyPair getKey(String keyId) throws Exception {
+        // Check cache first
+        if (keyCache.containsKey(keyId)) {
+            return keyCache.get(keyId);
+        }
+
+        // Read private key from AWS Secrets Manager
+        String privateSecretName = getSecretName(keyId, "private");
+        GetSecretValueResponse privateResponse = getSecret(privateSecretName);
+
+        // Read public key from AWS Secrets Manager
+        String publicSecretName = getSecretName(keyId, "public");
+        GetSecretValueResponse publicResponse = getSecret(publicSecretName);
+
+        // Parse secret values
+        SecretData privateData = 
objectMapper.readValue(privateResponse.secretString(), SecretData.class);
+        SecretData publicData = 
objectMapper.readValue(publicResponse.secretString(), SecretData.class);
+
+        byte[] privateKeyBytes = 
Base64.getDecoder().decode(privateData.getKey());
+        byte[] publicKeyBytes = 
Base64.getDecoder().decode(publicData.getKey());
+
+        // Use KeyFormatConverter to reconstruct keys from standard formats
+        PrivateKey privateKey = 
KeyFormatConverter.importPrivateKey(privateKeyBytes,
+                KeyLifecycleManager.KeyFormat.DER, 
getAlgorithmName(privateData.getAlgorithm()));
+        PublicKey publicKey = 
KeyFormatConverter.importPublicKey(publicKeyBytes,
+                KeyLifecycleManager.KeyFormat.DER, 
getAlgorithmName(publicData.getAlgorithm()));
+
+        KeyPair keyPair = new KeyPair(publicKey, privateKey);
+
+        // Cache it
+        keyCache.put(keyId, keyPair);
+        return keyPair;
+    }
+
+    @Override
+    public KeyMetadata getKeyMetadata(String keyId) throws Exception {
+        // Check cache first
+        if (metadataCache.containsKey(keyId)) {
+            return metadataCache.get(keyId);
+        }
+
+        // Read metadata from AWS Secrets Manager
+        String metadataSecretName = getSecretName(keyId, "metadata");
+
+        try {
+            GetSecretValueResponse response = getSecret(metadataSecretName);
+            MetadataData metadataData = 
objectMapper.readValue(response.secretString(), MetadataData.class);
+            KeyMetadata metadata = 
deserializeMetadata(metadataData.getMetadata());
+
+            // Cache it
+            metadataCache.put(keyId, metadata);
+            return metadata;
+        } catch (ResourceNotFoundException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public void updateKeyMetadata(String keyId, KeyMetadata metadata) throws 
Exception {
+        // Read existing key pair
+        KeyPair keyPair = getKey(keyId);
+
+        // Store updated metadata with existing key pair
+        storeKey(keyId, keyPair, metadata);
+    }
+
+    @Override
+    public void deleteKey(String keyId) throws Exception {
+        // Delete private key, public key, and metadata separately
+        deleteSecret(getSecretName(keyId, "private"));
+        deleteSecret(getSecretName(keyId, "public"));
+        deleteSecret(getSecretName(keyId, "metadata"));
+
+        keyCache.remove(keyId);
+        metadataCache.remove(keyId);
+
+        LOG.info("Deleted private key, public key, and metadata from AWS 
Secrets Manager: {}", keyId);
+    }
+
+    @Override
+    public List<KeyMetadata> listKeys() throws Exception {
+        // List all secrets with the key prefix
+        List<KeyMetadata> metadataList = new ArrayList<>();
+        String nextToken = null;
+
+        do {
+            ListSecretsRequest.Builder requestBuilder = 
ListSecretsRequest.builder()
+                    .maxResults(100);
+
+            if (nextToken != null) {
+                requestBuilder.nextToken(nextToken);
+            }
+
+            ListSecretsResponse response = 
secretsManagerClient.listSecrets(requestBuilder.build());
+
+            for (SecretListEntry secret : response.secretList()) {
+                String secretName = secret.name();
+                // Only process metadata secrets to avoid duplicates
+                if (secretName.startsWith(keyPrefix) && 
secretName.endsWith("/metadata")) {
+                    String keyId = extractKeyIdFromSecretName(secretName);
+                    try {
+                        KeyMetadata metadata = getKeyMetadata(keyId);
+                        if (metadata != null) {
+                            metadataList.add(metadata);
+                        }
+                    } catch (Exception e) {
+                        LOG.warn("Failed to load metadata for key: {}", keyId, 
e);
+                    }
+                }
+            }
+
+            nextToken = response.nextToken();
+        } while (nextToken != null);
+
+        return metadataList;
+    }
+
+    @Override
+    public boolean needsRotation(String keyId, Duration maxAge, long maxUsage) 
throws Exception {
+        KeyMetadata metadata = getKeyMetadata(keyId);
+        if (metadata == null) {
+            return false;
+        }
+
+        if (metadata.needsRotation()) {
+            return true;
+        }
+
+        if (maxAge != null && metadata.getAgeInDays() > maxAge.toDays()) {
+            return true;
+        }
+
+        if (maxUsage > 0 && metadata.getUsageCount() >= maxUsage) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void expireKey(String keyId) throws Exception {
+        KeyMetadata metadata = getKeyMetadata(keyId);
+        if (metadata != null) {
+            metadata.setStatus(KeyMetadata.KeyStatus.EXPIRED);
+            updateKeyMetadata(keyId, metadata);
+            LOG.info("Expired key in AWS Secrets Manager: {}", keyId);
+        }
+    }
+
+    @Override
+    public void revokeKey(String keyId, String reason) throws Exception {
+        KeyMetadata metadata = getKeyMetadata(keyId);
+        if (metadata != null) {
+            metadata.setStatus(KeyMetadata.KeyStatus.REVOKED);
+            metadata.setDescription((metadata.getDescription() != null ? 
metadata.getDescription() + "; " : "")
+                                    + "Revoked: " + reason);
+            updateKeyMetadata(keyId, metadata);
+            LOG.info("Revoked key in AWS Secrets Manager: {} - {}", keyId, 
reason);
+        }
+    }
+
+    private void loadExistingKeys() throws Exception {
+        List<KeyMetadata> keys = listKeys();
+        if (!keys.isEmpty()) {
+            LOG.info("Found {} existing keys in AWS Secrets Manager", 
keys.size());
+            for (KeyMetadata metadata : keys) {
+                LOG.debug("Loaded existing key from AWS Secrets Manager: {}", 
metadata);
+            }
+        }
+    }
+
+    private String getSecretName(String keyId, String type) {
+        return keyPrefix + "/" + keyId + "/" + type;
+    }
+
+    private String extractKeyIdFromSecretName(String secretName) {
+        // Extract keyId from pattern: pqc/keys/{keyId}/metadata
+        String withoutPrefix = secretName.substring(keyPrefix.length() + 1);
+        int lastSlash = withoutPrefix.lastIndexOf('/');
+        return withoutPrefix.substring(0, lastSlash);
+    }
+
+    private void createOrUpdateSecret(String secretName, String secretValue, 
String description) {
+        try {
+            // Try to update existing secret
+            PutSecretValueRequest putRequest = PutSecretValueRequest.builder()
+                    .secretId(secretName)
+                    .secretString(secretValue)
+                    .build();
+            secretsManagerClient.putSecretValue(putRequest);
+            LOG.debug("Updated secret: {}", secretName);
+        } catch (ResourceNotFoundException e) {
+            // Secret doesn't exist, create it
+            CreateSecretRequest createRequest = CreateSecretRequest.builder()
+                    .name(secretName)
+                    .secretString(secretValue)
+                    .description(description)
+                    
.tags(Tag.builder().key("ManagedBy").value("camel-pqc").build())
+                    .build();
+            secretsManagerClient.createSecret(createRequest);
+            LOG.debug("Created secret: {}", secretName);
+        }
+    }
+
+    private GetSecretValueResponse getSecret(String secretName) {
+        GetSecretValueRequest request = GetSecretValueRequest.builder()
+                .secretId(secretName)
+                .build();
+        return secretsManagerClient.getSecretValue(request);
+    }
+
+    private void deleteSecret(String secretName) {
+        try {
+            DeleteSecretRequest request = DeleteSecretRequest.builder()
+                    .secretId(secretName)
+                    .forceDeleteWithoutRecovery(true)
+                    .build();
+            secretsManagerClient.deleteSecret(request);
+            LOG.debug("Deleted secret: {}", secretName);
+        } catch (ResourceNotFoundException e) {
+            LOG.debug("Secret not found, skipping deletion: {}", secretName);
+        }
+    }
+
+    private String serializeMetadata(KeyMetadata metadata) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            oos.writeObject(metadata);
+        }
+        return Base64.getEncoder().encodeToString(baos.toByteArray());
+    }
+
+    private KeyMetadata deserializeMetadata(String base64) throws Exception {
+        byte[] data = Base64.getDecoder().decode(base64);
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
+            return (KeyMetadata) ois.readObject();
+        }
+    }
+
+    private String determineProvider(String algorithm) {
+        try {
+            PQCSignatureAlgorithms sigAlg = 
PQCSignatureAlgorithms.valueOf(algorithm);
+            return sigAlg.getBcProvider();
+        } catch (IllegalArgumentException e1) {
+            try {
+                PQCKeyEncapsulationAlgorithms kemAlg = 
PQCKeyEncapsulationAlgorithms.valueOf(algorithm);
+                return kemAlg.getBcProvider();
+            } catch (IllegalArgumentException e2) {
+                return null;
+            }
+        }
+    }
+
+    private String getAlgorithmName(String algorithm) {
+        try {
+            return PQCSignatureAlgorithms.valueOf(algorithm).getAlgorithm();
+        } catch (IllegalArgumentException e1) {
+            try {
+                return 
PQCKeyEncapsulationAlgorithms.valueOf(algorithm).getAlgorithm();
+            } catch (IllegalArgumentException e2) {
+                return algorithm;
+            }
+        }
+    }
+
+    private AlgorithmParameterSpec getDefaultParameterSpec(String algorithm) {
+        // Provide default parameter specs for PQC algorithms
+        try {
+            switch (algorithm) {
+                case "DILITHIUM":
+                    return 
org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec.dilithium2;
+                case "MLDSA":
+                case "SLHDSA":
+                    // These use default initialization
+                    return null;
+                case "FALCON":
+                    return 
org.bouncycastle.pqc.jcajce.spec.FalconParameterSpec.falcon_512;
+                case "SPHINCSPLUS":
+                    return 
org.bouncycastle.pqc.jcajce.spec.SPHINCSPlusParameterSpec.sha2_128s;
+                case "XMSS":
+                    return new 
org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec(
+                            10,
+                            
org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec.SHA256);
+                case "XMSSMT":
+                    return 
org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec.XMSSMT_SHA2_20d2_256;
+                case "LMS":
+                case "HSS":
+                    return new 
org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec(
+                            
org.bouncycastle.pqc.crypto.lms.LMSigParameters.lms_sha256_n32_h10,
+                            
org.bouncycastle.pqc.crypto.lms.LMOtsParameters.sha256_n32_w4);
+                case "MLKEM":
+                case "KYBER":
+                    // These use default initialization
+                    return null;
+                case "NTRU":
+                    return 
org.bouncycastle.pqc.jcajce.spec.NTRUParameterSpec.ntruhps2048509;
+                case "NTRULPRime":
+                    return 
org.bouncycastle.pqc.jcajce.spec.NTRULPRimeParameterSpec.ntrulpr653;
+                case "SNTRUPrime":
+                    return 
org.bouncycastle.pqc.jcajce.spec.SNTRUPrimeParameterSpec.sntrup761;
+                case "SABER":
+                    return 
org.bouncycastle.pqc.jcajce.spec.SABERParameterSpec.lightsaberkem128r3;
+                case "FRODO":
+                    return 
org.bouncycastle.pqc.jcajce.spec.FrodoParameterSpec.frodokem640aes;
+                case "BIKE":
+                    return 
org.bouncycastle.pqc.jcajce.spec.BIKEParameterSpec.bike128;
+                case "HQC":
+                    return 
org.bouncycastle.pqc.jcajce.spec.HQCParameterSpec.hqc128;
+                case "CMCE":
+                    return 
org.bouncycastle.pqc.jcajce.spec.CMCEParameterSpec.mceliece348864;
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            LOG.warn("Failed to create default parameter spec for algorithm: 
{}", algorithm, e);
+            return null;
+        }
+    }
+
+    private int getDefaultKeySize(String algorithm) {
+        // Default key sizes for different algorithms
+        // For PQC algorithms, key size is usually determined by parameter 
specs
+        return 256;
+    }
+
+    /**
+     * Get the underlying SecretsManagerClient for advanced operations
+     */
+    public SecretsManagerClient getSecretsManagerClient() {
+        return secretsManagerClient;
+    }
+
+    /**
+     * Helper class for storing secret data in JSON format
+     */
+    private static class SecretData {
+        private String key;
+        private String format;
+        private String algorithm;
+
+        public SecretData() {
+        }
+
+        public SecretData(String key, String format, String algorithm) {
+            this.key = key;
+            this.format = format;
+            this.algorithm = algorithm;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public void setKey(String key) {
+            this.key = key;
+        }
+
+        public String getFormat() {
+            return format;
+        }
+
+        public void setFormat(String format) {
+            this.format = format;
+        }
+
+        public String getAlgorithm() {
+            return algorithm;
+        }
+
+        public void setAlgorithm(String algorithm) {
+            this.algorithm = algorithm;
+        }
+    }
+
+    /**
+     * Helper class for storing metadata in JSON format
+     */
+    private static class MetadataData {
+        private String metadata;
+        private String keyId;
+        private String algorithm;
+
+        public MetadataData() {
+        }
+
+        public MetadataData(String metadata, String keyId, String algorithm) {
+            this.metadata = metadata;
+            this.keyId = keyId;
+            this.algorithm = algorithm;
+        }
+
+        public String getMetadata() {
+            return metadata;
+        }
+
+        public void setMetadata(String metadata) {
+            this.metadata = metadata;
+        }
+
+        public String getKeyId() {
+            return keyId;
+        }
+
+        public void setKeyId(String keyId) {
+            this.keyId = keyId;
+        }
+
+        public String getAlgorithm() {
+            return algorithm;
+        }
+
+        public void setAlgorithm(String algorithm) {
+            this.algorithm = algorithm;
+        }
+    }
+}
diff --git 
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/AwsSecretsManagerKeyLifecycleIT.java
 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/AwsSecretsManagerKeyLifecycleIT.java
new file mode 100644
index 000000000000..5aab9f12ee04
--- /dev/null
+++ 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/AwsSecretsManagerKeyLifecycleIT.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc;
+
+import java.security.KeyPair;
+import java.security.Security;
+import java.time.Duration;
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import 
org.apache.camel.component.pqc.lifecycle.AwsSecretsManagerKeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager;
+import org.apache.camel.component.pqc.lifecycle.KeyMetadata;
+import org.apache.camel.test.infra.aws.common.services.AWSService;
+import org.apache.camel.test.infra.aws2.services.AWSServiceFactory;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.FalconParameterSpec;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * End-to-end integration test for AwsSecretsManagerKeyLifecycleManager. Tests 
key generation, storage, retrieval,
+ * rotation, and usage in Camel routes with a real AWS Secrets Manager 
instance via testcontainers (LocalStack).
+ */
+public class AwsSecretsManagerKeyLifecycleIT extends CamelTestSupport {
+
+    @RegisterExtension
+    public static AWSService service = 
AWSServiceFactory.createSecretsManagerService();
+
+    private AwsSecretsManagerKeyLifecycleManager keyManager;
+
+    @EndpointInject("mock:signed")
+    private MockEndpoint mockSigned;
+
+    @EndpointInject("mock:verified")
+    private MockEndpoint mockVerified;
+
+    @BeforeAll
+    public static void startup() {
+        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == 
null) {
+            Security.addProvider(new BouncyCastlePQCProvider());
+        }
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        // Create AwsSecretsManagerKeyLifecycleManager using AWS test 
infrastructure
+        String accessKey = 
service.getConnectionProperties().getProperty("aws.access.key");
+        String secretKey = 
service.getConnectionProperties().getProperty("aws.secret.key");
+        String region = 
service.getConnectionProperties().getProperty("aws.region");
+        String protocol = 
service.getConnectionProperties().getProperty("aws.protocol");
+        String host = 
service.getConnectionProperties().getProperty("aws.host");
+        String endpointOverride = protocol + "://" + host;
+
+        keyManager = new AwsSecretsManagerKeyLifecycleManager(
+                region,
+                accessKey,
+                secretKey,
+                "pqc/test-keys",
+                endpointOverride);
+
+        // Register the manager in the registry
+        context.getRegistry().bind("keyLifecycleManager", keyManager);
+
+        return context;
+    }
+
+    @Test
+    public void testGenerateAndStoreKeyInSecretsManager() throws Exception {
+        // Generate a Dilithium key
+        KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", 
"test-dilithium-key", DilithiumParameterSpec.dilithium2);
+
+        assertNotNull(keyPair);
+        assertNotNull(keyPair.getPublic());
+        assertNotNull(keyPair.getPrivate());
+
+        // Verify metadata was created
+        KeyMetadata metadata = keyManager.getKeyMetadata("test-dilithium-key");
+        assertNotNull(metadata);
+        assertEquals("test-dilithium-key", metadata.getKeyId());
+        assertEquals("DILITHIUM", metadata.getAlgorithm());
+        assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+    }
+
+    @Test
+    public void testRetrieveKeyFromSecretsManager() throws Exception {
+        // Generate and store key
+        keyManager.generateKeyPair("FALCON", "test-falcon-key", 
FalconParameterSpec.falcon_512);
+
+        // Clear cache to force AWS Secrets Manager read
+        // (In production this would simulate a different process/server 
accessing the key)
+
+        // Retrieve key from AWS Secrets Manager
+        KeyPair retrieved = keyManager.getKey("test-falcon-key");
+        assertNotNull(retrieved);
+        assertNotNull(retrieved.getPublic());
+        assertNotNull(retrieved.getPrivate());
+
+        // Verify metadata
+        KeyMetadata metadata = keyManager.getKeyMetadata("test-falcon-key");
+        assertEquals("FALCON", metadata.getAlgorithm());
+    }
+
+    @Test
+    public void testKeyRotation() throws Exception {
+        // Generate initial key
+        keyManager.generateKeyPair("DILITHIUM", "rotation-key-old", 
DilithiumParameterSpec.dilithium2);
+
+        KeyMetadata oldMetadata = 
keyManager.getKeyMetadata("rotation-key-old");
+        assertEquals(KeyMetadata.KeyStatus.ACTIVE, oldMetadata.getStatus());
+
+        // Rotate the key
+        KeyPair newKeyPair = keyManager.rotateKey("rotation-key-old", 
"rotation-key-new", "DILITHIUM");
+        assertNotNull(newKeyPair);
+
+        // Verify old key is deprecated
+        oldMetadata = keyManager.getKeyMetadata("rotation-key-old");
+        assertEquals(KeyMetadata.KeyStatus.DEPRECATED, 
oldMetadata.getStatus());
+
+        // Verify new key is active
+        KeyMetadata newMetadata = 
keyManager.getKeyMetadata("rotation-key-new");
+        assertEquals(KeyMetadata.KeyStatus.ACTIVE, newMetadata.getStatus());
+    }
+
+    @Test
+    public void testNeedsRotation() throws Exception {
+        keyManager.generateKeyPair("DILITHIUM", "rotation-check-key", 
DilithiumParameterSpec.dilithium2);
+
+        // New key should not need rotation
+        assertFalse(keyManager.needsRotation("rotation-check-key", 
Duration.ofDays(90), 10000));
+
+        // Simulate old key by setting next rotation time in the past
+        KeyMetadata metadata = keyManager.getKeyMetadata("rotation-check-key");
+        metadata.setNextRotationAt(java.time.Instant.now().minusSeconds(1));
+        keyManager.updateKeyMetadata("rotation-check-key", metadata);
+
+        // Now it should need rotation
+        assertTrue(keyManager.needsRotation("rotation-check-key", 
Duration.ofDays(90), 10000));
+    }
+
+    @Test
+    public void testListKeys() throws Exception {
+        // Generate multiple keys
+        keyManager.generateKeyPair("DILITHIUM", "list-key-1", 
DilithiumParameterSpec.dilithium2);
+        keyManager.generateKeyPair("FALCON", "list-key-2", 
FalconParameterSpec.falcon_512);
+        keyManager.generateKeyPair("DILITHIUM", "list-key-3", 
DilithiumParameterSpec.dilithium3);
+
+        // List all keys
+        List<KeyMetadata> keys = keyManager.listKeys();
+        assertTrue(keys.size() >= 3, "Should have at least 3 keys");
+
+        // Verify all our keys are present
+        assertTrue(keys.stream().anyMatch(k -> 
k.getKeyId().equals("list-key-1")));
+        assertTrue(keys.stream().anyMatch(k -> 
k.getKeyId().equals("list-key-2")));
+        assertTrue(keys.stream().anyMatch(k -> 
k.getKeyId().equals("list-key-3")));
+    }
+
+    @Test
+    public void testExpireAndRevokeKey() throws Exception {
+        // Test expiration
+        keyManager.generateKeyPair("DILITHIUM", "expire-key", 
DilithiumParameterSpec.dilithium2);
+        keyManager.expireKey("expire-key");
+
+        KeyMetadata expiredMetadata = keyManager.getKeyMetadata("expire-key");
+        assertEquals(KeyMetadata.KeyStatus.EXPIRED, 
expiredMetadata.getStatus());
+
+        // Test revocation
+        keyManager.generateKeyPair("DILITHIUM", "revoke-key", 
DilithiumParameterSpec.dilithium2);
+        keyManager.revokeKey("revoke-key", "Key compromised in test");
+
+        KeyMetadata revokedMetadata = keyManager.getKeyMetadata("revoke-key");
+        assertEquals(KeyMetadata.KeyStatus.REVOKED, 
revokedMetadata.getStatus());
+        assertTrue(revokedMetadata.getDescription().contains("Revoked: Key 
compromised in test"));
+    }
+
+    @Test
+    public void testDeleteKey() throws Exception {
+        keyManager.generateKeyPair("DILITHIUM", "delete-key", 
DilithiumParameterSpec.dilithium2);
+        assertNotNull(keyManager.getKey("delete-key"));
+
+        keyManager.deleteKey("delete-key");
+
+        // Should throw exception when trying to get deleted key
+        assertThrows(Exception.class, () -> keyManager.getKey("delete-key"));
+    }
+
+    @Test
+    public void testExportAndImportKey() throws Exception {
+        KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", 
"export-key", DilithiumParameterSpec.dilithium2);
+
+        // Export public key as PEM
+        byte[] exported = keyManager.exportPublicKey(keyPair, 
KeyLifecycleManager.KeyFormat.PEM);
+        assertNotNull(exported);
+        assertTrue(exported.length > 0);
+
+        String pemString = new String(exported);
+        assertTrue(pemString.contains("-----BEGIN PUBLIC KEY-----"));
+        assertTrue(pemString.contains("-----END PUBLIC KEY-----"));
+
+        // Import the key
+        KeyPair imported = keyManager.importKey(exported, 
KeyLifecycleManager.KeyFormat.PEM, "DILITHIUM");
+        assertNotNull(imported);
+        assertNotNull(imported.getPublic());
+    }
+
+    @Test
+    public void testMetadataTracking() throws Exception {
+        // Generate key
+        keyManager.generateKeyPair("DILITHIUM", "tracking-key", 
DilithiumParameterSpec.dilithium2);
+
+        // Get initial metadata
+        KeyMetadata metadata = keyManager.getKeyMetadata("tracking-key");
+        assertEquals(0, metadata.getUsageCount());
+        assertEquals(KeyMetadata.KeyStatus.ACTIVE, metadata.getStatus());
+
+        // Simulate usage by updating metadata
+        for (int i = 0; i < 5; i++) {
+            metadata.updateLastUsed();
+        }
+        keyManager.updateKeyMetadata("tracking-key", metadata);
+
+        // Verify usage was tracked
+        metadata = keyManager.getKeyMetadata("tracking-key");
+        assertEquals(5, metadata.getUsageCount());
+        assertNotNull(metadata.getLastUsedAt());
+
+        // Verify age calculation
+        long ageInDays = metadata.getAgeInDays();
+        assertEquals(0, ageInDays); // Should be 0 for a newly created key
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // Signing route using PQC component with AWS Secrets 
Manager-stored key
+                from("direct:sign")
+                        
.to("pqc:sign?operation=sign&signatureAlgorithm=DILITHIUM")
+                        .to("mock:signed")
+                        
.to("pqc:verify?operation=verify&signatureAlgorithm=DILITHIUM")
+                        .to("mock:verified");
+            }
+        };
+    }
+}

Reply via email to