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,431 @@
namespace StellaOps.Cryptography.Plugin.Fips;
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>
/// FIPS 140-2 compliant cryptography plugin.
/// Uses .NET's FIPS-validated cryptographic implementations.
/// </summary>
public sealed class FipsPlugin : CryptoPluginBase
{
private FipsOptions? _options;
private RSA? _rsaKey;
private ECDsa? _ecdsaKey;
/// <inheritdoc />
public override PluginInfo Info => new(
Id: "com.stellaops.crypto.fips",
Name: "FIPS 140-2 Cryptography Provider",
Version: "1.0.0",
Vendor: "Stella Ops",
Description: "US FIPS 140-2 compliant cryptographic algorithms",
LicenseId: "AGPL-3.0-or-later");
/// <inheritdoc />
public override IReadOnlyList<string> SupportedAlgorithms => new[]
{
"RSA-SHA256",
"RSA-SHA384",
"RSA-SHA512",
"RSA-PSS-SHA256",
"RSA-PSS-SHA384",
"RSA-PSS-SHA512",
"ECDSA-P256-SHA256",
"ECDSA-P384-SHA384",
"ECDSA-P521-SHA512",
"AES-128-CBC",
"AES-256-CBC",
"AES-128-GCM",
"AES-256-GCM",
"SHA-256",
"SHA-384",
"SHA-512",
"HMAC-SHA256",
"HMAC-SHA384",
"HMAC-SHA512"
};
/// <inheritdoc />
protected override Task InitializeCryptoServiceAsync(IPluginContext context, CancellationToken ct)
{
_options = context.Configuration.Bind<FipsOptions>() ?? new FipsOptions();
// Verify FIPS mode if required
if (_options.RequireFipsMode && !IsFipsModeEnabled())
{
throw new InvalidOperationException(
"FIPS mode is required but not enabled. Set CryptoConfig.AllowOnlyFipsAlgorithms = true.");
}
// Initialize RSA key
if (!string.IsNullOrEmpty(_options.RsaKeyXml))
{
_rsaKey = RSA.Create();
_rsaKey.FromXmlString(_options.RsaKeyXml);
Context?.Logger.Info("FIPS provider initialized with configured RSA key");
}
else if (_options.GenerateKeysOnInit)
{
_rsaKey = RSA.Create(_options.RsaKeySize);
Context?.Logger.Info("FIPS provider initialized with generated {KeySize}-bit RSA key", _options.RsaKeySize);
}
// Initialize ECDSA key
if (!string.IsNullOrEmpty(_options.EcdsaKeyXml))
{
_ecdsaKey = ECDsa.Create();
_ecdsaKey.FromXmlString(_options.EcdsaKeyXml);
}
else if (_options.GenerateKeysOnInit)
{
_ecdsaKey = ECDsa.Create(GetECCurve(_options.EcdsaCurve));
}
return Task.CompletedTask;
}
/// <inheritdoc />
public override bool CanHandle(CryptoOperation operation, string algorithm)
{
return SupportedAlgorithms.Contains(algorithm, StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
public override Task<byte[]> SignAsync(ReadOnlyMemory<byte> data, CryptoSignOptions options, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
var algorithm = options.Algorithm;
byte[] signature;
if (algorithm.StartsWith("ECDSA", StringComparison.OrdinalIgnoreCase))
{
if (_ecdsaKey == null)
{
throw new InvalidOperationException("No ECDSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
signature = _ecdsaKey.SignData(data.ToArray(), hashAlgorithm);
}
else if (algorithm.Contains("PSS", StringComparison.OrdinalIgnoreCase))
{
if (_rsaKey == null)
{
throw new InvalidOperationException("No RSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
signature = _rsaKey.SignData(data.ToArray(), hashAlgorithm, RSASignaturePadding.Pss);
}
else
{
if (_rsaKey == null)
{
throw new InvalidOperationException("No RSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
signature = _rsaKey.SignData(data.ToArray(), hashAlgorithm, RSASignaturePadding.Pkcs1);
}
Context?.Logger.Debug("Signed {DataLength} bytes with {Algorithm}", data.Length, algorithm);
return Task.FromResult(signature);
}
/// <inheritdoc />
public override Task<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CryptoVerifyOptions options, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
var algorithm = options.Algorithm;
bool isValid;
if (algorithm.StartsWith("ECDSA", StringComparison.OrdinalIgnoreCase))
{
if (_ecdsaKey == null)
{
throw new InvalidOperationException("No ECDSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
isValid = _ecdsaKey.VerifyData(data.ToArray(), signature.ToArray(), hashAlgorithm);
}
else if (algorithm.Contains("PSS", StringComparison.OrdinalIgnoreCase))
{
if (_rsaKey == null)
{
throw new InvalidOperationException("No RSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
isValid = _rsaKey.VerifyData(data.ToArray(), signature.ToArray(), hashAlgorithm, RSASignaturePadding.Pss);
}
else
{
if (_rsaKey == null)
{
throw new InvalidOperationException("No RSA key available.");
}
var hashAlgorithm = GetHashAlgorithmName(algorithm);
isValid = _rsaKey.VerifyData(data.ToArray(), signature.ToArray(), hashAlgorithm, RSASignaturePadding.Pkcs1);
}
Context?.Logger.Debug("Verified signature: {IsValid}", isValid);
return Task.FromResult(isValid);
}
/// <inheritdoc />
public override Task<byte[]> EncryptAsync(ReadOnlyMemory<byte> data, CryptoEncryptOptions options, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
var algorithm = options.Algorithm;
var keyBytes = GetSymmetricKey(options.KeyId, algorithm);
if (algorithm.Contains("GCM", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(AesGcmEncrypt(data.ToArray(), keyBytes, options.Iv, options.Aad));
}
else
{
return Task.FromResult(AesCbcEncrypt(data.ToArray(), keyBytes, options.Iv));
}
}
/// <inheritdoc />
public override Task<byte[]> DecryptAsync(ReadOnlyMemory<byte> data, CryptoDecryptOptions options, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
var algorithm = options.Algorithm;
var keyBytes = GetSymmetricKey(options.KeyId, algorithm);
if (algorithm.Contains("GCM", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(AesGcmDecrypt(data.ToArray(), keyBytes, options.Iv, options.Aad));
}
else
{
return Task.FromResult(AesCbcDecrypt(data.ToArray(), keyBytes, options.Iv));
}
}
/// <inheritdoc />
public override Task<byte[]> HashAsync(ReadOnlyMemory<byte> data, string algorithm, CancellationToken ct)
{
EnsureActive();
ct.ThrowIfCancellationRequested();
byte[] hash;
if (algorithm.Contains("HMAC", StringComparison.OrdinalIgnoreCase))
{
// For HMAC, we need a key - use a default for demonstration
var hmacKey = System.Text.Encoding.UTF8.GetBytes("default-hmac-key");
hash = algorithm.ToUpperInvariant() switch
{
var a when a.Contains("512") => HMACSHA512.HashData(hmacKey, data.Span),
var a when a.Contains("384") => HMACSHA384.HashData(hmacKey, data.Span),
_ => HMACSHA256.HashData(hmacKey, data.Span)
};
}
else
{
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 {Algorithm} hash of {DataLength} bytes", algorithm, data.Length);
return Task.FromResult(hash);
}
/// <inheritdoc />
public override ValueTask DisposeAsync()
{
_rsaKey?.Dispose();
_ecdsaKey?.Dispose();
_rsaKey = null;
_ecdsaKey = null;
State = PluginLifecycleState.Stopped;
return ValueTask.CompletedTask;
}
private static bool IsFipsModeEnabled()
{
// Check if FIPS mode is enabled at OS level
try
{
// This will throw if FIPS mode is required but algorithm is not FIPS-compliant
using var _ = SHA256.Create();
return true;
}
catch (InvalidOperationException)
{
return false;
}
}
private static ECCurve GetECCurve(string curveName)
{
return curveName.ToUpperInvariant() switch
{
"P-521" or "SECP521R1" => ECCurve.NamedCurves.nistP521,
"P-384" or "SECP384R1" => ECCurve.NamedCurves.nistP384,
_ => ECCurve.NamedCurves.nistP256
};
}
private static HashAlgorithmName GetHashAlgorithmName(string algorithm)
{
return algorithm.ToUpperInvariant() switch
{
var a when a.Contains("512") => HashAlgorithmName.SHA512,
var a when a.Contains("384") => HashAlgorithmName.SHA384,
_ => HashAlgorithmName.SHA256
};
}
private byte[] GetSymmetricKey(string keyId, string algorithm)
{
// Determine key size from algorithm
var keySize = algorithm.Contains("256") ? 32 : 16;
// Derive key from key ID using SHA-256
var hash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(keyId));
return hash.Take(keySize).ToArray();
}
private static byte[] AesCbcEncrypt(byte[] data, byte[] key, byte[]? iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
iv ??= aes.IV;
aes.IV = iv;
using var encryptor = aes.CreateEncryptor();
var encrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
// Prepend IV to ciphertext
var result = new byte[iv.Length + encrypted.Length];
Array.Copy(iv, 0, result, 0, iv.Length);
Array.Copy(encrypted, 0, result, iv.Length, encrypted.Length);
return result;
}
private static byte[] AesCbcDecrypt(byte[] data, byte[] key, byte[]? iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
if (iv == null)
{
iv = data.Take(16).ToArray();
data = data.Skip(16).ToArray();
}
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(data, 0, data.Length);
}
private static byte[] AesGcmEncrypt(byte[] data, byte[] key, byte[]? iv, byte[]? aad)
{
iv ??= RandomNumberGenerator.GetBytes(12);
var tag = new byte[16];
using var aesGcm = new AesGcm(key, 16);
var ciphertext = new byte[data.Length];
aesGcm.Encrypt(iv, data, ciphertext, tag, aad);
// Format: IV (12) + Tag (16) + Ciphertext
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 result;
}
private static byte[] AesGcmDecrypt(byte[] data, byte[] key, byte[]? iv, byte[]? aad)
{
if (iv == null)
{
iv = data.Take(12).ToArray();
var tag = data.Skip(12).Take(16).ToArray();
var ciphertext = data.Skip(28).ToArray();
using var aesGcm = new AesGcm(key, 16);
var plaintext = new byte[ciphertext.Length];
aesGcm.Decrypt(iv, ciphertext, tag, plaintext, aad);
return plaintext;
}
else
{
var tag = data.Take(16).ToArray();
var ciphertext = data.Skip(16).ToArray();
using var aesGcm = new AesGcm(key, 16);
var plaintext = new byte[ciphertext.Length];
aesGcm.Decrypt(iv, ciphertext, tag, plaintext, aad);
return plaintext;
}
}
}
/// <summary>
/// Configuration options for FIPS cryptography plugin.
/// </summary>
public sealed class FipsOptions
{
/// <summary>
/// Require FIPS mode to be enabled at OS level.
/// </summary>
public bool RequireFipsMode { get; init; }
/// <summary>
/// RSA key in XML format.
/// </summary>
public string? RsaKeyXml { get; init; }
/// <summary>
/// RSA key size in bits (2048, 3072, 4096).
/// </summary>
public int RsaKeySize { get; init; } = 2048;
/// <summary>
/// ECDSA key in XML format.
/// </summary>
public string? EcdsaKeyXml { get; init; }
/// <summary>
/// ECDSA curve name (P-256, P-384, P-521).
/// </summary>
public string EcdsaCurve { get; init; } = "P-256";
/// <summary>
/// Generate keys on initialization if not configured.
/// </summary>
public bool GenerateKeysOnInit { 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,64 @@
plugin:
id: com.stellaops.crypto.fips
name: FIPS 140-2 Cryptography Provider
version: 1.0.0
vendor: Stella Ops
description: US FIPS 140-2 compliant cryptographic algorithms
license: AGPL-3.0-or-later
entryPoint: StellaOps.Cryptography.Plugin.Fips.FipsPlugin
minPlatformVersion: 1.0.0
capabilities:
- type: crypto
id: fips
algorithms:
- RSA-SHA256
- RSA-SHA384
- RSA-SHA512
- RSA-PSS-SHA256
- RSA-PSS-SHA384
- RSA-PSS-SHA512
- ECDSA-P256-SHA256
- ECDSA-P384-SHA384
- ECDSA-P521-SHA512
- AES-128-CBC
- AES-256-CBC
- AES-128-GCM
- AES-256-GCM
- SHA-256
- SHA-384
- SHA-512
- HMAC-SHA256
- HMAC-SHA384
- HMAC-SHA512
configSchema:
type: object
properties:
requireFipsMode:
type: boolean
default: false
description: Require FIPS mode to be enabled at OS level
rsaKeyXml:
type: string
description: RSA key in XML format
rsaKeySize:
type: integer
enum: [2048, 3072, 4096]
default: 2048
description: RSA key size in bits
ecdsaKeyXml:
type: string
description: ECDSA key in XML format
ecdsaCurve:
type: string
enum: [P-256, P-384, P-521]
default: P-256
description: ECDSA curve name
generateKeysOnInit:
type: boolean
default: true
description: Generate keys on initialization
required: []