KNM.LicenseValidator 1.1.1
Ecco il README.md aggiornato con il nuovo pattern fluent builder e le configurazioni corrette:
# KNM License Validator
[](https://dotnet.microsoft.com/)
A robust, enterprise-grade license validation library for .NET 10 applications with hybrid offline/online validation, triple-layer security, and fluent configuration API.
## β¨ Features
- **π Hybrid Validation**: Online-first with automatic offline fallback for maximum reliability
- **π Triple Security Layer**: RSA signatures + AES-256 encryption + HMAC integrity verification
- **β‘ Lightweight & Fast**: Zero database dependencies - integrate with your existing data layer
- **π― Fluent Configuration**: Modern builder pattern with `.With()` methods for intuitive setup
- **π Custom Storage Providers**: Implement `ILicenseKeyProvider` for database, Azure Key Vault, or any storage
- **π Custom License Format**: Branded license files with Base32 encoding
- **π§ AOT & Trimming Ready**: Fully compatible with Native AOT compilation
- **π¦ Dependency Injection**: First-class DI support with ASP.NET Core integration
- **πΎ Intelligent Caching**: Built-in validation result caching with configurable expiration
- **π Startup Validation**: Optional background service for license validation on application startup
## π Requirements
- .NET 10.0
- C# 13 or later
- KNM.CryptoHelper (automatically installed)
## π¦ Installation
### From Private NuGet Feed
```bash
# Add your private feed source (one-time setup)
dotnet nuget add source "https://your-private-feed-url" \
--name "KNM-Internal" \
--username "your-username" \
--password "your-token" \
--store-password-in-clear-text
# Install the package
dotnet add package KNM.LicenseValidator
From Local Path
dotnet add package KNM.LicenseValidator --source "./local-packages"
PackageReference
<PackageReference Include="KNM.LicenseValidator" Version="1.0.0" />
π Quick Start
1. Minimal Configuration
using KNM.LicenseValidator.Extensions;
using KNM.LicenseValidator.Configuration;
var builder = WebApplication.CreateBuilder(args);
// Simple offline validation
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.OfflineOnly)
.WithOfflineFiles("./Licenses")
.Build();
var app = builder.Build();
app.Run();
2. Fluent Configuration (Recommended)
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.Hybrid)
.WithApiSettings(
baseUrl: "https://api.yourcompany.com",
apiKey: "your-api-key",
timeout: TimeSpan.FromSeconds(60))
.WithOfflineFiles(
licenseDirectory: "./Licenses",
publicKeyFileName: "PublicKey.xml",
licenseFileName: "License.txt")
.WithCacheExpiration(TimeSpan.FromHours(24))
.WithFallbackBehavior(
fallbackToOffline: true,
fallbackToOnline: false)
.WithSecuritySettings(
strictValidation: true,
logAttempts: true)
.WithStartupValidation()
.Build();
3. Configuration from appsettings.json
appsettings.json:
{
"LicenseValidator": {
"Mode": "Hybrid",
"ApiBaseUrl": "https://api.yourcompany.com",
"ApiKey": "your-api-key-here",
"LicenseDirectory": "./Licenses",
"PublicKeyFileName": "PublicKey.xml",
"LicenseFileName": "License.txt",
"CacheExpiration": "1.00:00:00",
"ApiTimeout": "00:00:30",
"ValidateOnStartup": true,
"FallbackToOffline": true,
"StrictValidation": true,
"SoftwareId": 1
}
}
Program.cs:
builder.Services
.AddKnmLicenseValidator(builder.Configuration)
.Build();
// Or with custom provider
builder.Services
.AddKnmLicenseValidator(builder.Configuration)
.UseCustomProvider<DatabaseLicenseKeyProvider>()
.WithStartupValidation()
.Build();
4. Basic Usage
public class LicenseService
{
private readonly ILicenseValidator _validator;
private readonly ILogger<LicenseService> _logger;
public LicenseService(
ILicenseValidator validator,
ILogger<LicenseService> logger)
{
_validator = validator;
_logger = logger;
}
public async Task<bool> CheckLicenseAsync()
{
var result = await _validator.ValidateLicenseAsync();
if (result.IsValid)
{
_logger.LogInformation(
"License valid until {ExpirationDate} (Source: {Source})",
result.LicenseInfo?.ExpirationDate,
result.Source
);
return true;
}
_logger.LogWarning("License validation failed: {Error}", result.ErrorMessage);
return false;
}
public async Task<bool> CheckSpecificFeatureAsync(string featureName)
{
var result = await _validator.ValidateLicenseAsync();
return result.HasFeature(featureName, "features");
}
}
π Advanced Usage
Custom License Key Provider
Implement ILicenseKeyProvider for custom storage (database, Azure Key Vault, etc.):
public class DatabaseLicenseKeyProvider : ILicenseKeyProvider
{
private readonly ApplicationDbContext _context;
public DatabaseLicenseKeyProvider(ApplicationDbContext context)
{
_context = context;
}
public async Task<string?> GetLicenseKeyAsync()
{
var license = await _context.Licenses
.FirstOrDefaultAsync(l => l.IsActive);
return license?.Key;
}
public async Task<bool> StoreLicenseKeyAsync(string licenseKey)
{
var license = new License { Key = licenseKey, IsActive = true };
_context.Licenses.Add(license);
return await _context.SaveChangesAsync() > 0;
}
public async Task<bool> ClearLicenseKeyAsync()
{
var licenses = await _context.Licenses.ToListAsync();
_context.Licenses.RemoveRange(licenses);
return await _context.SaveChangesAsync() > 0;
}
}
Register with fluent API:
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.Hybrid)
.WithApiSettings("https://api.company.com", "api-key")
.UseCustomProvider<DatabaseLicenseKeyProvider>()
.Build();
// Or use a specific instance
var provider = new AzureKeyVaultProvider(keyVaultUrl);
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.OnlineOnly)
.WithApiSettings("https://api.company.com", "api-key")
.UseProvider(provider)
.Build();
Working with Extra Data
public async Task ProcessLicenseDataAsync()
{
var result = await _validator.ValidateLicenseAsync();
if (!result.IsValid) return;
// Check for specific features
var hasReporting = result.HasFeature("reporting", "modules");
var hasExport = result.HasFeature("export", "modules");
// Extract typed data from ExtraData JSON
var maxUsers = result.GetExtraDataValue<int>("maxUsers");
var companyName = result.GetExtraDataValue<string>("companyName");
_logger.LogInformation(
"License for {Company} allows {MaxUsers} users",
companyName,
maxUsers
);
}
Force Specific Validation Mode
// Force online validation (requires internet)
var onlineResult = await _validator.ValidateOnlineAsync("LICENSE-KEY-HERE");
// Force offline validation (uses local files)
var offlineResult = await _validator.ValidateOfflineAsync();
// Get cached result without validation
var cached = _validator.GetCachedResult();
if (cached?.IsValid == true)
{
_logger.LogInformation("Using cached validation result");
}
// Clear cache to force fresh validation
_validator.ClearCache();
Custom HTTP Client Configuration
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.OnlineOnly)
.WithApiSettings("https://api.company.com", "api-key")
.WithHttpClient(client =>
{
client.DefaultRequestHeaders.Add("X-Custom-Header", "value");
client.DefaultRequestHeaders.Add("X-Application-Id", "my-app");
})
.Build();
Context-Aware Configuration
builder.Services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.Hybrid)
.WithApiSettings("https://api.company.com", "api-key")
.WithContext(
softwareId: 42,
userId: "user@example.com",
installationId: Guid.NewGuid())
.Build();
ASP.NET Core Middleware Example
public class LicenseValidationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LicenseValidationMiddleware> _logger;
public LicenseValidationMiddleware(
RequestDelegate next,
ILogger<LicenseValidationMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(
HttpContext context,
ILicenseValidator validator)
{
var result = await validator.ValidateLicenseAsync();
if (!result.IsValid)
{
_logger.LogWarning("Request blocked: Invalid license");
context.Response.StatusCode = 402; // Payment Required
await context.Response.WriteAsJsonAsync(new
{
error = "License validation failed",
message = result.ErrorMessage
});
return;
}
await _next(context);
}
}
Register in Program.cs:
app.UseMiddleware<LicenseValidationMiddleware>();
Blazor Component Example
@inject ILicenseValidator LicenseValidator
<div class="license-status">
@if (_isValidating)
{
<p>Validating license...</p>
}
else if (_licenseResult?.IsValid == true)
{
<div class="alert alert-success">
<strong>License Active</strong>
<p>Valid until: @_licenseResult.LicenseInfo?.ExpirationDate.ToShortDateString()</p>
</div>
}
else
{
<div class="alert alert-danger">
<strong>License Invalid</strong>
<p>@_licenseResult?.ErrorMessage</p>
</div>
}
</div>
@code {
private LicenseValidationResult? _licenseResult;
private bool _isValidating = true;
protected override async Task OnInitializedAsync()
{
_licenseResult = await LicenseValidator.ValidateLicenseAsync();
_isValidating = false;
}
}
βοΈ Configuration Options
Fluent Builder Methods
| Method | Parameters | Description |
|---|---|---|
WithValidationMode() |
ValidationMode |
Sets validation mode: OfflineOnly, OnlineOnly, or Hybrid |
WithApiSettings() |
baseUrl, apiKey, timeout? |
Configures online API validation settings |
WithOfflineFiles() |
directory, publicKey?, license? |
Sets paths for offline license files |
WithCacheExpiration() |
TimeSpan |
Sets cache expiration duration |
WithFallbackBehavior() |
toOffline, toOnline |
Configures fallback behavior between modes |
WithSecuritySettings() |
strict, logAttempts |
Configures security and logging settings |
WithContext() |
softwareId, userId?, installationId? |
Sets application context information |
WithStartupValidation() |
- | Enables validation on application startup |
WithHttpClient() |
Action<HttpClient> |
Customizes HTTP client configuration |
UseCustomProvider<T>() |
- | Registers custom license key provider type |
UseProvider() |
ILicenseKeyProvider |
Registers specific provider instance |
Configuration Properties
| Property | Type | Default | Description |
|---|---|---|---|
Mode |
ValidationMode |
Hybrid |
Validation mode |
ApiBaseUrl |
string? |
null |
Base URL for online validation API |
ApiKey |
string? |
null |
API key for authentication |
ApiTimeout |
TimeSpan |
00:00:30 |
HTTP request timeout |
LicenseDirectory |
string |
./Licenses |
Directory containing license files |
PublicKeyFileName |
string |
PublicKey.xml |
RSA public key filename |
LicenseFileName |
string |
License.txt |
License file name |
PublicKeyPath |
string? |
null |
Absolute path to public key |
LicensePath |
string? |
null |
Absolute path to license file |
CacheExpiration |
TimeSpan |
00:30:00 |
Cache expiration duration |
ValidateOnStartup |
bool |
true |
Validate on application startup |
FallbackToOffline |
bool |
true |
Fallback to offline if online fails |
FallbackToOnline |
bool |
false |
Fallback to online if offline fails |
StrictValidation |
bool |
true |
Enforce strict validation checks |
LogValidationAttempts |
bool |
true |
Log validation attempts |
SoftwareId |
int |
0 |
Software identifier |
UserId |
string? |
null |
User identifier |
InstallationId |
Guid? |
null |
Installation identifier |
π Security Architecture
The library implements a three-layer security model:
- RSA Signature Verification: Ensures license authenticity using public key cryptography (2048-bit RSA)
- AES-256 Encryption: Protects license data confidentiality with industry-standard encryption
- HMAC-SHA256 Integrity Check: Validates data hasn't been tampered with (online mode only)
License File Format
-----START LICENSE DATA-----
[Base32-encoded encrypted payload]
-----STOP LICENSE DATA-----
The payload contains:
- Expiration date (UTC ticks format)
- Software ID (integer identifier)
- Client ID (integer identifier)
- Cryptographic salt (unique per license)
- Extra data (JSON format for custom fields)
Security Best Practices
- Keep private keys secure: Never distribute RSA private keys with your application
- Use HTTPS: Always use HTTPS for online validation API endpoints
- Rotate API keys: Periodically rotate API keys for enhanced security
- Monitor failed attempts: Log and monitor failed validation attempts
- Secure storage: Use secure storage mechanisms (Azure Key Vault, etc.) for license keys
π§ͺ Testing
Unit Test Example
[Fact]
public async Task ValidateLicense_WithValidLicense_ReturnsSuccess()
{
// Arrange
var services = new ServiceCollection();
services
.AddKnmLicenseValidator()
.WithValidationMode(ValidationMode.OfflineOnly)
.WithOfflineFiles("./TestLicenses")
.Build();
var provider = services.BuildServiceProvider();
var validator = provider.GetRequiredService<ILicenseValidator>();
// Act
var result = await validator.ValidateOfflineAsync();
// Assert
Assert.True(result.IsValid);
Assert.NotNull(result.LicenseInfo);
Assert.Equal(ValidationSource.Offline, result.Source);
}
Integration Test Example
public class LicenseValidationIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public LicenseValidationIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Endpoint_WithValidLicense_Returns200()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/license/status");
// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("valid", content, StringComparison.OrdinalIgnoreCase);
}
}
π Troubleshooting
License file not found
Problem: Application can't find license files.
Solution:
- Ensure
LicenseDirectorypoints to the correct location - Verify files exist:
PublicKey.xmlandLicense.txt - Check file permissions (read access required)
- Use absolute paths with
PublicKeyPathandLicensePathproperties
Online validation fails
Problem: Online validation returns errors or times out.
Solution:
- Verify
ApiBaseUrlis correct and reachable - Check
ApiKeyis valid and not expired - Ensure firewall allows outbound HTTPS connections
- Increase
ApiTimeout:.WithApiSettings(baseUrl, apiKey, TimeSpan.FromSeconds(60))
RSA signature verification failed
Problem: License validation fails with RSA signature error.
Solution:
- Ensure public key file matches the private key used to sign the license
- Verify license file hasn't been modified or corrupted
- Check file encoding (UTF-8 expected)
Cache not working
Problem: Validation doesn't use cached results.
Solution:
- Adjust cache expiration:
.WithCacheExpiration(TimeSpan.FromHours(24)) - Verify cache isn't being cleared unexpectedly
- Use
GetCachedResult()to inspect cache state
π Performance Considerations
- Caching: Validation results are cached to minimize API calls and file I/O
- Singleton Pattern: Core validators use appropriate lifetimes for optimal performance
- Async Operations: All validation methods are fully asynchronous
- Memory Efficient: Minimal memory footprint with no database dependencies
- Fast Startup: Optional startup validation runs in background, non-blocking
Typical Performance:
- Offline validation: < 10ms (with warm cache)
- Online validation: 50-500ms (network dependent)
- Cached result retrieval: < 1ms
π€ Contributing
We welcome contributions! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Related Packages
- KNM.CryptoHelper - Cryptographic utilities for encryption and signing
- KNM.RazorComponents - Reusable Blazor component library
π§ Support
For issues, questions, or suggestions:
- π Report bugs: GitHub Issues
- π¬ Discussions: GitHub Discussions
- π§ Email: support@konima.com
πΊοΈ Roadmap
- Support for .NET 11 when released
- Hardware ID binding support
- Multi-tenant license management
- Cloud-based license server reference implementation
- License renewal automation
- Enhanced audit logging
π Changelog
Version 1.0.0 (Initial Release)
- Fluent configuration API with builder pattern
- Hybrid offline/online validation
- Triple-layer security (RSA + AES + HMAC)
- Custom license key providers
- Validation result caching
- Startup validation service
- Full .NET 10 and AOT support
Made with β€οΈ by KoNiMa
Built for enterprise applications requiring robust license management
No packages depend on KNM.LicenseValidator.
.NET 10.0
- KNM.CryptoHelper (>= 1.3.9)
- Microsoft.EntityFrameworkCore (>= 10.0.1)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.1)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Http (>= 10.0.1)
- Microsoft.Extensions.Localization.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
- System.Security.Cryptography.ProtectedData (>= 10.0.1)