PQC Key Lifecycle Management

The PQC component provides comprehensive key lifecycle management capabilities for Post-Quantum Cryptography keys. This includes key generation, storage, rotation, expiration, revocation, and format conversion.

Key lifecycle management is critical for maintaining security in production environments, especially with stateful hash-based signature algorithms (XMSS, XMSSMT, LMS, HSS) that have limited signature capacity.

Key Lifecycle Manager Interface

The KeyLifecycleManager interface provides the following operations:

  • Key Generation - Generate new key pairs with algorithm-specific parameters

  • Key Storage - Store and retrieve keys with metadata

  • Key Rotation - Rotate keys and deprecate old ones

  • Key Expiration - Mark keys as expired based on age or policy

  • Key Revocation - Revoke compromised keys with reason tracking

  • Key Export/Import - Convert keys between formats (PEM, DER, PKCS8, X.509)

  • Key Listing - List all keys with their metadata

  • Rotation Detection - Check if keys need rotation based on age or usage

Available Implementations

The component provides four implementations of KeyLifecycleManager:

FileBasedKeyLifecycleManager

A production-ready implementation that persists keys and metadata to disk.

Features:

  • Persistent storage - keys survive application restarts

  • File-based storage with .key and .metadata files

  • In-memory caching for performance

  • Automatic loading of existing keys on startup

  • Secure file permissions

Use Cases:

  • Production environments

  • Single-instance deployments

  • When persistence is required

  • Audit and compliance requirements

Example:

Java-only: FileBasedKeyLifecycleManager usage
KeyLifecycleManager keyManager = new FileBasedKeyLifecycleManager("/secure/keys");

// Generate a new Dilithium key
KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "app-signing-key",
    DilithiumParameterSpec.dilithium2);

// Use the key
KeyMetadata metadata = keyManager.getKeyMetadata("app-signing-key");
logger.info("Key created: {}", metadata);

InMemoryKeyLifecycleManager

A lightweight implementation that stores keys in memory only.

Features:

  • Non-persistent storage - keys lost on restart

  • ConcurrentHashMap-based storage

  • No I/O overhead

  • Thread-safe operations

  • Additional utility methods: clear() and size()

Use Cases:

  • Testing and development

  • Ephemeral workloads

  • Short-lived applications

  • Unit and integration tests

Example:

Java-only: InMemoryKeyLifecycleManager usage
InMemoryKeyLifecycleManager keyManager = new InMemoryKeyLifecycleManager();

// Generate a test key
KeyPair keyPair = keyManager.generateKeyPair("FALCON", "test-key");

// Clean up after testing
keyManager.clear();

HashicorpVaultKeyLifecycleManager

An enterprise-grade implementation that integrates with HashiCorp Vault for centralized secret management.

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 in distinct Vault paths

  • Fine-grained ACLs: Enables different access policies for private keys (restricted) vs public keys (read-only)

Security Note: This implementation stores PQC keys in Vault’s KV secrets engine. While this approach uses industry-standard formats and enables fine-grained access control, organizations with stringent security requirements should consider:

  • Using Hardware Security Modules (HSMs) via PKCS#11 for keys that must never be exportable

  • Implementing additional encryption layers using Vault’s Transit engine to encrypt key material before storage

  • Applying strict Vault policies limiting private key access to specific services or key IDs only

  • Regular key rotation and audit log monitoring

  • For production use, review and customize the Vault ACL policies shown in the setup section

Features:

  • Centralized secret management via HashiCorp Vault

  • Industry-standard key formats (PKCS#8 for private keys, X.509 for public keys)

  • Separate storage for private and public keys (enables different ACLs)

  • Automatic audit logging of all key operations

  • Fine-grained access control with Vault policies

  • Encryption at rest

  • High availability support (Vault HA clusters)

  • In-memory caching for performance

  • Uses Spring Vault (spring-vault-core) consistent with camel-hashicorp-vault

  • HashiCorp Cloud Platform (HCP) Vault support

Dependencies:

To use HashicorpVaultKeyLifecycleManager, add the following optional dependency:

<dependency>
    <groupId>org.springframework.vault</groupId>
    <artifactId>spring-vault-core</artifactId>
    <version>${spring-vault-core-version}</version>
</dependency>

Example with VaultTemplate:

Java-only: HashicorpVaultKeyLifecycleManager with VaultTemplate
// Using existing VaultTemplate (recommended when using camel-hashicorp-vault)
@BindToRegistry("vaultTemplate")
public VaultTemplate createVaultTemplate() {
    VaultEndpoint vaultEndpoint = new VaultEndpoint();
    vaultEndpoint.setHost("localhost");
    vaultEndpoint.setPort(8200);
    vaultEndpoint.setScheme("https");

    return new VaultTemplate(vaultEndpoint, new TokenAuthentication("s.token"));
}

@BindToRegistry("keyLifecycleManager")
public HashicorpVaultKeyLifecycleManager createKeyManager() {
    return new HashicorpVaultKeyLifecycleManager(
        vaultTemplate,      // Reuse existing VaultTemplate
        "secret",           // Secrets engine name
        "pqc/keys"         // Key prefix in Vault
    );
}

// Generate a Dilithium key stored in Vault
KeyPair keyPair = keyManager.generateKeyPair("DILITHIUM", "app-signing-key",
    DilithiumParameterSpec.dilithium2);

// Key is stored in Vault at: secret/data/pqc/keys/app-signing-key

Example with Direct Configuration:

Java-only: HashicorpVaultKeyLifecycleManager with direct configuration
// Direct configuration with connection parameters
HashicorpVaultKeyLifecycleManager keyManager =
    new HashicorpVaultKeyLifecycleManager(
        "vault.example.com",  // host
        8200,                 // port
        "https",              // scheme
        "s.your-token",       // Vault token
        "secret",             // secrets engine (optional, defaults to "secret")
        "pqc/keys"           // key prefix (optional, defaults to "pqc/keys")
    );

Example with HashiCorp Cloud Platform (HCP) Vault:

Java-only: HashicorpVaultKeyLifecycleManager with HCP Vault
// Configuration for HCP Vault with namespace
HashicorpVaultKeyLifecycleManager keyManager =
    new HashicorpVaultKeyLifecycleManager(
        "your-cluster.vault.hashicorp.cloud",  // HCP Vault host
        8200,                                    // port
        "https",                                 // scheme
        "s.your-hcp-token",                     // HCP Vault token
        "secret",                                // secrets engine
        "pqc/keys",                             // key prefix
        true,                                    // cloud=true for HCP Vault
        "admin"                                  // namespace (required for HCP)
    );

// Key is stored in HCP Vault at: admin/secret/data/pqc/keys/hcp-key

YAML Configuration:

camel:
  beans:
    vaultEndpoint:
      type: org.springframework.vault.client.VaultEndpoint
      properties:
        host: "vault.example.com"
        port: 8200
        scheme: "https"

    tokenAuthentication:
      type: org.springframework.vault.authentication.TokenAuthentication
      constructorArgs:
        - "${VAULT_TOKEN}"

    vaultTemplate:
      type: org.springframework.vault.core.VaultTemplate
      constructorArgs:
        - "#bean:vaultEndpoint"
        - "#bean:tokenAuthentication"

    keyLifecycleManager:
      type: org.apache.camel.component.pqc.lifecycle.HashicorpVaultKeyLifecycleManager
      constructorArgs:
        - "#bean:vaultTemplate"
        - "secret"
        - "pqc/keys"
        - false              # cloud
        - null               # namespace

Vault Storage Structure:

Keys are stored in Vault’s KV v2 secrets engine with separate paths 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.

secret/                                    # Secrets engine
├── data/
│   └── pqc/
│       └── keys/
│           ├── app-signing-key/
│           │   ├── private                # Private key path (STRICT ACL)
│           │   │   ├── key                # Base64-encoded PKCS#8 private key
│           │   │   ├── format             # "PKCS8"
│           │   │   └── algorithm          # "DILITHIUM"
│           │   ├── public                 # Public key path (READ-ONLY ACL)
│           │   │   ├── key                # Base64-encoded X.509 public key
│           │   │   ├── format             # "X509"
│           │   │   └── algorithm          # "DILITHIUM"
│           │   └── metadata               # Metadata path
│           │       ├── metadata           # Serialized KeyMetadata
│           │       ├── keyId              # "app-signing-key"
│           │       └── algorithm          # "DILITHIUM"
│           └── app-signing-key-v2/
│               ├── private/
│               ├── public/
│               └── metadata/
└── metadata/
    └── pqc/
        └── keys/
            ├── app-signing-key/           # Vault metadata entry
            └── app-signing-key-v2/

Vault Setup and Security Policies:

To use HashicorpVaultKeyLifecycleManager, configure Vault with appropriate policies. The implementation stores private keys, public keys, and metadata separately to enable fine-grained access control.

Basic Policy (Full Access - Development/Testing):

# Enable KV v2 secrets engine (usually enabled by default)
vault secrets enable -path=secret kv-v2

# Create basic policy with full access to all key paths
cat > pqc-policy-full.hcl <<EOF
# Full access to private keys
path "secret/data/pqc/keys/*/private" {
  capabilities = ["create", "read", "update", "delete"]
}

# Full access to public keys
path "secret/data/pqc/keys/*/public" {
  capabilities = ["create", "read", "update", "delete"]
}

# Full access to metadata
path "secret/data/pqc/keys/*/metadata" {
  capabilities = ["create", "read", "update", "delete"]
}

# List access to key paths
path "secret/metadata/pqc/keys/*" {
  capabilities = ["list", "read", "delete"]
}
EOF

# Apply policy
vault policy write pqc-keys-full pqc-policy-full.hcl

# Create token with policy
vault token create -policy=pqc-keys-full

Production Policies (Fine-Grained Access Control):

For production, create separate policies for different access levels. The following example shows four policies with increasing restriction:

# POLICY 1: Admin Policy (Key Management Service)
# Full access to generate, rotate, and manage keys
cat > pqc-policy-admin.hcl <<EOF
path "secret/data/pqc/keys/*/private" {
  capabilities = ["create", "read", "update", "delete"]
}
path "secret/data/pqc/keys/*/public" {
  capabilities = ["create", "read", "update", "delete"]
}
path "secret/data/pqc/keys/*/metadata" {
  capabilities = ["create", "read", "update", "delete"]
}
path "secret/metadata/pqc/keys/*" {
  capabilities = ["list", "read", "delete"]
}
EOF
vault policy write pqc-admin pqc-policy-admin.hcl

# POLICY 2: Signing Service (Read Private Keys)
cat > pqc-policy-signing.hcl <<EOF
path "secret/data/pqc/keys/*/private" {
  capabilities = ["read"]
}
path "secret/data/pqc/keys/*/public" {
  capabilities = ["read"]
}
path "secret/data/pqc/keys/*/metadata" {
  capabilities = ["read"]
}
path "secret/metadata/pqc/keys/*" {
  capabilities = ["list", "read"]
}
EOF
vault policy write pqc-signing pqc-policy-signing.hcl

# POLICY 3: Application Policy (Public Keys Only - Verification)
cat > pqc-policy-app.hcl <<EOF
path "secret/data/pqc/keys/*/public" {
  capabilities = ["read"]
}
path "secret/data/pqc/keys/*/metadata" {
  capabilities = ["read"]
}
path "secret/metadata/pqc/keys/*" {
  capabilities = ["list", "read"]
}
EOF
vault policy write pqc-app pqc-policy-app.hcl

# POLICY 4: Specific Key Access (Production Best Practice)
cat > pqc-policy-specific.hcl <<EOF
path "secret/data/pqc/keys/app-signing-key/private" {
  capabilities = ["read"]
}
path "secret/data/pqc/keys/app-signing-key/public" {
  capabilities = ["read"]
}
path "secret/data/pqc/keys/app-signing-key/metadata" {
  capabilities = ["read"]
}
EOF
vault policy write pqc-app-signing-key pqc-policy-specific.hcl

# Create tokens with different policies
vault token create -policy=pqc-admin        # For key management service
vault token create -policy=pqc-signing      # For signing service
vault token create -policy=pqc-app          # For applications (verification only)
vault token create -policy=pqc-app-signing-key  # For specific key access

Integration with camel-hashicorp-vault:

HashicorpVaultKeyLifecycleManager can share the same VaultTemplate with the camel-hashicorp-vault component:

Java-only: reusing VaultTemplate from camel-hashicorp-vault
@BindToRegistry("keyLifecycleManager")
public HashicorpVaultKeyLifecycleManager createKeyManager() {
    VaultTemplate vaultTemplate = context.getRegistry()
        .lookupByNameAndType("vaultTemplate", VaultTemplate.class);

    return new HashicorpVaultKeyLifecycleManager(
        vaultTemplate,
        "secret",
        "pqc/keys"
    );
}

AwsSecretsManagerKeyLifecycleManager

An enterprise-grade implementation that integrates with AWS Secrets Manager for centralized secret management.

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

Dependencies:

To use AwsSecretsManagerKeyLifecycleManager, add the following optional dependency:

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>secretsmanager</artifactId>
    <version>${aws-java-sdk2-version}</version>
</dependency>

Example with SecretsManagerClient:

Java-only: AwsSecretsManagerKeyLifecycleManager with SecretsManagerClient
// 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:

Java-only: AwsSecretsManagerKeyLifecycleManager with direct configuration
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")
    );

Example with LocalStack (Testing):

Java-only: AwsSecretsManagerKeyLifecycleManager with LocalStack
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
    );

YAML Configuration:

camel:
  beans:
    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:

Each key creates three secrets in AWS Secrets Manager:

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

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

pqc/keys/app-signing-key/metadata    # Key metadata
  {
    "metadata": "rO0ABXNyAC9vcm...",  # Serialized KeyMetadata object
    "keyId": "app-signing-key",
    "algorithm": "DILITHIUM"
  }
  Tags: ManagedBy=camel-pqc

AWS IAM Policies:

To use AwsSecretsManagerKeyLifecycleManager, configure IAM policies for appropriate access control. The pattern follows the same four-level model as Vault (Admin, Signing Service, Application, Specific Key). See the full policy examples below.

Basic Policy (Full Access - Development/Testing):

{
  "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):

The production setup follows the same four-tier model as Vault:

  • Admin Policy - Full access for key management service (create, read, update, delete all key paths)

  • Signing Service Policy - Read-only access to private keys, public keys, and metadata for signing operations

  • Application Policy - Read-only access to public keys and metadata (explicitly denies private key access)

  • Specific Key Access - Limits access to a specific key ID only (production best practice)

Application Policy (Public Keys Only) example:

{
  "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": "*"
    }
  ]
}

Integration with camel-aws-secrets-manager:

AwsSecretsManagerKeyLifecycleManager can share the same SecretsManagerClient with the camel-aws-secrets-manager component:

Java-only: reusing 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

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 (Vault)

Automatic (CloudTrail)

Access Control

File permissions

None

Vault policies

IAM policies

Encryption at Rest

OS-dependent

N/A

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

Use Case

Single server

Testing/Dev

Production/Enterprise (Vault)

Production/Enterprise (AWS)

Key Generation

The key lifecycle manager supports all PQC algorithms with sensible default parameter specifications.

Signature Algorithms

Java-only: generating key pairs for various PQC signature algorithms
// ML-DSA (uses default ML-DSA-65)
KeyPair mldsaKey = keyManager.generateKeyPair("MLDSA", "mldsa-key");

// Dilithium with specific parameter
KeyPair dilithiumKey = keyManager.generateKeyPair("DILITHIUM", "dilithium-key",
    DilithiumParameterSpec.dilithium3);

// Falcon (uses default Falcon-512)
KeyPair falconKey = keyManager.generateKeyPair("FALCON", "falcon-key");

// XMSS (uses default 10-tree height with SHA-256)
KeyPair xmssKey = keyManager.generateKeyPair("XMSS", "xmss-key");

// XMSSMT (uses default XMSSMT-SHA2-20d2-256)
KeyPair xmssmtKey = keyManager.generateKeyPair("XMSSMT", "xmssmt-key");

// LMS/HSS (uses default LMS-SHA256-N32-H10)
KeyPair lmsKey = keyManager.generateKeyPair("LMS", "lms-key");

Key Encapsulation Algorithms

Java-only: generating key pairs for various PQC KEM algorithms
// ML-KEM (uses default)
KeyPair mlkemKey = keyManager.generateKeyPair("MLKEM", "mlkem-key");

// NTRU (uses default ntruhps2048509)
KeyPair ntruKey = keyManager.generateKeyPair("NTRU", "ntru-key");

// SNTRUPrime (uses default sntrup761)
KeyPair sntrupKey = keyManager.generateKeyPair("SNTRUPrime", "sntrup-key");

// BIKE (uses default bike128)
KeyPair bikeKey = keyManager.generateKeyPair("BIKE", "bike-key");

Key Metadata

Each key is associated with metadata that tracks its lifecycle:

Java-only: retrieving and inspecting key metadata
KeyMetadata metadata = keyManager.getKeyMetadata("my-key");

System.out.println("Key ID: " + metadata.getKeyId());
System.out.println("Algorithm: " + metadata.getAlgorithm());
System.out.println("Status: " + metadata.getStatus());
System.out.println("Created: " + metadata.getCreatedAt());
System.out.println("Age (days): " + metadata.getAgeInDays());
System.out.println("Usage count: " + metadata.getUsageCount());
System.out.println("Expires at: " + metadata.getExpiresAt());
System.out.println("Next rotation: " + metadata.getNextRotationAt());

Key Status Values:

  • ACTIVE - Key is active and can be used

  • EXPIRED - Key has expired and should not be used

  • REVOKED - Key has been revoked due to compromise

  • PENDING_ROTATION - Key should be rotated soon

  • DEPRECATED - Key has been rotated and replaced

Key Rotation

Key rotation is essential for maintaining security. The lifecycle manager supports automated rotation:

Java-only: checking rotation need and rotating a key
// Check if key needs rotation
boolean needsRotation = keyManager.needsRotation("old-key",
    Duration.ofDays(90),  // Max age: 90 days
    10000);                // Max usage: 10,000 operations

if (needsRotation) {
    // Rotate the key
    KeyPair newKey = keyManager.rotateKey("old-key", "new-key", "DILITHIUM");

    // Old key is now DEPRECATED
    // New key is ACTIVE
    logger.info("Key rotated successfully");
}

Automated Rotation Scheduling

Rather than calling needsRotation()/rotateKey() by hand, the KeyRotationScheduler can evaluate all managed keys periodically in the background and rotate the ones that breach a configurable policy. It is opt-in: nothing runs until the scheduler is started.

On every tick the scheduler lists the keys of its KeyLifecycleManager, keeps the ones matching the key filter (by default only ACTIVE keys) and, for each, delegates the decision to needsRotation(keyId, maxKeyAge, maxKeyUsage). A key that needs rotation is replaced by a freshly generated key while the previous key is deprecated. At least one rotation signal must be configured - a maximum age, a maximum usage, or a per-key nextRotationAt/PENDING_ROTATION status recorded in the key metadata - otherwise no key will ever be selected.

Java-only: scheduling automated key rotation
KeyLifecycleManager keyManager = new FileBasedKeyLifecycleManager("/secure/keys");

KeyRotationScheduler scheduler = new KeyRotationScheduler(keyManager)
    .setCheckInterval(Duration.ofHours(6))   // how often to evaluate the keys (default: 1 hour)
    .setMaxKeyAge(Duration.ofDays(90))       // rotate keys older than 90 days
    .setMaxKeyUsage(1_000_000)               // and/or rotate keys used more than 1,000,000 times
    .setListener(new KeyRotationScheduler.KeyRotationListener() {
        @Override
        public void onRotated(String oldKeyId, String newKeyId, KeyMetadata previous, KeyPair newKeyPair) {
            logger.info("Rotated {} -> {}", oldKeyId, newKeyId);
        }
    });

scheduler.start();       // begin the background schedule
// ... application runs ...
scheduler.stop();        // stop the background schedule

Because KeyRotationScheduler is a Camel Service, it can instead be added to the CamelContext so that it is started and stopped together with the context:

Java-only: managing the scheduler with the CamelContext
camelContext.addService(scheduler);   // started and stopped with the context

Alternatively, the scheduler can be started and configured entirely through component options - no Java code - by setting camel.component.pqc.* properties (for example in application.properties). Bind a KeyLifecycleManager in the registry and enable the scheduler:

application.properties: property-driven key rotation
# a KeyLifecycleManager bean must be available in the registry, e.g. #myKeyManager
camel.component.pqc.key-lifecycle-manager = #myKeyManager
camel.component.pqc.key-rotation-scheduler-enabled = true
camel.component.pqc.key-rotation-check-interval = 6h
camel.component.pqc.key-rotation-max-age = 90d
camel.component.pqc.key-rotation-max-usage = 1000000

When keyRotationSchedulerEnabled is true, the pqc component builds a KeyRotationScheduler around the configured keyLifecycleManager and starts and stops it with the component. The same options are available in the YAML DSL and as PQCComponent setters, which makes the scheduler usable from Spring Boot and Quarkus without any Java code.

The default id of a replacement key is the previous key id with a rotation marker and timestamp appended; supply a setKeyIdStrategy(Function<KeyMetadata, String>) to control it, and a setKeyFilter(Predicate<KeyMetadata>) to change which keys are considered. The scheduler also exposes counters (getChecksPerformed(), getRotationsPerformed(), getRotationFailures(), getLastCheckAt()) for monitoring. A single pass can be triggered on demand with checkAndRotate().

Key Export and Import

Keys can be exported and imported in multiple formats:

Supported Formats:

  • PEM - Privacy-Enhanced Mail format (Base64 encoded)

  • DER - Distinguished Encoding Rules (binary)

  • PKCS8 - PKCS#8 format for private keys

  • X509 - X.509 format for public keys

Export Example:

Java-only: exporting keys in PEM and DER formats
KeyPair keyPair = keyManager.getKey("my-key");

// Export public key as PEM
byte[] publicPem = keyManager.exportPublicKey(keyPair, KeyFormat.PEM);
String pemString = new String(publicPem);
System.out.println(pemString);
// Output:
// -----BEGIN PUBLIC KEY-----
// MIIBIjAN...
// -----END PUBLIC KEY-----

// Export entire key pair (public only for safety)
byte[] keyPairPem = keyManager.exportKey(keyPair, KeyFormat.PEM, false);

// Export with private key (USE WITH CAUTION)
byte[] fullExport = keyManager.exportKey(keyPair, KeyFormat.DER, true);

Import Example:

Java-only: importing a key from PEM format
// Import from PEM format
byte[] pemData = Files.readAllBytes(Paths.get("public-key.pem"));
KeyPair imported = keyManager.importKey(pemData, KeyFormat.PEM, "DILITHIUM");

// Store the imported key
KeyMetadata metadata = new KeyMetadata("imported-key", "DILITHIUM");
keyManager.storeKey("imported-key", imported, metadata);

Key Expiration and Revocation

Expire a Key:

Java-only: setting expiration and manually expiring a key
// Set expiration time
KeyMetadata metadata = keyManager.getKeyMetadata("my-key");
metadata.setExpiresAt(Instant.now().plus(Duration.ofDays(365)));
keyManager.updateKeyMetadata("my-key", metadata);

// Manually expire immediately
keyManager.expireKey("my-key");

// Check if expired
if (metadata.isExpired()) {
    logger.warn("Key {} has expired", metadata.getKeyId());
}

Revoke a Key:

Java-only: revoking a compromised key
// Revoke a compromised key
keyManager.revokeKey("compromised-key", "Private key exposed in log file");

KeyMetadata metadata = keyManager.getKeyMetadata("compromised-key");
assert metadata.getStatus() == KeyMetadata.KeyStatus.REVOKED;
assert metadata.getDescription().contains("Revoked: Private key exposed");

Listing and Managing Keys

Java-only: listing, filtering, and deleting keys
// List all keys
List<KeyMetadata> allKeys = keyManager.listKeys();

// Filter active keys
List<KeyMetadata> activeKeys = allKeys.stream()
    .filter(m -> m.getStatus() == KeyMetadata.KeyStatus.ACTIVE)
    .collect(Collectors.toList());

// Find keys needing rotation
for (KeyMetadata metadata : allKeys) {
    if (keyManager.needsRotation(metadata.getKeyId(), Duration.ofDays(90), 5000)) {
        logger.warn("Key {} needs rotation", metadata.getKeyId());
    }
}

// Delete old keys
for (KeyMetadata metadata : allKeys) {
    if (metadata.getStatus() == KeyMetadata.KeyStatus.DEPRECATED
        && metadata.getAgeInDays() > 365) {
        keyManager.deleteKey(metadata.getKeyId());
        logger.info("Deleted old deprecated key: {}", metadata.getKeyId());
    }
}

Integration with Camel Routes

The key lifecycle manager can be registered in the Camel registry and used in routes:

  • Java

  • YAML

@BindToRegistry("keyLifecycleManager")
public KeyLifecycleManager createKeyManager() throws IOException {
    return new FileBasedKeyLifecycleManager("/secure/keys");
}

@BindToRegistry("signingKey")
public KeyPair createSigningKey() throws Exception {
    KeyLifecycleManager manager = createKeyManager();
    return manager.generateKeyPair("DILITHIUM", "route-signing-key",
        DilithiumParameterSpec.dilithium2);
}
camel:
  beans:
    keyLifecycleManager:
      type: org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager
      properties:
        keyDirectoryPath: "/secure/keys"

    signingKey:
      type: org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager
      scriptLanguage: groovy
      script: |
        def manager = new org.apache.camel.component.pqc.lifecycle.FileBasedKeyLifecycleManager('/secure/keys')
        def spec = org.bouncycastle.pqc.jcajce.spec.DilithiumParameterSpec.dilithium2
        manager.generateKeyPair('DILITHIUM', 'route-signing-key', spec)

Using in Routes:

  • Java

  • YAML

from("timer:check-rotation?period=86400000") // Check daily
    .bean("keyLifecycleManager", "needsRotation('route-signing-key', 90, 10000)")
    .choice()
        .when(body().isEqualTo(true))
            .log("Rotating signing key")
            .bean("keyLifecycleManager", "rotateKey('route-signing-key', 'route-signing-key-new', 'DILITHIUM')")
            .to("log:rotation-complete")
        .otherwise()
            .log("Key rotation not needed")
    .end();
- route:
    id: check-key-rotation
    from:
      uri: timer:check-rotation
      parameters:
        period: 86400000  # Check daily
    steps:
      - bean:
          ref: keyLifecycleManager
          method: needsRotation('route-signing-key', 90, 10000)
      - choice:
          when:
            - expression:
                simple:
                  expression: "${body} == true"
              steps:
                - log:
                    message: "Rotating signing key"
                - bean:
                    ref: keyLifecycleManager
                    method: rotateKey('route-signing-key', 'route-signing-key-new', 'DILITHIUM')
                - to:
                    uri: log:rotation-complete
          otherwise:
            steps:
              - log:
                  message: "Key rotation not needed"

Default Parameter Specifications

The lifecycle manager provides sensible defaults for all algorithms:

Algorithm Default Parameter Spec

DILITHIUM

dilithium2

FALCON

falcon_512

SPHINCSPLUS

sha2_128s

XMSS

10-tree height with SHA-256

XMSSMT

XMSSMT-SHA2-20d2-256

LMS/HSS

LMS-SHA256-N32-H10 with SHA256-N32-W4

NTRU

ntruhps2048509

NTRULPRime

ntrulpr653

SNTRUPrime

sntrup761

SABER

lightsaberkem128r3

FRODO

frodokem640aes

BIKE

bike128

HQC

hqc128

CMCE

mceliece348864

Best Practices

1. Regular Key Rotation

Implement automated key rotation policies:

Java-only: automated key rotation policy
// Rotate keys every 90 days or after 10,000 uses
Duration maxAge = Duration.ofDays(90);
long maxUsage = 10000;

if (keyManager.needsRotation(keyId, maxAge, maxUsage)) {
    keyManager.rotateKey(keyId, keyId + "-new", algorithm);
}

2. Secure Storage

For FileBasedKeyLifecycleManager:

  • Store keys in a secure directory with restricted permissions

  • Use encrypted file systems where possible

  • Implement backup strategies for key files

  • Monitor access to key directories

3. Key Metadata Tracking

Always update metadata when using keys:

Java-only: updating key usage metadata
KeyMetadata metadata = keyManager.getKeyMetadata(keyId);
metadata.updateLastUsed(); // Increments usage count
keyManager.updateKeyMetadata(keyId, metadata);

4. Handle Stateful Signatures Carefully

For XMSS, XMSSMT, LMS, and HSS algorithms:

  • Track signature usage to prevent exhaustion

  • Never reuse a stateful signature state (critical security requirement)

  • Monitor usage percentage and rotate before exhaustion

  • Set appropriate maxSignatures based on tree height

5. Secure Key Export

When exporting keys:

  • Export public keys only when possible

  • Encrypt private key exports

  • Use secure channels for key transfer

  • Audit all key export operations

6. Testing Strategy

Use InMemoryKeyLifecycleManager for tests:

Java-only: test setup and cleanup with InMemoryKeyLifecycleManager
@BeforeEach
public void setup() {
    keyManager = new InMemoryKeyLifecycleManager();
}

@AfterEach
public void cleanup() {
    keyManager.clear();
}

Thread Safety

Both implementations are thread-safe:

  • FileBasedKeyLifecycleManager: Uses ConcurrentHashMap for caching and atomic file operations

  • InMemoryKeyLifecycleManager: Uses ConcurrentHashMap for all storage

Concurrent key operations are safe:

Java-only: concurrent key generation with thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 100; i++) {
    final int index = i;
    executor.submit(() -> {
        try {
            keyManager.generateKeyPair("DILITHIUM", "key-" + index,
                DilithiumParameterSpec.dilithium2);
        } catch (Exception e) {
            logger.error("Key generation failed", e);
        }
    });
}

executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);

Migration Between Implementations

To migrate from InMemoryKeyLifecycleManager to FileBasedKeyLifecycleManager:

Java-only: migrating keys between lifecycle manager implementations
InMemoryKeyLifecycleManager memoryManager = new InMemoryKeyLifecycleManager();
FileBasedKeyLifecycleManager fileManager = new FileBasedKeyLifecycleManager("/secure/keys");

// Export all keys
List<KeyMetadata> keys = memoryManager.listKeys();
for (KeyMetadata metadata : keys) {
    KeyPair keyPair = memoryManager.getKey(metadata.getKeyId());
    fileManager.storeKey(metadata.getKeyId(), keyPair, metadata);
}

logger.info("Migrated {} keys to file-based storage", keys.size());