release orchestrator v1 draft and build fixes

This commit is contained in:
master
2026-01-12 12:24:17 +02:00
parent f3de858c59
commit 9873f80830
1598 changed files with 240385 additions and 5944 deletions

View File

@@ -0,0 +1,464 @@
namespace StellaOps.Cryptography.Plugin.Hsm;
using System.Security.Cryptography;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Plugin.Abstractions.Context;
using StellaOps.Plugin.Abstractions.Health;
using StellaOps.Plugin.Abstractions.Lifecycle;
/// <summary>
/// Hardware Security Module (HSM) cryptography plugin.
/// Provides integration with PKCS#11 compliant HSMs for secure key storage and operations.
/// </summary>
public sealed class HsmPlugin : CryptoPluginBase
{
private HsmOptions? _options;
private IHsmClient? _hsmClient;
private bool _isConnected;
/// <inheritdoc />
public override PluginInfo Info => new(
Id: "com.stellaops.crypto.hsm",
Name: "HSM Cryptography Provider",
Version: "1.0.0",
Vendor: "Stella Ops",
Description: "Hardware Security Module integration via PKCS#11",
LicenseId: "AGPL-3.0-or-later");
/// <inheritdoc />
public override IReadOnlyList<string> SupportedAlgorithms => new[]
{
"HSM-RSA-SHA256",
"HSM-RSA-SHA384",
"HSM-RSA-SHA512",
"HSM-RSA-PSS-SHA256",
"HSM-ECDSA-P256",
"HSM-ECDSA-P384",
"HSM-AES-128-GCM",
"HSM-AES-256-GCM"
};
/// <inheritdoc />
protected override async Task InitializeCryptoServiceAsync(IPluginContext context, CancellationToken ct)
{
_options = context.Configuration.Bind<HsmOptions>() ?? new HsmOptions();
if (string.IsNullOrEmpty(_options.LibraryPath))
{
Context?.Logger.Warning("HSM provider initialized in simulation mode (no library configured)");
_hsmClient = new SimulatedHsmClient();
}
else
{
_hsmClient = new Pkcs11HsmClient(_options.LibraryPath, Context?.Logger);
}
await _hsmClient.ConnectAsync(
_options.SlotId,
_options.Pin,
ct);
_isConnected = true;
Context?.Logger.Info("HSM provider connected to slot {SlotId}", _options.SlotId);
}
/// <inheritdoc />
public override async Task<HealthCheckResult> HealthCheckAsync(CancellationToken ct)
{
if (!_isConnected || _hsmClient == null)
{
return HealthCheckResult.Unhealthy("HSM not connected");
}
try
{
var isHealthy = await _hsmClient.PingAsync(ct);
if (!isHealthy)
{
return HealthCheckResult.Degraded("HSM responding slowly");
}
return HealthCheckResult.Healthy().WithDetails(new Dictionary<string, object>
{
["slot"] = _options?.SlotId ?? 0,
["library"] = _options?.LibraryPath ?? "simulated"
});
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(ex);
}
}
/// <inheritdoc />
public override bool CanHandle(CryptoOperation operation, string algorithm)
{
return algorithm.StartsWith("HSM-", StringComparison.OrdinalIgnoreCase) &&
SupportedAlgorithms.Contains(algorithm, StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
public override async Task<byte[]> SignAsync(ReadOnlyMemory<byte> data, CryptoSignOptions options, CancellationToken ct)
{
EnsureActive();
EnsureConnected();
ct.ThrowIfCancellationRequested();
var keyId = options.KeyId;
var mechanism = GetSigningMechanism(options.Algorithm);
var signature = await _hsmClient!.SignAsync(keyId, data.ToArray(), mechanism, ct);
Context?.Logger.Debug("HSM signed {DataLength} bytes with key {KeyId}", data.Length, keyId);
return signature;
}
/// <inheritdoc />
public override async Task<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CryptoVerifyOptions options, CancellationToken ct)
{
EnsureActive();
EnsureConnected();
ct.ThrowIfCancellationRequested();
var keyId = options.KeyId;
var mechanism = GetSigningMechanism(options.Algorithm);
var isValid = await _hsmClient!.VerifyAsync(keyId, data.ToArray(), signature.ToArray(), mechanism, ct);
Context?.Logger.Debug("HSM verified signature with key {KeyId}: {IsValid}", keyId, isValid);
return isValid;
}
/// <inheritdoc />
public override async Task<byte[]> EncryptAsync(ReadOnlyMemory<byte> data, CryptoEncryptOptions options, CancellationToken ct)
{
EnsureActive();
EnsureConnected();
ct.ThrowIfCancellationRequested();
var keyId = options.KeyId;
var mechanism = GetEncryptionMechanism(options.Algorithm);
var encrypted = await _hsmClient!.EncryptAsync(keyId, data.ToArray(), mechanism, options.Iv, ct);
Context?.Logger.Debug("HSM encrypted {DataLength} bytes with key {KeyId}", data.Length, keyId);
return encrypted;
}
/// <inheritdoc />
public override async Task<byte[]> DecryptAsync(ReadOnlyMemory<byte> data, CryptoDecryptOptions options, CancellationToken ct)
{
EnsureActive();
EnsureConnected();
ct.ThrowIfCancellationRequested();
var keyId = options.KeyId;
var mechanism = GetEncryptionMechanism(options.Algorithm);
var decrypted = await _hsmClient!.DecryptAsync(keyId, data.ToArray(), mechanism, options.Iv, ct);
Context?.Logger.Debug("HSM decrypted {DataLength} bytes with key {KeyId}", data.Length, keyId);
return decrypted;
}
/// <inheritdoc />
public override Task<byte[]> HashAsync(ReadOnlyMemory<byte> data, string algorithm, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
// Hashing can be done locally - no need to use HSM
var hash = algorithm.ToUpperInvariant() switch
{
var a when a.Contains("512") => SHA512.HashData(data.Span),
var a when a.Contains("384") => SHA384.HashData(data.Span),
_ => SHA256.HashData(data.Span)
};
Context?.Logger.Debug("Computed hash of {DataLength} bytes", data.Length);
return Task.FromResult(hash);
}
/// <inheritdoc />
public override async ValueTask DisposeAsync()
{
if (_hsmClient != null)
{
await _hsmClient.DisconnectAsync(CancellationToken.None);
_hsmClient = null;
}
_isConnected = false;
State = PluginLifecycleState.Stopped;
}
private void EnsureConnected()
{
if (!_isConnected || _hsmClient == null)
{
throw new InvalidOperationException("HSM is not connected.");
}
}
private static HsmMechanism GetSigningMechanism(string algorithm)
{
return algorithm.ToUpperInvariant() switch
{
var a when a.Contains("PSS") && a.Contains("256") => HsmMechanism.RsaPssSha256,
var a when a.Contains("RSA") && a.Contains("512") => HsmMechanism.RsaSha512,
var a when a.Contains("RSA") && a.Contains("384") => HsmMechanism.RsaSha384,
var a when a.Contains("RSA") => HsmMechanism.RsaSha256,
var a when a.Contains("ECDSA") && a.Contains("384") => HsmMechanism.EcdsaP384,
var a when a.Contains("ECDSA") => HsmMechanism.EcdsaP256,
_ => throw new NotSupportedException($"Signing mechanism not supported: {algorithm}")
};
}
private static HsmMechanism GetEncryptionMechanism(string algorithm)
{
return algorithm.ToUpperInvariant() switch
{
var a when a.Contains("256-GCM") => HsmMechanism.Aes256Gcm,
var a when a.Contains("128-GCM") => HsmMechanism.Aes128Gcm,
_ => throw new NotSupportedException($"Encryption mechanism not supported: {algorithm}")
};
}
}
/// <summary>
/// HSM mechanism identifiers.
/// </summary>
public enum HsmMechanism
{
RsaSha256,
RsaSha384,
RsaSha512,
RsaPssSha256,
EcdsaP256,
EcdsaP384,
Aes128Gcm,
Aes256Gcm
}
/// <summary>
/// Interface for HSM client implementations.
/// </summary>
public interface IHsmClient
{
Task ConnectAsync(int slotId, string? pin, CancellationToken ct);
Task DisconnectAsync(CancellationToken ct);
Task<bool> PingAsync(CancellationToken ct);
Task<byte[]> SignAsync(string keyId, byte[] data, HsmMechanism mechanism, CancellationToken ct);
Task<bool> VerifyAsync(string keyId, byte[] data, byte[] signature, HsmMechanism mechanism, CancellationToken ct);
Task<byte[]> EncryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct);
Task<byte[]> DecryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct);
}
/// <summary>
/// Simulated HSM client for testing without actual HSM hardware.
/// </summary>
internal sealed class SimulatedHsmClient : IHsmClient
{
private readonly Dictionary<string, RSA> _rsaKeys = new();
private readonly Dictionary<string, byte[]> _aesKeys = new();
private bool _connected;
public Task ConnectAsync(int slotId, string? pin, CancellationToken ct)
{
_connected = true;
// Generate some default keys for simulation
_rsaKeys["default"] = RSA.Create(2048);
_aesKeys["default"] = RandomNumberGenerator.GetBytes(32);
return Task.CompletedTask;
}
public Task DisconnectAsync(CancellationToken ct)
{
foreach (var key in _rsaKeys.Values)
{
key.Dispose();
}
_rsaKeys.Clear();
_aesKeys.Clear();
_connected = false;
return Task.CompletedTask;
}
public Task<bool> PingAsync(CancellationToken ct)
{
return Task.FromResult(_connected);
}
public Task<byte[]> SignAsync(string keyId, byte[] data, HsmMechanism mechanism, CancellationToken ct)
{
if (!_rsaKeys.TryGetValue(keyId, out var rsa))
{
rsa = _rsaKeys["default"];
}
var (hashAlg, padding) = GetRsaParameters(mechanism);
var signature = rsa.SignData(data, hashAlg, padding);
return Task.FromResult(signature);
}
public Task<bool> VerifyAsync(string keyId, byte[] data, byte[] signature, HsmMechanism mechanism, CancellationToken ct)
{
if (!_rsaKeys.TryGetValue(keyId, out var rsa))
{
rsa = _rsaKeys["default"];
}
var (hashAlg, padding) = GetRsaParameters(mechanism);
var isValid = rsa.VerifyData(data, signature, hashAlg, padding);
return Task.FromResult(isValid);
}
public Task<byte[]> EncryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct)
{
if (!_aesKeys.TryGetValue(keyId, out var key))
{
key = _aesKeys["default"];
}
var keyToUse = mechanism == HsmMechanism.Aes128Gcm ? key.Take(16).ToArray() : key;
iv ??= RandomNumberGenerator.GetBytes(12);
var tag = new byte[16];
using var aesGcm = new AesGcm(keyToUse, 16);
var ciphertext = new byte[data.Length];
aesGcm.Encrypt(iv, data, ciphertext, tag);
var result = new byte[iv.Length + tag.Length + ciphertext.Length];
Array.Copy(iv, 0, result, 0, iv.Length);
Array.Copy(tag, 0, result, iv.Length, tag.Length);
Array.Copy(ciphertext, 0, result, iv.Length + tag.Length, ciphertext.Length);
return Task.FromResult(result);
}
public Task<byte[]> DecryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct)
{
if (!_aesKeys.TryGetValue(keyId, out var key))
{
key = _aesKeys["default"];
}
var keyToUse = mechanism == HsmMechanism.Aes128Gcm ? key.Take(16).ToArray() : key;
iv ??= data.Take(12).ToArray();
var tag = data.Skip(12).Take(16).ToArray();
var ciphertext = data.Skip(28).ToArray();
using var aesGcm = new AesGcm(keyToUse, 16);
var plaintext = new byte[ciphertext.Length];
aesGcm.Decrypt(iv, ciphertext, tag, plaintext);
return Task.FromResult(plaintext);
}
private static (HashAlgorithmName, RSASignaturePadding) GetRsaParameters(HsmMechanism mechanism)
{
return mechanism switch
{
HsmMechanism.RsaPssSha256 => (HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
HsmMechanism.RsaSha512 => (HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1),
HsmMechanism.RsaSha384 => (HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1),
_ => (HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
};
}
}
/// <summary>
/// PKCS#11 HSM client implementation stub.
/// In production, this would use a PKCS#11 library like PKCS11Interop.
/// </summary>
internal sealed class Pkcs11HsmClient : IHsmClient
{
private readonly string _libraryPath;
private readonly IPluginLogger? _logger;
public Pkcs11HsmClient(string libraryPath, IPluginLogger? logger)
{
_libraryPath = libraryPath;
_logger = logger;
}
public Task ConnectAsync(int slotId, string? pin, CancellationToken ct)
{
_logger?.Info("Connecting to HSM via PKCS#11 library: {LibraryPath}", _libraryPath);
// In production: Load PKCS#11 library, open session, login
throw new NotImplementedException(
"PKCS#11 implementation requires Net.Pkcs11Interop package. " +
"Use simulation mode for testing.");
}
public Task DisconnectAsync(CancellationToken ct)
{
throw new NotImplementedException();
}
public Task<bool> PingAsync(CancellationToken ct)
{
throw new NotImplementedException();
}
public Task<byte[]> SignAsync(string keyId, byte[] data, HsmMechanism mechanism, CancellationToken ct)
{
throw new NotImplementedException();
}
public Task<bool> VerifyAsync(string keyId, byte[] data, byte[] signature, HsmMechanism mechanism, CancellationToken ct)
{
throw new NotImplementedException();
}
public Task<byte[]> EncryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct)
{
throw new NotImplementedException();
}
public Task<byte[]> DecryptAsync(string keyId, byte[] data, HsmMechanism mechanism, byte[]? iv, CancellationToken ct)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Configuration options for HSM cryptography plugin.
/// </summary>
public sealed class HsmOptions
{
/// <summary>
/// Path to PKCS#11 library (.so/.dll).
/// Leave empty for simulation mode.
/// </summary>
public string? LibraryPath { get; init; }
/// <summary>
/// HSM slot identifier.
/// </summary>
public int SlotId { get; init; }
/// <summary>
/// PIN for HSM authentication.
/// </summary>
public string? Pin { get; init; }
/// <summary>
/// Token label for identifying the HSM.
/// </summary>
public string? TokenLabel { get; init; }
/// <summary>
/// Connection timeout in seconds.
/// </summary>
public int ConnectionTimeoutSeconds { get; init; } = 30;
/// <summary>
/// Whether to use read-only session (no key generation/modification).
/// </summary>
public bool ReadOnlySession { get; init; } = true;
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Cryptography.Plugin\StellaOps.Cryptography.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="plugin.yaml" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
plugin:
id: com.stellaops.crypto.hsm
name: HSM Cryptography Provider
version: 1.0.0
vendor: Stella Ops
description: Hardware Security Module integration via PKCS#11
license: AGPL-3.0-or-later
entryPoint: StellaOps.Cryptography.Plugin.Hsm.HsmPlugin
minPlatformVersion: 1.0.0
capabilities:
- type: crypto
id: hsm
algorithms:
- HSM-RSA-SHA256
- HSM-RSA-SHA384
- HSM-RSA-SHA512
- HSM-RSA-PSS-SHA256
- HSM-ECDSA-P256
- HSM-ECDSA-P384
- HSM-AES-128-GCM
- HSM-AES-256-GCM
configSchema:
type: object
properties:
libraryPath:
type: string
description: Path to PKCS#11 library (.so/.dll). Leave empty for simulation mode.
slotId:
type: integer
default: 0
description: HSM slot identifier
pin:
type: string
description: PIN for HSM authentication
tokenLabel:
type: string
description: Token label for identifying the HSM
connectionTimeoutSeconds:
type: integer
default: 30
description: Connection timeout in seconds
readOnlySession:
type: boolean
default: true
description: Use read-only session (no key generation/modification)
required: []