Feedemy.KeyManagement 2.5.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Feedemy.KeyManagement --version 2.5.0
                    
NuGet\Install-Package Feedemy.KeyManagement -Version 2.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Feedemy.KeyManagement" Version="2.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Feedemy.KeyManagement" Version="2.5.0" />
                    
Directory.Packages.props
<PackageReference Include="Feedemy.KeyManagement" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Feedemy.KeyManagement --version 2.5.0
                    
#r "nuget: Feedemy.KeyManagement, 2.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Feedemy.KeyManagement@2.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Feedemy.KeyManagement&version=2.5.0
                    
Install as a Cake Addin
#tool nuget:?package=Feedemy.KeyManagement&version=2.5.0
                    
Install as a Cake Tool

Feedemy.KeyManagement

A production-ready, high-performance key management library for .NET 9.0 applications with built-in caching, versioning, automatic rotation, and comprehensive health monitoring.

Overview

Feedemy.KeyManagement provides a secure, scalable solution for managing cryptographic keys and sensitive configuration values in enterprise .NET applications. It combines platform-specific secure storage with advanced features like automatic key rotation, version management, distributed caching, and full audit trails.

Key Features

  • Secure Storage: Platform-specific secure storage providers (Windows DPAPI, Linux KeyRing, Azure Key Vault)
  • High Performance: Cache-first architecture with Redis/Memory cache support and multi-layer caching
  • Automatic Rotation: Background service with configurable rotation policies and health monitoring
  • Version Management: Complete key version history with rollback capabilities and deactivation
  • Health Monitoring: Built-in health checks, rotation warnings, and system diagnostics
  • Admin Operations: Comprehensive admin API for complete key lifecycle management
  • Master Key Encryption: Optional envelope encryption for ENV keys using master EncryptionKey
  • Distributed Cache Invalidation: Pub/sub pattern for multi-server cache synchronization
  • Cache Warming: Automatic preloading of critical keys after invalidation
  • Audit Trail: Full audit logging for all administrative operations
  • Test Coverage: 412 automated unit + integration tests exercise RSA/ECDSA workflows, master-key re-encryption, cache invalidation paths, and storage fallbacks. Latest dotnet test tests/Feedemy.KeyManagement.IntegrationTests run (2025‑02‑14) completed in ~64s with 411 passing and 1 known failure (see below).

Current Test Status

  • Failing scenario: EndToEndTests.EncryptedKeyWorkflow_WithMasterKeyEncryption_ShouldHandleEncryption (tests/Feedemy.KeyManagement.IntegrationTests/EndToEndTests.cs:223). The assertion still expects a 32-byte plaintext buffer, but the storage pipeline is returning the 60-byte master-key-encrypted payload when cache serves the item. We need to ensure the retrieval path always flows through IKeyEncryptionOrchestrator (e.g., by enforcing RetrieveEncryptedKeyAsync for cache hits) so the decrypted data length matches the metadata contract.

New Features (v1.0)

Health Checks Integration

Monitor key management system health using ASP.NET Core health checks:

// In Program.cs
builder.Services.AddHealthChecks()
    .AddKeyManagementHealthCheck(
        name: "key_management",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "security", "keys" });

app.MapHealthChecks("/health");

Health Status Levels:

  • Healthy: All keys operational, storage available
  • Degraded: Warning keys detected or storage issues
  • Unhealthy: Critical keys or system failure

Response Example:

{
  "status": "Healthy",
  "totalKeys": 15,
  "healthyKeys": 13,
  "warningKeys": 2,
  "criticalKeys": 0,
  "storageProvider": "Windows DPAPI",
  "storageAvailable": true
}

Redis Distributed Caching

Enable distributed cache invalidation for multi-server deployments:

// In Program.cs
builder.Services.AddSingleton<IConnectionMultiplexer>(
    ConnectionMultiplexer.Connect("localhost:6379"));

builder.Services.AddKeyManagement(options =>
{
    options.Cache.ProviderType = CacheProviderType.Redis;
    options.Cache.RedisConnectionString = "localhost:6379";
    options.Cache.EnableDistributedInvalidation = true;
    options.Cache.InvalidationChannel = "feedemy:keymanagement:invalidation";
});

How it Works:

  • Key rotations/updates publish invalidation messages to Redis
  • All servers subscribe to the invalidation channel
  • Each server clears its local cache when notified
  • Ensures cache consistency across multiple instances

Platform-Specific Storage

The library automatically selects the best storage provider for your platform:

Windows (DPAPI)
  • Location: %LOCALAPPDATA%\Feedemy\Keys
  • Encryption: Windows Data Protection API (DPAPI)
  • Scope: CurrentUser (tied to user account)
  • Persistent: Yes, survives restarts
  • Security: Protected by Windows ACLs + DPAPI encryption
Linux (AES-256-GCM)
  • Location: ~/.feedemy-keys/
  • Encryption: AES-256-GCM with machine-specific key
  • Key Derivation: PBKDF2-SHA256 (100,000 iterations) from /etc/machine-id
  • File Permissions: 600 (owner read/write only)
  • Persistent: Yes, survives restarts
  • Security: Encrypted at rest + file system permissions
Azure Key Vault
  • Authentication: DefaultAzureCredential (Managed Identity or Client Secret)
  • Retry Policy: 3 attempts with exponential backoff (2s → 4s → 8s)
  • Transient Failures: Handles 429 (rate limit), 503 (unavailable), 504 (timeout)
  • Security: Managed by Azure, hardware security modules (HSM) optional

Configuration:

builder.Services.AddKeyManagement(options =>
{
    // Auto-detect best provider for current OS
    options.Storage.ProviderType = StorageProviderType.Auto;

    // OR explicitly choose:
    // options.Storage.ProviderType = StorageProviderType.WindowsCredentialManager;
    // options.Storage.ProviderType = StorageProviderType.LinuxKeyring;
    // options.Storage.ProviderType = StorageProviderType.AzureKeyVault;
    // options.Storage.AzureKeyVaultUrl = "https://your-vault.vault.azure.net/";
});

Security Improvements

v1.0 Security Enhancements:

  1. Master Key Encryption: ENV keys automatically encrypted with EncryptionKey
  2. Encrypted Storage at Rest: Both Windows (DPAPI) and Linux (AES-256-GCM) encrypt keys on disk
  3. Compensating Transactions: Key creation failures automatically roll back storage writes
  4. Sensitive Logging: Key names only logged at Debug level (not Information)
  5. Audit Trail: Complete audit logging for all admin operations

Performance

Benchmark Results (BenchmarkDotNet):

| Method                  | Mean     | Allocated |
|------------------------|----------|-----------|
| RetrieveKey_Cached     | 0.85 μs  | 96 B      |
| GetCurrentVersion      | 0.62 μs  | 48 B      |
| GetMetadata            | 1.20 μs  | 128 B     |

Run benchmarks:

cd benchmarks/Feedemy.KeyManagement.Benchmarks
dotnet run -c Release

Migration from Legacy System

If migrating from FeedemyBackend's built-in key management:

  1. Install Package:

    dotnet add package Feedemy.KeyManagement
    
  2. Update DI Registration:

    // Old (FeedemyBackend):
    services.AddKeyManagementServices();
    
    // New (v1.0):
    services.AddKeyManagement(options => {
        options.Persistence.ProviderType = PersistenceProviderType.EntityFramework;
    });
    
  3. No Code Changes: Interfaces remain compatible (IKeyManagementService, IKeyManagementAdminService)

  4. Data Migration: Keys automatically migrated on first retrieval

See Migration Guide for detailed step-by-step instructions.

Installation

Install the core package:

dotnet add package Feedemy.KeyManagement

Install provider packages based on your needs:

# For Entity Framework Core persistence
dotnet add package Feedemy.KeyManagement.Providers.EntityFramework

# For Windows DPAPI storage
dotnet add package Feedemy.KeyManagement.Providers.Windows

# For Linux KeyRing storage
dotnet add package Feedemy.KeyManagement.Providers.Linux

# For Azure Key Vault storage
dotnet add package Feedemy.KeyManagement.Providers.Azure

Quick Start

Basic Setup (InMemory - Development)

using Feedemy.KeyManagement.Extensions;
using Microsoft.Extensions.DependencyInjection;

// Program.cs or Startup.cs
services.AddKeyManagement(options =>
{
    options.EnableAutoRotation = true;
    options.RotationCheckInterval = TimeSpan.FromHours(6);
    options.DefaultRotationDays = 90;
})
.UseInMemoryStorage()           // Development only
.UseMemoryCache()               // Development only
.UseInMemoryPersistence()       // Development only
.AddConsoleNotifications();

Production Setup (SQL Server + Redis)

services.AddKeyManagement(options =>
{
    options.EnableAutoRotation = true;
    options.RotationCheckInterval = TimeSpan.FromHours(6);
    options.DefaultRotationDays = 90;
})
.UseWindowsStorage()            // Or .UseLinuxStorage() or .UseAzureKeyVault()
.UseRedisCache(Configuration.GetConnectionString("Redis"))
.UseEntityFramework()           // Uses existing DbContext
.AddConsoleNotifications();

Basic Usage

public class MyService
{
    private readonly IKeyManagementService _keyManagement;
    private readonly IKeyManagementAdminService _adminService;

    public MyService(
        IKeyManagementService keyManagement,
        IKeyManagementAdminService adminService)
    {
        _keyManagement = keyManagement;
        _adminService = adminService;
    }

    // Create a new key (admin operation)
    public async Task CreateApiKeyAsync()
    {
        var result = await _adminService.CreateKeyAsync(new CreateKeyRequest
        {
            KeyName = "ApiKey",
            Description = "External API authentication key",
            RotationIntervalDays = 90,
            AutoRotationEnabled = true,
            MaxVersionsToRetain = 5,
            SourceType = KeySourceType.Generated,
            Category = KeyCategory.ApiKey,
            CreatedBy = "AdminUser"
        });

        if (result.Success)
        {
            Console.WriteLine($"Key created: {result.Data.KeyName} v{result.Data.CurrentVersion}");
        }
    }

    // Retrieve a key (read operation)
    public async Task<byte[]> GetApiKeyAsync()
    {
        // Cache-first retrieval - extremely fast after first access
        var key = await _keyManagement.RetrieveKeyAsync("ApiKey");
        return key; // Returns null if key doesn't exist or is deactivated
    }

    // Get key metadata
    public async Task<KeyMetadata> GetKeyInfoAsync()
    {
        var metadata = await _keyManagement.GetKeyMetadataAsync("ApiKey");
        Console.WriteLine($"Current Version: {metadata.CurrentVersion}");
        Console.WriteLine($"Next Rotation: {metadata.NextRotationDue}");
        Console.WriteLine($"Auto Rotation: {metadata.AutoRotationEnabled}");
        return metadata;
    }

    // Check key health
    public async Task MonitorKeyHealthAsync()
    {
        var health = await _keyManagement.GetKeyHealthAsync("ApiKey");
        Console.WriteLine($"Health Status: {health.Status}");
        Console.WriteLine($"Days Until Rotation: {health.DaysUntilRotation}");

        if (health.Status == KeyHealthStatus.NeedsRotation)
        {
            // Trigger alert or automatic rotation
        }
    }
}

Configuration

KeyManagementOptions

Configure the core behavior of the key management system:

services.AddKeyManagement(options =>
{
    // Enable automatic key rotation background service
    options.EnableAutoRotation = true;

    // How often to check for keys needing rotation (default: 6 hours)
    options.RotationCheckInterval = TimeSpan.FromHours(6);

    // Default rotation interval for new keys (default: 90 days)
    options.DefaultRotationDays = 90;

    // Storage provider configuration
    options.Storage.ProviderType = StorageProviderType.Auto;  // Auto-detect based on OS

    // Cache provider configuration
    options.Cache.ProviderType = CacheProviderType.Redis;
    options.Cache.RedisConnectionString = "localhost:6379";

    // Persistence provider configuration
    options.Persistence.ProviderType = PersistenceProviderType.EntityFramework;

    // Notification configuration
    options.Notifications.EnableNotifications = true;

    // Initialization configuration
    options.Initialization.EnableAutoInitialization = true;
    options.Initialization.ExternalKeysJsonPath = "keys.json";  // Optional
});

KeyManagementCacheOptions

Configure advanced caching behavior:

services.Configure<KeyManagementCacheOptions>(options =>
{
    // Enable distributed cache invalidation (required for multi-server)
    options.EnableDistributedInvalidation = true;

    // Pub/sub channel for cache invalidation messages
    options.InvalidationChannel = "feedemy:keymanagement:invalidation";

    // Timeout for pub/sub operations
    options.PubSubTimeoutSeconds = 5;

    // Retry configuration
    options.MaxInvalidationRetries = 3;
    options.RetryDelayMilliseconds = 100;

    // Enable cache warming (pre-load after invalidation)
    options.EnableCacheWarming = true;
    options.MaxConcurrentWarmingTasks = 5;
});

Environment Variables

All configuration can be overridden via environment variables:

# Core settings
KeyManagement__EnableAutoRotation=true
KeyManagement__RotationCheckInterval=06:00:00
KeyManagement__DefaultRotationDays=90

# Storage provider
KeyManagement__Storage__ProviderType=WindowsCredentialManager

# Cache provider
KeyManagement__Cache__ProviderType=Redis
KeyManagement__Cache__RedisConnectionString=localhost:6379

# Persistence provider
KeyManagement__Persistence__ProviderType=EntityFramework

# Cache options
KeyManagementCache__EnableDistributedInvalidation=true
KeyManagementCache__InvalidationChannel=feedemy:keymanagement:invalidation
KeyManagementCache__EnableCacheWarming=true

Providers

Storage Providers

Storage providers securely store the actual key content:

InMemory (Development Only)
.UseInMemoryStorage()

Stores keys in memory. Not suitable for production - keys are lost on restart.

Windows DPAPI
.UseWindowsStorage()

Uses Windows Data Protection API (DPAPI) via Credential Manager. Requires Windows OS.

Linux KeyRing
.UseLinuxStorage()

Uses Linux Secret Service API (KeyRing). Requires Linux OS with GNOME Keyring or KWallet.

Azure Key Vault
.UseAzureKeyVault(options =>
{
    options.VaultUri = "https://your-vault.vault.azure.net/";
    options.TenantId = "your-tenant-id";
    options.ClientId = "your-client-id";
    options.ClientSecret = "your-client-secret";
})

Uses Azure Key Vault for cloud-based secure storage. Best for cloud deployments.

Auto-Detection
.UseAutoStorage()  // Automatically selects Windows/Linux based on OS

Cache Providers

Cache providers accelerate key retrieval with multi-layer caching:

Memory Cache (Development)
.UseMemoryCache()

In-memory caching using IMemoryCache. Fast but not distributed.

Redis Cache (Production)
.UseRedisCache("localhost:6379")

Distributed caching with Redis. Supports pub/sub for cache invalidation across multiple servers.

Null Cache (Testing)
.UseNullCache()

No caching - always reads from storage. Useful for testing cache-miss scenarios.

Persistence Providers

Persistence providers store key metadata, versions, and audit logs:

InMemory (Development)
.UseInMemoryPersistence()

In-memory storage. Lost on restart. Development only.

Entity Framework Core (Production)
.UseEntityFramework()

Stores data in SQL Server (or any EF Core provider) using your existing DbContext.

Migration Setup:

# Add migration
dotnet ef migrations add AddKeyManagement

# Apply migration
dotnet ef database update

Required entities automatically added:

  • KeyMetadata - Key configuration and current version
  • KeyVersion - Version history with content hashes
  • KeyAuditLog - Full audit trail of admin operations

Features

Key Retrieval (Cache-First Strategy)

The retrieval system uses a three-tier cache strategy for maximum performance:

// Fast path: Cache hit (< 1ms)
var key = await _keyManagement.RetrieveKeyAsync("MyKey");

// Cache layers checked in order:
// 1. Content cache (key content)
// 2. Metadata cache (key metadata)
// 3. Version cache (version info)
// 4. Storage layer (if cache miss)

Performance characteristics:

  • Cache hit: < 1ms
  • Cache miss (storage): 10-50ms
  • Database fallback: 50-200ms

Retrieve by version:

// Get specific version
var key = await _keyManagement.RetrieveKeyByVersionAsync("MyKey", version: 3);

Retrieve using enum (type-safe):

// Define in your code
public enum KeyName
{
    EncryptionKey,
    ApiKey,
    JwtSigningKey
}

// Retrieve
var key = await _keyManagement.RetrieveKeyAsync(KeyName.EncryptionKey);

Key Rotation

Manual Rotation
// Update key content (creates new version)
var result = await _adminService.UpdateKeyContentAsync(
    keyName: "ApiKey",
    newContent: newKeyBytes,
    reason: "Scheduled rotation",
    updatedBy: "RotationService"
);

Console.WriteLine($"Rotated to version {result.Data.CurrentVersion}");
Automatic Rotation

The background service automatically rotates keys based on configured intervals:

// Enable in configuration
services.AddKeyManagement(options =>
{
    options.EnableAutoRotation = true;
    options.RotationCheckInterval = TimeSpan.FromHours(6);  // Check every 6 hours
});

// Per-key configuration
await _adminService.CreateKeyAsync(new CreateKeyRequest
{
    KeyName = "ApiKey",
    RotationIntervalDays = 90,      // Rotate every 90 days
    AutoRotationEnabled = true,     // Enable auto-rotation for this key
    // ...
});

Rotation behavior:

  • Background service runs every RotationCheckInterval
  • Checks all keys with AutoRotationEnabled = true
  • Rotates keys where NextRotationDue <= Now
  • Automatically invalidates all cache layers
  • Triggers cache warming for critical keys
  • Logs all rotation events to audit trail
Monitor Rotation Status
var status = await _adminService.GetRotationServiceStatusAsync();

Console.WriteLine($"Service Running: {status.IsRunning}");
Console.WriteLine($"Last Check: {status.LastCheckTime}");
Console.WriteLine($"Next Check: {status.NextCheckTime}");
Console.WriteLine($"Keys Pending Rotation: {status.KeysPendingRotation.Count}");

foreach (var key in status.KeysPendingRotation)
{
    Console.WriteLine($"  - {key.KeyName}: Due {key.NextRotationDue}");
}

Version Management

Version History
// Get all versions
var versions = await _keyManagement.GetKeyVersionsAsync("ApiKey");

foreach (var version in versions)
{
    Console.WriteLine($"Version {version.Version}: Created {version.CreatedAt}");
    Console.WriteLine($"  Status: {version.Status}");
    Console.WriteLine($"  Active: {version.IsActive}");
}

// Get current version number
var currentVersion = await _keyManagement.GetCurrentVersionAsync("ApiKey");
Rollback

Rollback to a previous version when needed:

// Rollback to version 3
var result = await _adminService.RollbackToVersionAsync(
    keyName: "ApiKey",
    targetVersion: 3,
    reason: "Revert to stable version after incident",
    performedBy: "AdminUser"
);

if (result.Success)
{
    Console.WriteLine($"Rolled back from v{result.Data.FromVersion} to v{result.Data.ToVersion}");
    Console.WriteLine($"Deactivated versions: {string.Join(", ", result.Data.DeactivatedVersions)}");
}

Rollback behavior:

  • Sets target version as current active version
  • Deactivates all versions newer than target
  • Invalidates all cache layers (content, metadata, versions)
  • Triggers cache warming
  • Creates audit log entry
Version Deactivation
// Deactivate old version (cannot deactivate current version)
await _adminService.DeactivateVersionAsync(
    keyName: "ApiKey",
    version: 1,
    reason: "Old version no longer needed",
    performedBy: "AdminUser"
);

// Reactivate version if needed
await _adminService.ReactivateVersionAsync(
    keyName: "ApiKey",
    version: 1,
    performedBy: "AdminUser"
);

Health Monitoring

Key Health
var health = await _keyManagement.GetKeyHealthAsync("ApiKey");

switch (health.Status)
{
    case KeyHealthStatus.Healthy:
        // Key is fresh (> 7 days from rotation)
        break;
    case KeyHealthStatus.Warning:
        // Key approaching rotation (< 7 days)
        Console.WriteLine($"Warning: Rotation due in {health.DaysUntilRotation} days");
        break;
    case KeyHealthStatus.NeedsRotation:
        // Key is overdue for rotation
        Console.WriteLine("Critical: Key needs immediate rotation");
        break;
    case KeyHealthStatus.Inactive:
        // Key is deactivated
        break;
}
System Health
var systemHealth = await _keyManagement.GetSystemHealthAsync();

Console.WriteLine($"Overall Status: {systemHealth.OverallStatus}");
Console.WriteLine($"Total Keys: {systemHealth.TotalKeys}");
Console.WriteLine($"Healthy: {systemHealth.HealthyKeys}");
Console.WriteLine($"Warning: {systemHealth.WarningKeys}");
Console.WriteLine($"Critical: {systemHealth.CriticalKeys}");
Console.WriteLine($"Inactive: {systemHealth.InactiveKeys}");

foreach (var keyHealth in systemHealth.KeyHealthDetails)
{
    if (keyHealth.Status != KeyHealthStatus.Healthy)
    {
        Console.WriteLine($"  - {keyHealth.KeyName}: {keyHealth.Status} (v{keyHealth.CurrentVersion})");
    }
}

Cache Warming

After cache invalidation (rotation, rollback, update), critical keys are automatically preloaded:

// Manually trigger cache warming for specific keys
await _keyManagement.WarmCacheAsync(new[] { "ApiKey", "JwtSigningKey" });

// Configure warming behavior
services.Configure<KeyManagementCacheOptions>(options =>
{
    options.EnableCacheWarming = true;
    options.MaxConcurrentWarmingTasks = 5;  // Warm up to 5 keys concurrently
});

Warming strategy:

  • Preloads content cache
  • Preloads metadata cache
  • Preloads version cache
  • Parallel warming with concurrency limit
  • Resilient to failures (logs but doesn't throw)

Admin Operations

Creating Keys
var result = await _adminService.CreateKeyAsync(new CreateKeyRequest
{
    KeyName = "JwtSigningKey",
    Description = "JWT token signing key for authentication",

    // Auto-generate content or provide custom content
    AutoGenerate = true,        // Auto-generate random bytes
    KeySize = 64,               // Size in bytes (if auto-generating)
    // Content = customBytes,   // Or provide custom content

    // Rotation configuration
    RotationIntervalDays = 90,
    AutoRotationEnabled = true,

    // Version retention
    MaxVersionsToRetain = 5,    // Keep last 5 versions

    // Master key encryption (optional)
    RequiresMasterKeyEncryption = true,  // Encrypt with EncryptionKey (ENV)

    // Metadata
    Category = KeyCategory.Signing,
    SourceType = KeySourceType.Generated,
    Tags = new[] { "jwt", "auth", "production" },

    // Audit
    CreatedBy = "AdminUser"
});
Updating Key Configuration
// Update rotation policy and retention without changing content
var result = await _adminService.UpdateKeyConfigurationAsync(
    keyName: "ApiKey",
    request: new UpdateKeyConfigRequest
    {
        RotationIntervalDays = 60,      // Change from 90 to 60 days
        AutoRotationEnabled = true,
        MaxVersionsToRetain = 10,       // Increase retention
        UpdatedBy = "AdminUser"
    }
);
Key Lifecycle
// Deactivate key (prevents further use)
await _adminService.DeactivateKeyAsync(
    keyName: "OldApiKey",
    reason: "Migrated to new authentication system",
    performedBy: "AdminUser"
);

// Reactivate key
await _adminService.ReactivateKeyAsync(
    keyName: "OldApiKey",
    performedBy: "AdminUser"
);
Audit Logs
// Get audit history
var logs = await _adminService.GetAuditLogsAsync(
    keyName: "ApiKey",
    fromDate: DateTime.UtcNow.AddMonths(-1)
);

foreach (var log in logs)
{
    Console.WriteLine($"{log.PerformedAt}: {log.Operation} by {log.PerformedBy}");
    Console.WriteLine($"  Reason: {log.Reason}");
    Console.WriteLine($"  Details: {log.Details}");
}

Audit operations tracked:

  • KeyCreated
  • KeyUpdated
  • KeyRotated
  • KeyDeactivated
  • KeyReactivated
  • VersionDeactivated
  • VersionReactivated
  • RollbackPerformed
  • ConfigurationUpdated

Architecture

See Architecture Documentation for detailed architecture diagrams, component descriptions, and data flow.

Service Layers (Summary)

┌─────────────────────────────────────────────────────────────┐
│                     Application Layer                       │
│  Controllers / Services / Background Jobs                   │
└──────────────────┬──────────────────┬──────────────────────┘
                   │                  │
      ┌────────────▼──────────┐  ┌───▼────────────────────┐
      │ IKeyManagementService │  │ IKeyManagementAdmin    │
      │ (Read Operations)     │  │ Service                │
      │ - RetrieveKey         │  │ (Write Operations)     │
      │ - GetMetadata         │  │ - CreateKey            │
      │ - GetHealth           │  │ - UpdateKey            │
      └────────────┬──────────┘  │ - RollbackVersion      │
                   │             │ - DeactivateKey        │
                   │             └───┬────────────────────┘
                   │                 │
       ┌───────────▼─────────────────▼──────────────────────┐
       │            Domain Services                         │
       │  - KeyHealthCalculator                            │
       │  - KeyValidationService                           │
       │  - KeyVersionService                              │
       │  - KeyEncryptionOrchestrator                      │
       │  - KeyRotationService                             │
       └───────────┬────────────────────────────────────────┘
                   │
       ┌───────────▼────────────────────────────────────────┐
       │         Infrastructure Services                    │
       ├────────────────────┬──────────────┬───────────────┤
       │  Cache Layer       │ Storage      │ Persistence   │
       │  - IKeyCacheService│ - IKeyStorage│ - IKeyMetadata│
       │  - Multi-layer     │ - Platform-  │   Repository  │
       │  - Invalidation    │   specific   │ - IKeyVersion │
       │  - Warming         │   providers  │   Repository  │
       │  - Pub/sub         │              │ - IKeyAudit   │
       │                    │              │   Repository  │
       └────────────────────┴──────────────┴───────────────┘

Repository Pattern

The system uses repository pattern for data access:

  • IKeyMetadataRepository: Key configuration and current version
  • IKeyVersionRepository: Version history and content hashes
  • IKeyAuditRepository: Audit trail of admin operations

Implementations:

  • InMemory: For development and testing
  • Entity Framework: For production SQL Server / PostgreSQL / MySQL

Cache Strategies

Multi-Layer Caching
Request Flow:
1. Content Cache     → "key:ApiKey:content:v5"      (1-hour TTL)
2. Metadata Cache    → "key:ApiKey:metadata"        (5-min TTL)
3. Version Cache     → "key:ApiKey:versions"        (5-min TTL)
4. Storage Layer     → Windows DPAPI / Azure KV
5. Database          → SQL Server (metadata only)
Distributed Cache Invalidation

For multi-server deployments:

// Server 1: Updates key
await _adminService.UpdateKeyContentAsync("ApiKey", newContent, ...);

// Internally publishes to Redis pub/sub:
// Channel: "feedemy:keymanagement:invalidation"
// Message: {"KeyName": "ApiKey", "Operation": "Update"}

// Server 2, 3, 4, ...: Automatically invalidate local cache
// All servers receive pub/sub message and clear cache

Domain Services

KeyHealthCalculator

Calculates health status based on rotation schedule:

  • Healthy: > 7 days until rotation
  • Warning: 0-7 days until rotation
  • NeedsRotation: Overdue for rotation
  • Inactive: Key is deactivated
KeyValidationService

Validates all admin operations:

  • Key name format and uniqueness
  • Content size limits
  • Rotation interval bounds
  • Version constraints
  • Deactivation rules
KeyVersionService

Manages version lifecycle:

  • Version numbering (auto-increment)
  • Version retention (cleanup old versions)
  • Active/inactive status
  • Content hash calculation
KeyEncryptionOrchestrator

Handles master key encryption (envelope encryption):

  • ENV keys encrypted with EncryptionKey
  • Re-encryption after EncryptionKey rotation
  • Version mismatch detection
KeyRotationService

Orchestrates rotation process:

  • Validation and locking
  • Content generation/update
  • Cache invalidation
  • Notification dispatch

Testing

Running Unit Tests

cd tests/Feedemy.KeyManagement.Tests
dotnet test

149 unit tests covering:

  • Core services (KeyManagementService, KeyManagementAdminService)
    • Creation, Update, Deactivation, Rollback operations
    • Service Status monitoring
    • Audit log retrieval and filtering
    • Version mismatch detection
  • Domain services (Health, Validation, Versioning, Encryption)
  • Cache services (Multi-layer caching, Invalidation, Warming)
  • Storage coordination
  • Repository patterns

Running Integration Tests

cd tests/Feedemy.KeyManagement.IntegrationTests
dotnet test

30 integration tests covering:

  • End-to-end key lifecycle
  • Multi-threaded scenarios
  • Cache invalidation propagation
  • Automatic rotation
  • Rollback workflows
  • Provider integration

Sample Application

cd samples/Feedemy.KeyManagement.Sample
dotnet run

Demonstrates:

  • Creating keys
  • Retrieving keys
  • Getting metadata
  • Checking health
  • Updating keys
  • Version management

Migration from FeedemyBackend

If you're migrating from the embedded KeyManagement in FeedemyBackend, follow these steps:

1. Update Package References

Remove old embedded code and install packages:

# Remove old KeyManagement folder from project
rm -rf Services/KeyManagement

# Install new packages
dotnet add package Feedemy.KeyManagement
dotnet add package Feedemy.KeyManagement.Providers.EntityFramework
dotnet add package Feedemy.KeyManagement.Providers.Windows  # Or Linux/Azure

2. Update Service Registration

Before (FeedemyBackend):

services.AddScoped<IKeyManagementService, KeyManagementService>();
services.AddScoped<IKeyRepository, KeyRepository>();
// ... manual registration

After (Feedemy.KeyManagement):

services.AddKeyManagement(options =>
{
    options.EnableAutoRotation = true;
    options.RotationCheckInterval = TimeSpan.FromHours(6);
})
.UseWindowsStorage()
.UseRedisCache(Configuration.GetConnectionString("Redis"))
.UseEntityFramework();

3. Update DbContext

Add to your ApplicationDbContext:

using Feedemy.KeyManagement.Persistence.EntityFramework;

public class ApplicationDbContext : DbContext
{
    // ... existing DbSets

    // Add KeyManagement entities
    public DbSet<KeyMetadata> KeyMetadata { get; set; }
    public DbSet<KeyVersion> KeyVersions { get; set; }
    public DbSet<KeyAuditLog> KeyAuditLogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Apply KeyManagement configurations
        modelBuilder.ApplyConfigurationsFromAssembly(
            typeof(KeyManagementDbContext).Assembly);
    }
}

4. Create Migration

dotnet ef migrations add AddKeyManagement
dotnet ef database update

5. Update Code References

Key retrieval:

// Before
var key = await _keyRepository.GetKeyAsync("EncryptionKey");

// After
var key = await _keyManagement.RetrieveKeyAsync("EncryptionKey");

Admin operations:

// Before
await _keyRepository.CreateKeyAsync(new Key { ... });

// After
await _adminService.CreateKeyAsync(new CreateKeyRequest { ... });

6. Adapter Pattern (Optional)

If you need backward compatibility during migration:

public class KeyManagementAdapter : IKeyRepository  // Old interface
{
    private readonly IKeyManagementService _keyManagement;
    private readonly IKeyManagementAdminService _adminService;

    public async Task<byte[]> GetKeyAsync(string keyName)
    {
        return await _keyManagement.RetrieveKeyAsync(keyName);
    }

    public async Task CreateKeyAsync(Key key)
    {
        await _adminService.CreateKeyAsync(new CreateKeyRequest
        {
            KeyName = key.Name,
            Content = key.Content,
            // ... map properties
        });
    }

    // ... implement other methods
}

Breaking Changes

  1. Cache layer now mandatory: Old code bypassed cache, new code is cache-first
  2. Version management: All keys now have versions (old code was single-version)
  3. Master key encryption: Now optional (configure via RequiresMasterKeyEncryption)
  4. Audit logs: Now built-in (old code had limited logging)
  5. Health monitoring: New feature (no equivalent in old code)

Contributing

Development Setup

# Clone repository
git clone https://github.com/feedemy/keymanagement.git
cd keymanagement

# Restore packages
dotnet restore

# Build solution
dotnet build

# Run all tests
dotnet test

Code Style

  • Follow .NET coding conventions
  • Use nullable reference types
  • Add XML documentation for public APIs
  • Write unit tests for new features
  • Update integration tests for workflows

Pull Request Process

  1. Create feature branch from main
  2. Implement feature with tests
  3. Ensure all tests pass: dotnet test
  4. Update README if adding features
  5. Submit PR with description

License

MIT License

Copyright (c) 2025 Feedemy

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Documentation

  • Architecture Guide - System architecture, data flows, and component diagrams
  • Migration Guide - Step-by-step migration from legacy systems
  • Redis Setup Guide - Detailed Redis configuration and troubleshooting
  • API Reference: See XML documentation in code

Support

Asymmetric Key Support (v2.0+)

Feedemy.KeyManagement v2.0 adds RSA and ECDSA asymmetric key support for digital signatures, JWT signing, and public key encryption.

Creating Asymmetric Keys

RSA Key for JWT Signing
var createResult = await keyManagementAdmin.CreateKeyAsync(new CreateKeyRequest
{
    KeyName = "JwtSigningKey",
    KeyType = KeyType.AsymmetricRSA,
    RsaKeySize = RsaKeySize.Rsa2048,
    Category = KeyCategory.Signing,
    Description = "RSA key for JWT RS256 tokens",
    AutoRotationEnabled = true,
    RotationIntervalDays = 90,
    CreatedBy = "System"
});
ECDSA Key for Document Signing
var createResult = await keyManagementAdmin.CreateKeyAsync(new CreateKeyRequest
{
    KeyName = "DocumentSigningKey",
    KeyType = KeyType.AsymmetricECDSA,
    EcdsaCurve = EcdsaCurve.P256,
    Category = KeyCategory.Signing,
    Description = "ECDSA key for document signatures",
    CreatedBy = "Admin"
});

Using Asymmetric Keys

Inject IAsymmetricKeyOperations service:

public class JwtService
{
    private readonly IAsymmetricKeyOperations _asymmetricOps;

    public JwtService(IAsymmetricKeyOperations asymmetricOps)
    {
        _asymmetricOps = asymmetricOps;
    }

    public async Task<string> CreateJwtToken(Dictionary<string, object> claims)
    {
        var payload = JsonSerializer.SerializeToUtf8Bytes(claims);

        // Sign with RS256
        var signature = await _asymmetricOps.SignAsync(
            "JwtSigningKey",
            payload,
            HashAlgorithmName.SHA256);

        return EncodeJwt(payload, signature);
    }

    public async Task<bool> VerifyJwtToken(string token)
    {
        var (payload, signature) = DecodeJwt(token);

        // Verify using cached public key (very fast - ~0.15ms)
        return await _asymmetricOps.VerifyAsync(
            "JwtSigningKey",
            payload,
            signature,
            HashAlgorithmName.SHA256);
    }

    public async Task<string> GetPublicKeyForClients()
    {
        // Export public key in PEM format (safe to share)
        return await _asymmetricOps.GetPublicKeyPemAsync("JwtSigningKey");
    }
}

Supported Algorithms

Algorithm Key Sizes Use Case Performance
RSA 2048, 3072, 4096-bit Signing, Encryption Sign: ~1.2ms, Verify: ~0.15ms
ECDSA P-256, P-384, P-521 Signing (compact) Sign: ~0.35ms, Verify: ~0.12ms

Key Rotation

Asymmetric keys rotate automatically:

// Auto-rotation (background service)
services.AddKeyManagement(options =>
{
    options.EnableAutoRotation = true;
    options.DefaultRotationDays = 90;
});

// Manual rotation
await rotationService.RotateKeyAsync(
    "JwtSigningKey",
    "Scheduled rotation",
    "Admin");

Note: After rotation, old signatures remain verifiable if you keep old key versions. Use version-aware verification for backward compatibility.

Storage Compatibility

All storage providers support asymmetric keys:

  • Windows DPAPI
  • Linux AES-256-GCM
  • Azure Key Vault Secrets
  • InMemory (testing)

Private keys are always encrypted at rest using master key encryption.


Roadmap

  • HashiCorp Vault provider
  • AWS Secrets Manager provider
  • Multi-region replication support
  • Key import/export utilities
  • Grafana dashboard templates
  • Performance benchmark suite
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Feedemy.KeyManagement:

Package Downloads
Feedemy.KeyManagement.Providers.Npgsql

PostgreSQL (Npgsql) persistence provider for Feedemy.KeyManagement. Provides PostgreSQL storage for key metadata, versions, and audit logs with migrations support. Platform-independent alternative to SQL Server.

Feedemy.KeyManagement.Providers.EntityFramework

Entity Framework Core persistence provider for Feedemy.KeyManagement. Provides SQL Server storage for key metadata, versions, and audit logs with migrations support. Fully tested with 56/56 integration tests passing.

Feedemy.KeyManagement.Providers.Sqlite

SQLite persistence provider for Feedemy.KeyManagement. Provides lightweight, file-based storage for key metadata, versions, and audit logs. Ideal for development, testing, and single-server deployments.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.0 78 3/9/2026
3.0.6 72 3/9/2026
3.0.5 89 3/9/2026
3.0.4 80 3/9/2026
3.0.3 111 3/9/2026
3.0.2 99 3/9/2026
3.0.1 110 2/27/2026
2.5.10 230 1/21/2026
2.5.9 154 1/10/2026
2.5.8 106 1/10/2026
2.5.7 104 1/9/2026
2.5.6 101 1/9/2026
2.5.5 132 1/6/2026
2.5.4 133 1/4/2026 2.5.4 is deprecated because it has critical bugs.
2.5.3 239 1/3/2026 2.5.3 is deprecated because it has critical bugs.
2.5.2 1,424 12/1/2025 2.5.2 is deprecated because it has critical bugs.
2.5.1 1,050 12/1/2025 2.5.1 is deprecated because it is no longer maintained and has critical bugs.
2.5.0 1,043 12/1/2025 2.5.0 is deprecated because it is no longer maintained and has critical bugs.

v3.0.0 - Generic Library Refactor (BREAKING CHANGES)

BREAKING CHANGES:
- Removed KeyName enum - library is now fully generic and string-based
- Removed all KeyName enum overloads from interfaces (IKeyManagementService, IKeyManagementAdminService, IKeyRotationService)
- Removed KeyNameExtensions (ToKeyString(), ToKeyName())
- Removed KeyConfigurationExtensions with application-specific key configurations
- Applications must now provide their own key names as strings
- WarmCriticalKeysAsync() now requires string[] parameter (no default hardcoded keys)

MIGRATION GUIDE:
- Replace: KeyName.AKey → "AKey" (or your custom key name)
- Replace: keyName.ToKeyString() → keyName (direct string usage)
- Replace: KeyName.EncryptionKey → "EncryptionKey"
- Provide your own KeyConfiguration for each key instead of using GetKeyConfiguration()
- Pass critical key names to WarmCriticalKeysAsync(string[] criticalKeyNames)

WHY THIS CHANGE:
- Library was not truly generic - contained application-specific business logic
- Hardcoded key names (AKey, CKey, OKey, LidioDeliverySecrets, NetGSM_AppKey, etc.)
- Applications can now use ANY key names without library modifications
- Follows true library design principles - zero business logic coupling

IMPORTANT: This is a major version bump due to breaking API changes. Review migration guide before upgrading.