Files
git.stella-ops.org/docs/cli/crypto-plugins.md
master b444284be5 docs: Archive Sprint 3500 (PoE), Sprint 7100 (Proof Moats), and additional sprints
Archive completed sprint documentation and deliverables:

## SPRINT_3500 - Proof of Exposure (PoE) Implementation (COMPLETE )
- Windows filesystem hash sanitization (colon → underscore)
- Namespace conflict resolution (Subgraph → PoESubgraph)
- Mock test improvements with It.IsAny<>()
- Direct orchestrator unit tests
- 8/8 PoE tests passing (100% success)
- Archived to: docs/implplan/archived/2025-12-23-sprint-3500-poe/

## SPRINT_7100.0001 - Proof-Driven Moats Core (COMPLETE )
- Four-tier backport detection system
- 9 production modules (4,044 LOC)
- Binary fingerprinting (TLSH + instruction hashing)
- VEX integration with proof-carrying verdicts
- 42+ unit tests passing (100% success)
- Archived to: docs/implplan/archived/2025-12-23-sprint-7100-proof-moats/

## SPRINT_7100.0002 - Proof Moats Storage Layer (COMPLETE )
- PostgreSQL repository implementations
- Database migrations (4 evidence tables + audit)
- Test data seed scripts (12 evidence records, 3 CVEs)
- Integration tests with Testcontainers
- <100ms proof generation performance
- Archived to: docs/implplan/archived/2025-12-23-sprint-7100-proof-moats/

## SPRINT_3000_0200 - Authority Admin & Branding (COMPLETE )
- Console admin RBAC UI components
- Branding editor with tenant isolation
- Authority backend endpoints
- Archived to: docs/implplan/archived/

## Additional Documentation
- CLI command reference and compliance guides
- Module architecture docs (26 modules documented)
- Data schemas and contracts
- Operations runbooks
- Security risk models
- Product roadmap

All archived sprints achieved 100% completion of planned deliverables.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 15:02:38 +02:00

1018 lines
27 KiB
Markdown

# stella CLI - Crypto Plugin Development Guide
**Sprint:** SPRINT_4100_0006_0006 - CLI Documentation Overhaul
## Overview
This guide explains how to develop custom cryptographic plugins for the `stella` CLI. Plugins enable support for regional cryptographic algorithms (GOST, eIDAS, SM) and custom signing infrastructure (HSMs, KMS, remote signers).
**Prerequisites:**
- .NET 10 SDK
- Understanding of cryptographic concepts (signing, verification, key management)
- Familiarity with Dependency Injection patterns
---
## Plugin Interface Specification
### ICryptoProvider
All crypto providers must implement the `ICryptoProvider` interface:
```csharp
namespace StellaOps.Cli.Crypto;
/// <summary>
/// Core interface for all cryptographic providers
/// </summary>
public interface ICryptoProvider
{
/// <summary>
/// Unique provider name (e.g., "gost", "eidas", "sm", "default")
/// Used for --provider flag in CLI
/// </summary>
string Name { get; }
/// <summary>
/// Supported cryptographic algorithms
/// (e.g., "GOST12-256", "ECDSA-P256", "SM2")
/// </summary>
string[] SupportedAlgorithms { get; }
/// <summary>
/// Sign data with specified algorithm and key
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="algorithm">Algorithm to use</param>
/// <param name="keyRef">Key reference</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Signature bytes</returns>
Task<byte[]> SignAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken = default);
/// <summary>
/// Verify signature
/// </summary>
/// <param name="data">Original data</param>
/// <param name="signature">Signature to verify</param>
/// <param name="algorithm">Algorithm used</param>
/// <param name="keyRef">Key reference (public key)</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if valid, false otherwise</returns>
Task<bool> VerifyAsync(
byte[] data,
byte[] signature,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken = default);
/// <summary>
/// List available keys in this provider
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of available keys</returns>
Task<IReadOnlyList<CryptoKeyInfo>> ListKeysAsync(
CancellationToken cancellationToken = default);
}
```
### ICryptoProviderDiagnostics (Optional)
For advanced diagnostics and health checks:
```csharp
namespace StellaOps.Cli.Crypto;
/// <summary>
/// Optional interface for provider diagnostics and health checks
/// </summary>
public interface ICryptoProviderDiagnostics
{
/// <summary>
/// Run provider self-test
/// </summary>
Task<ProviderHealthCheck> HealthCheckAsync(
CancellationToken cancellationToken = default);
/// <summary>
/// Get provider version and capabilities
/// </summary>
ProviderInfo GetInfo();
}
/// <summary>
/// Health check result
/// </summary>
public sealed record ProviderHealthCheck
{
public required string ProviderName { get; init; }
public required bool IsHealthy { get; init; }
public required string[] Checks { get; init; }
public string? ErrorMessage { get; init; }
}
/// <summary>
/// Provider metadata
/// </summary>
public sealed record ProviderInfo
{
public required string Name { get; init; }
public required string Version { get; init; }
public required string[] Capabilities { get; init; }
public required string[] SupportedAlgorithms { get; init; }
}
```
---
## Supporting Types
### CryptoKeyReference
Represents a reference to a cryptographic key:
```csharp
namespace StellaOps.Cli.Crypto;
/// <summary>
/// Reference to a cryptographic key
/// </summary>
public sealed record CryptoKeyReference
{
/// <summary>
/// Key identifier (e.g., "prod-key-2024", file path, HSM slot)
/// </summary>
public required string KeyId { get; init; }
/// <summary>
/// Key source type: "file", "hsm", "kms", "csp", "pkcs11"
/// </summary>
public required string Source { get; init; }
/// <summary>
/// Additional parameters (e.g., HSM PIN, KMS region, CSP container)
/// </summary>
public IReadOnlyDictionary<string, string>? Parameters { get; init; }
}
```
### CryptoKeyInfo
Metadata about an available key:
```csharp
namespace StellaOps.Cli.Crypto;
/// <summary>
/// Information about an available cryptographic key
/// </summary>
public sealed record CryptoKeyInfo
{
public required string KeyId { get; init; }
public required string Algorithm { get; init; }
public required string Source { get; init; }
public string? FriendlyName { get; init; }
public DateTimeOffset? ExpiresAt { get; init; }
public bool CanSign { get; init; }
public bool CanVerify { get; init; }
}
```
---
## Implementation Guide
### Step 1: Create Plugin Project
```bash
# Create new library project
dotnet new classlib -n StellaOps.Cli.Crypto.MyProvider
cd StellaOps.Cli.Crypto.MyProvider
# Add reference to interface project
dotnet add reference ../StellaOps.Cli.Crypto/StellaOps.Cli.Crypto.csproj
# Add required packages
dotnet add package Microsoft.Extensions.Options
dotnet add package Microsoft.Extensions.Logging
```
**Project structure:**
```
StellaOps.Cli.Crypto.MyProvider/
├── MyProviderCryptoProvider.cs # ICryptoProvider implementation
├── MyProviderOptions.cs # Configuration options
├── ServiceCollectionExtensions.cs # DI registration
├── Adapters/
│ ├── LibraryAdapter.cs # Native library adapter
│ └── RemoteSignerAdapter.cs # Remote signer client
└── StellaOps.Cli.Crypto.MyProvider.csproj
```
---
### Step 2: Implement ICryptoProvider
```csharp
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cli.Crypto;
namespace StellaOps.Cli.Crypto.MyProvider;
/// <summary>
/// Crypto provider for MyProvider algorithm
/// </summary>
public class MyProviderCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
{
private readonly MyProviderOptions _options;
private readonly ILogger<MyProviderCryptoProvider> _logger;
public MyProviderCryptoProvider(
IOptions<MyProviderOptions> options,
ILogger<MyProviderCryptoProvider> logger)
{
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger.LogInformation("MyProvider crypto provider initialized");
}
public string Name => "myprovider";
public string[] SupportedAlgorithms => new[]
{
"MYPROVIDER-ALG1",
"MYPROVIDER-ALG2"
};
public async Task<byte[]> SignAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(data);
ArgumentNullException.ThrowIfNull(algorithm);
ArgumentNullException.ThrowIfNull(keyRef);
_logger.LogDebug("Signing {DataLength} bytes with {Algorithm}", data.Length, algorithm);
if (!SupportedAlgorithms.Contains(algorithm))
{
throw new NotSupportedException($"Algorithm '{algorithm}' is not supported by this provider");
}
// Implementation: Call native library, HSM, or remote signer
// Example: Use native library
if (_options.UseNativeLibrary)
{
return await SignWithNativeLibraryAsync(data, algorithm, keyRef, cancellationToken);
}
// Example: Use remote signer
if (_options.UseRemoteSigner)
{
return await SignWithRemoteSignerAsync(data, algorithm, keyRef, cancellationToken);
}
throw new InvalidOperationException("No signing method configured");
}
public async Task<bool> VerifyAsync(
byte[] data,
byte[] signature,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(data);
ArgumentNullException.ThrowIfNull(signature);
ArgumentNullException.ThrowIfNull(algorithm);
ArgumentNullException.ThrowIfNull(keyRef);
_logger.LogDebug("Verifying signature for {DataLength} bytes with {Algorithm}", data.Length, algorithm);
if (!SupportedAlgorithms.Contains(algorithm))
{
throw new NotSupportedException($"Algorithm '{algorithm}' is not supported by this provider");
}
// Implementation: Verify signature
if (_options.UseNativeLibrary)
{
return await VerifyWithNativeLibraryAsync(data, signature, algorithm, keyRef, cancellationToken);
}
throw new InvalidOperationException("No verification method configured");
}
public async Task<IReadOnlyList<CryptoKeyInfo>> ListKeysAsync(
CancellationToken cancellationToken = default)
{
_logger.LogDebug("Listing available keys");
var keys = new List<CryptoKeyInfo>();
// Example: List keys from configuration
if (_options.Keys != null)
{
foreach (var keyConfig in _options.Keys)
{
keys.Add(new CryptoKeyInfo
{
KeyId = keyConfig.KeyId,
Algorithm = keyConfig.Algorithm,
Source = keyConfig.Source,
FriendlyName = keyConfig.FriendlyName,
CanSign = true,
CanVerify = true
});
}
}
return keys.AsReadOnly();
}
public async Task<ProviderHealthCheck> HealthCheckAsync(
CancellationToken cancellationToken = default)
{
var checks = new List<string>();
var isHealthy = true;
string? errorMessage = null;
try
{
// Check 1: Native library loaded
if (_options.UseNativeLibrary)
{
if (IsNativeLibraryLoaded())
{
checks.Add("✅ Native library loaded");
}
else
{
checks.Add("❌ Native library not loaded");
isHealthy = false;
errorMessage = "Native library not found or failed to load";
}
}
// Check 2: Remote signer reachable
if (_options.UseRemoteSigner)
{
if (await IsRemoteSignerReachableAsync(cancellationToken))
{
checks.Add("✅ Remote signer reachable");
}
else
{
checks.Add("❌ Remote signer unreachable");
isHealthy = false;
errorMessage = "Remote signer not reachable";
}
}
// Check 3: Keys accessible
var keyList = await ListKeysAsync(cancellationToken);
if (keyList.Count > 0)
{
checks.Add($"✅ {keyList.Count} keys accessible");
}
else
{
checks.Add("⚠️ No keys configured");
}
}
catch (Exception ex)
{
checks.Add($"❌ Health check failed: {ex.Message}");
isHealthy = false;
errorMessage = ex.Message;
}
return new ProviderHealthCheck
{
ProviderName = Name,
IsHealthy = isHealthy,
Checks = checks.ToArray(),
ErrorMessage = errorMessage
};
}
public ProviderInfo GetInfo()
{
return new ProviderInfo
{
Name = Name,
Version = "1.0.0",
Capabilities = new[] { "sign", "verify", "list-keys" },
SupportedAlgorithms = SupportedAlgorithms
};
}
// Private helper methods
private async Task<byte[]> SignWithNativeLibraryAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken)
{
// Example: Call native library via P/Invoke or wrapper
// This is a placeholder - actual implementation depends on your crypto library
throw new NotImplementedException("Native library signing not implemented");
}
private async Task<bool> VerifyWithNativeLibraryAsync(
byte[] data,
byte[] signature,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken)
{
throw new NotImplementedException("Native library verification not implemented");
}
private async Task<byte[]> SignWithRemoteSignerAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken)
{
// Example: Call remote signer API
throw new NotImplementedException("Remote signer not implemented");
}
private bool IsNativeLibraryLoaded()
{
// Check if native library is loaded
return true; // Placeholder
}
private async Task<bool> IsRemoteSignerReachableAsync(CancellationToken cancellationToken)
{
// Ping remote signer
return true; // Placeholder
}
}
```
---
### Step 3: Configuration Options
```csharp
namespace StellaOps.Cli.Crypto.MyProvider;
/// <summary>
/// Configuration options for MyProvider crypto provider
/// </summary>
public sealed class MyProviderOptions
{
/// <summary>
/// Use native library for crypto operations
/// </summary>
public bool UseNativeLibrary { get; set; } = true;
/// <summary>
/// Path to native library (if not in standard location)
/// </summary>
public string? NativeLibraryPath { get; set; }
/// <summary>
/// Use remote signer API
/// </summary>
public bool UseRemoteSigner { get; set; } = false;
/// <summary>
/// Remote signer API URL
/// </summary>
public string? RemoteSignerUrl { get; set; }
/// <summary>
/// Remote signer API key
/// </summary>
public string? RemoteSignerApiKey { get; set; }
/// <summary>
/// Configured keys
/// </summary>
public List<KeyConfiguration>? Keys { get; set; }
public sealed class KeyConfiguration
{
public required string KeyId { get; init; }
public required string Algorithm { get; init; }
public required string Source { get; init; }
public string? FriendlyName { get; init; }
public string? FilePath { get; init; }
public string? HsmSlot { get; init; }
}
}
```
---
### Step 4: DI Registration
```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace StellaOps.Cli.Crypto.MyProvider;
/// <summary>
/// Service collection extensions for MyProvider crypto provider
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Add MyProvider crypto providers to DI container
/// </summary>
public static IServiceCollection AddMyProviderCryptoProviders(
this IServiceCollection services,
IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
// Register provider as ICryptoProvider
services.AddSingleton<ICryptoProvider, MyProviderCryptoProvider>();
// Bind configuration
services.Configure<MyProviderOptions>(
configuration.GetSection("StellaOps:Crypto:Providers:MyProvider"));
return services;
}
}
```
---
### Step 5: Configuration Example
```yaml
# appsettings.myprovider.yaml
StellaOps:
Crypto:
Providers:
MyProvider:
UseNativeLibrary: true
NativeLibraryPath: "/usr/lib/libmyprovider.so"
UseRemoteSigner: false
RemoteSignerUrl: "https://signer.example.com/api/v1/sign"
RemoteSignerApiKey: "${MYPROVIDER_API_KEY}"
Keys:
- KeyId: "prod-key-2024"
Algorithm: "MYPROVIDER-ALG1"
Source: "file"
FilePath: "/etc/stellaops/keys/prod-key.pem"
FriendlyName: "Production Signing Key 2024"
- KeyId: "hsm-key-001"
Algorithm: "MYPROVIDER-ALG2"
Source: "hsm"
HsmSlot: "0"
FriendlyName: "HSM Key Slot 0"
```
---
### Step 6: Update CLI Project
#### Update StellaOps.Cli.csproj
```xml
<Project Sdk="Microsoft.NET.Sdk">
<!-- ... -->
<!-- MyProvider plugin (custom distribution) -->
<ItemGroup Condition="'$(StellaOpsEnableMyProvider)' == 'true'">
<ProjectReference Include="..\StellaOps.Cli.Crypto.MyProvider\StellaOps.Cli.Crypto.MyProvider.csproj" />
<DefineConstants>$(DefineConstants);STELLAOPS_ENABLE_MYPROVIDER</DefineConstants>
</ItemGroup>
</Project>
```
#### Update Program.cs
```csharp
using StellaOps.Cli.Crypto.Default;
#if STELLAOPS_ENABLE_GOST
using StellaOps.Cli.Crypto.Gost;
#endif
#if STELLAOPS_ENABLE_MYPROVIDER
using StellaOps.Cli.Crypto.MyProvider;
#endif
namespace StellaOps.Cli;
public class Program
{
public static async Task<int> Main(string[] args)
{
// ... configuration setup ...
// Register default crypto providers (always available)
services.AddDefaultCryptoProviders(configuration);
#if STELLAOPS_ENABLE_GOST
services.AddGostCryptoProviders(configuration);
#endif
#if STELLAOPS_ENABLE_MYPROVIDER
services.AddMyProviderCryptoProviders(configuration);
#endif
// ... rest of setup ...
}
}
```
---
## Testing
### Unit Tests
```csharp
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Cli.Crypto;
using StellaOps.Cli.Crypto.MyProvider;
using Xunit;
namespace StellaOps.Cli.Crypto.MyProvider.Tests;
public class MyProviderCryptoProviderTests
{
[Fact]
public void Name_ReturnsExpectedName()
{
var provider = CreateProvider();
Assert.Equal("myprovider", provider.Name);
}
[Fact]
public void SupportedAlgorithms_ContainsExpectedAlgorithms()
{
var provider = CreateProvider();
Assert.Contains("MYPROVIDER-ALG1", provider.SupportedAlgorithms);
Assert.Contains("MYPROVIDER-ALG2", provider.SupportedAlgorithms);
}
[Fact]
public async Task SignAsync_WithUnsupportedAlgorithm_ThrowsNotSupportedException()
{
var provider = CreateProvider();
var data = "test"u8.ToArray();
var keyRef = new CryptoKeyReference { KeyId = "test-key", Source = "file" };
await Assert.ThrowsAsync<NotSupportedException>(async () =>
{
await provider.SignAsync(data, "UNSUPPORTED-ALG", keyRef);
});
}
[Fact]
public async Task ListKeysAsync_ReturnsConfiguredKeys()
{
var options = new MyProviderOptions
{
Keys = new List<MyProviderOptions.KeyConfiguration>
{
new()
{
KeyId = "key1",
Algorithm = "MYPROVIDER-ALG1",
Source = "file",
FriendlyName = "Test Key 1"
}
}
};
var provider = new MyProviderCryptoProvider(
Options.Create(options),
NullLogger<MyProviderCryptoProvider>.Instance);
var keys = await provider.ListKeysAsync();
Assert.Single(keys);
Assert.Equal("key1", keys[0].KeyId);
Assert.Equal("MYPROVIDER-ALG1", keys[0].Algorithm);
}
[Fact]
public async Task HealthCheckAsync_ReturnsHealthStatus()
{
var provider = CreateProvider();
var healthCheck = await provider.HealthCheckAsync();
Assert.NotNull(healthCheck);
Assert.Equal("myprovider", healthCheck.ProviderName);
}
[Fact]
public void GetInfo_ReturnsProviderInfo()
{
var provider = CreateProvider();
var info = provider.GetInfo();
Assert.Equal("myprovider", info.Name);
Assert.Equal("1.0.0", info.Version);
Assert.Contains("sign", info.Capabilities);
Assert.Contains("verify", info.Capabilities);
}
private static MyProviderCryptoProvider CreateProvider()
{
var options = Options.Create(new MyProviderOptions
{
UseNativeLibrary = true
});
return new MyProviderCryptoProvider(options, NullLogger<MyProviderCryptoProvider>.Instance);
}
}
```
### Integration Tests
```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Crypto;
using Xunit;
namespace StellaOps.Cli.Crypto.MyProvider.Tests;
public class MyProviderIntegrationTests
{
[Fact]
public void ServiceProvider_ResolvesMyProvider()
{
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder().Build();
services.AddLogging();
services.AddMyProviderCryptoProviders(configuration);
var serviceProvider = services.BuildServiceProvider();
var providers = serviceProvider.GetServices<ICryptoProvider>().ToList();
var myProvider = providers.FirstOrDefault(p => p.Name == "myprovider");
Assert.NotNull(myProvider);
}
[Fact]
public async Task EndToEnd_SignAndVerify()
{
// This test requires actual crypto library or mocking
// Example structure:
var provider = CreateProvider();
var data = "test data"u8.ToArray();
var keyRef = new CryptoKeyReference
{
KeyId = "test-key",
Source = "file",
Parameters = new Dictionary<string, string>
{
["FilePath"] = "/path/to/key.pem"
}
};
// Sign
var signature = await provider.SignAsync(data, "MYPROVIDER-ALG1", keyRef);
Assert.NotNull(signature);
Assert.NotEmpty(signature);
// Verify
var isValid = await provider.VerifyAsync(data, signature, "MYPROVIDER-ALG1", keyRef);
Assert.True(isValid);
}
private MyProviderCryptoProvider CreateProvider()
{
// Setup provider with test configuration
throw new NotImplementedException();
}
}
```
---
## Build and Distribution
### Build Plugin
```bash
# Build plugin
dotnet build src/Cli/StellaOps.Cli.Crypto.MyProvider
# Run tests
dotnet test src/Cli/StellaOps.Cli.Crypto.MyProvider.Tests
```
### Build CLI with Plugin
```bash
# Build CLI with MyProvider plugin
dotnet publish src/Cli/StellaOps.Cli \
--configuration Release \
--runtime linux-x64 \
-p:StellaOpsEnableMyProvider=true
```
### Verify Plugin Inclusion
```bash
# Check available providers
./stella crypto providers
# Expected output:
# Available Crypto Providers:
# - default (.NET Crypto, BouncyCastle)
# - myprovider (MYPROVIDER-ALG1, MYPROVIDER-ALG2)
```
---
## Best Practices
### 1. Error Handling
```csharp
public async Task<byte[]> SignAsync(...)
{
try
{
// Signing logic
}
catch (FileNotFoundException ex)
{
throw new CryptoException($"Key file not found: {keyRef.KeyId}", ex);
}
catch (UnauthorizedAccessException ex)
{
throw new CryptoException($"Access denied to key: {keyRef.KeyId}", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to sign data with {Algorithm}", algorithm);
throw;
}
}
```
### 2. Logging
```csharp
_logger.LogDebug("Signing {DataLength} bytes with {Algorithm}", data.Length, algorithm);
_logger.LogInformation("Successfully signed data with {Algorithm}", algorithm);
_logger.LogWarning("Key {KeyId} expires soon: {ExpiresAt}", keyRef.KeyId, expiresAt);
_logger.LogError(ex, "Failed to sign data with {Algorithm}", algorithm);
```
### 3. Configuration Validation
```csharp
public MyProviderCryptoProvider(IOptions<MyProviderOptions> options, ILogger<MyProviderCryptoProvider> logger)
{
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// Validate configuration
if (_options.UseNativeLibrary && string.IsNullOrEmpty(_options.NativeLibraryPath))
{
throw new InvalidOperationException("NativeLibraryPath must be set when UseNativeLibrary is true");
}
if (_options.UseRemoteSigner && string.IsNullOrEmpty(_options.RemoteSignerUrl))
{
throw new InvalidOperationException("RemoteSignerUrl must be set when UseRemoteSigner is true");
}
}
```
### 4. Thread Safety
```csharp
private readonly SemaphoreSlim _hsmLock = new(1, 1);
public async Task<byte[]> SignAsync(...)
{
// Protect HSM access with semaphore
await _hsmLock.WaitAsync(cancellationToken);
try
{
// HSM signing
}
finally
{
_hsmLock.Release();
}
}
```
---
## Advanced Topics
### HSM Integration
```csharp
// Example: PKCS#11 HSM integration
private async Task<byte[]> SignWithHsmAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken)
{
var hsmSlot = keyRef.Parameters?["HsmSlot"];
var pin = keyRef.Parameters?["Pin"];
// Initialize PKCS#11 library
using var pkcs11 = new Pkcs11(_options.Pkcs11LibraryPath, AppType.MultiThreaded);
// Get slot
var slot = pkcs11.GetSlotList(SlotsType.WithTokenPresent)[int.Parse(hsmSlot!)];
// Open session
using var session = slot.OpenSession(SessionType.ReadOnly);
// Login
session.Login(CKU.User, pin);
// Find private key
var template = new List<IObjectAttribute>
{
session.Factories.ObjectAttributeFactory.Create(CKA.Label, keyRef.KeyId)
};
var foundObjects = session.FindAllObjects(template);
// Sign
var mechanism = session.Factories.MechanismFactory.Create(CKM.ECDSA);
var signature = session.Sign(mechanism, foundObjects[0], data);
return signature;
}
```
### Remote Signer Integration
```csharp
private async Task<byte[]> SignWithRemoteSignerAsync(
byte[] data,
string algorithm,
CryptoKeyReference keyRef,
CancellationToken cancellationToken)
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(_options.RemoteSignerUrl!);
httpClient.DefaultRequestHeaders.Add("X-API-Key", _options.RemoteSignerApiKey);
var request = new
{
keyId = keyRef.KeyId,
algorithm = algorithm,
data = Convert.ToBase64String(data)
};
var response = await httpClient.PostAsJsonAsync("/api/v1/sign", request, cancellationToken);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<SignResponse>(cancellationToken);
return Convert.FromBase64String(result!.Signature);
}
private sealed record SignResponse(string Signature);
```
---
## See Also
- [CLI Architecture](architecture.md) - Plugin architecture overview
- [Command Reference](command-reference.md) - Crypto command usage
- [Compliance Guide](compliance-guide.md) - Regional crypto requirements
- [Distribution Matrix](distribution-matrix.md) - Build and distribution guide
- [Troubleshooting](troubleshooting.md) - Common plugin issues