Feedemy.KeyManagement
2.5.0
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
<PackageReference Include="Feedemy.KeyManagement" Version="2.5.0" />
<PackageVersion Include="Feedemy.KeyManagement" Version="2.5.0" />
<PackageReference Include="Feedemy.KeyManagement" />
paket add Feedemy.KeyManagement --version 2.5.0
#r "nuget: Feedemy.KeyManagement, 2.5.0"
#:package Feedemy.KeyManagement@2.5.0
#addin nuget:?package=Feedemy.KeyManagement&version=2.5.0
#tool nuget:?package=Feedemy.KeyManagement&version=2.5.0
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.IntegrationTestsrun (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 throughIKeyEncryptionOrchestrator(e.g., by enforcingRetrieveEncryptedKeyAsyncfor 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 availableDegraded: Warning keys detected or storage issuesUnhealthy: 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:
- Master Key Encryption: ENV keys automatically encrypted with
EncryptionKey - Encrypted Storage at Rest: Both Windows (DPAPI) and Linux (AES-256-GCM) encrypt keys on disk
- Compensating Transactions: Key creation failures automatically roll back storage writes
- Sensitive Logging: Key names only logged at Debug level (not Information)
- 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:
Install Package:
dotnet add package Feedemy.KeyManagementUpdate DI Registration:
// Old (FeedemyBackend): services.AddKeyManagementServices(); // New (v1.0): services.AddKeyManagement(options => { options.Persistence.ProviderType = PersistenceProviderType.EntityFramework; });No Code Changes: Interfaces remain compatible (
IKeyManagementService,IKeyManagementAdminService)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 versionKeyVersion- Version history with content hashesKeyAuditLog- 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
- Cache layer now mandatory: Old code bypassed cache, new code is cache-first
- Version management: All keys now have versions (old code was single-version)
- Master key encryption: Now optional (configure via
RequiresMasterKeyEncryption) - Audit logs: Now built-in (old code had limited logging)
- 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
- Create feature branch from
main - Implement feature with tests
- Ensure all tests pass:
dotnet test - Update README if adding features
- 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
- Documentation: https://github.com/feedemy/keymanagement/wiki
- Issues: https://github.com/feedemy/keymanagement/issues
- Discussions: https://github.com/feedemy/keymanagement/discussions
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 | Versions 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. |
-
net10.0
- Azure.Identity (>= 1.17.1)
- Azure.Security.KeyVault.Secrets (>= 4.8.0)
- Microsoft.Data.SqlClient (>= 6.1.3)
- Microsoft.EntityFrameworkCore.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- OpenTelemetry.Api (>= 1.14.0)
- Polly (>= 8.6.5)
- StackExchange.Redis (>= 2.10.1)
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.3 | 239 | 1/3/2026 | |
| 2.5.2 | 1,424 | 12/1/2025 | |
| 2.5.1 | 1,050 | 12/1/2025 | |
| 2.5.0 | 1,043 | 12/1/2025 |
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.