save checkpoint: save features

This commit is contained in:
master
2026-02-12 10:27:23 +02:00
parent dca86e1248
commit 5bca406787
8837 changed files with 1796879 additions and 5294 deletions

View File

@@ -0,0 +1,277 @@
// SPDX-License-Identifier: BUSL-1.1
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Cryptography.Models;
using StellaOps.Cryptography.Plugin.Eidas;
using StellaOps.Cryptography.Plugin.Fips;
using StellaOps.Cryptography.Plugin.Gost;
using StellaOps.Cryptography.Plugin.Hsm;
using StellaOps.Cryptography.Plugin.Sm;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Plugin.Abstractions.Lifecycle;
using StellaOps.Plugin.Testing;
namespace StellaOps.Cryptography.Tests;
public sealed class CryptoProviderPluginBehaviorTests
{
[Fact]
public async Task FipsPlugin_RoundTripsSignVerifyAndEncryptDecrypt()
{
var plugin = new FipsPlugin();
var context = CreateContext();
var payload = "stella-fips-payload"u8.ToArray();
await plugin.InitializeAsync(context, CancellationToken.None);
Assert.Equal(PluginLifecycleState.Active, plugin.State);
Assert.True(plugin.CanHandle(CryptoOperation.Sign, "RSA-SHA256"));
Assert.False(plugin.CanHandle(CryptoOperation.Sign, "SM2-SM3"));
var signature = await plugin.SignAsync(payload, new CryptoSignOptions("RSA-SHA256", "fips-signing-key"), CancellationToken.None);
Assert.NotEmpty(signature);
var isValid = await plugin.VerifyAsync(
payload,
signature,
new CryptoVerifyOptions("RSA-SHA256", "fips-signing-key"),
CancellationToken.None);
Assert.True(isValid);
var isTamperedValid = await plugin.VerifyAsync(
"tampered-fips-payload"u8.ToArray(),
signature,
new CryptoVerifyOptions("RSA-SHA256", "fips-signing-key"),
CancellationToken.None);
Assert.False(isTamperedValid);
var encrypted = await plugin.EncryptAsync(
payload,
new CryptoEncryptOptions("AES-256-GCM", "fips-encryption-key"),
CancellationToken.None);
var decrypted = await plugin.DecryptAsync(
encrypted,
new CryptoDecryptOptions("AES-256-GCM", "fips-encryption-key"),
CancellationToken.None);
Assert.Equal(payload, decrypted);
await plugin.DisposeAsync();
Assert.Equal(PluginLifecycleState.Stopped, plugin.State);
}
[Fact]
public async Task GostPlugin_SignsAndVerifiesAndSupportsHashing()
{
var plugin = new GostPlugin();
var context = CreateContext();
var payload = "stella-gost-payload"u8.ToArray();
await plugin.InitializeAsync(context, CancellationToken.None);
Assert.Equal(PluginLifecycleState.Active, plugin.State);
Assert.True(plugin.CanHandle(CryptoOperation.Sign, "GOST-R34.10-2012-256"));
Assert.False(plugin.CanHandle(CryptoOperation.Sign, "RSA-SHA256"));
var signature = await plugin.SignAsync(payload, new CryptoSignOptions("GOST-R34.10-2012-256", "gost-signing-key"), CancellationToken.None);
Assert.NotEmpty(signature);
var isValid = await plugin.VerifyAsync(
payload,
signature,
new CryptoVerifyOptions("GOST-R34.10-2012-256", "gost-signing-key"),
CancellationToken.None);
Assert.True(isValid);
var hash = await plugin.HashAsync(payload, "GOST-R34.11-2012-256", CancellationToken.None);
Assert.Equal(32, hash.Length);
await plugin.DisposeAsync();
Assert.Equal(PluginLifecycleState.Stopped, plugin.State);
}
[Fact]
public async Task SmPlugin_RoundTripsSignVerifyAndEncryptDecrypt()
{
var plugin = new SmPlugin();
var context = CreateContext();
var payload = "stella-sm-payload"u8.ToArray();
await plugin.InitializeAsync(context, CancellationToken.None);
Assert.Equal(PluginLifecycleState.Active, plugin.State);
Assert.True(plugin.CanHandle(CryptoOperation.Sign, "SM2-SM3"));
Assert.False(plugin.CanHandle(CryptoOperation.Sign, "RSA-SHA256"));
var signature = await plugin.SignAsync(payload, new CryptoSignOptions("SM2-SM3", "sm-signing-key"), CancellationToken.None);
Assert.NotEmpty(signature);
var isValid = await plugin.VerifyAsync(
payload,
signature,
new CryptoVerifyOptions("SM2-SM3", "sm-signing-key"),
CancellationToken.None);
Assert.True(isValid);
var encrypted = await plugin.EncryptAsync(
payload,
new CryptoEncryptOptions("SM4-GCM", "sm-encryption-key"),
CancellationToken.None);
var decrypted = await plugin.DecryptAsync(
encrypted,
new CryptoDecryptOptions("SM4-GCM", "sm-encryption-key"),
CancellationToken.None);
Assert.Equal(payload, decrypted);
await plugin.DisposeAsync();
Assert.Equal(PluginLifecycleState.Stopped, plugin.State);
}
[Fact]
public async Task HsmPlugin_SimulationMode_RoundTripsAndReportsHealth()
{
var plugin = new HsmPlugin();
var context = CreateContext();
var payload = "stella-hsm-payload"u8.ToArray();
await plugin.InitializeAsync(context, CancellationToken.None);
Assert.Equal(PluginLifecycleState.Active, plugin.State);
Assert.True(plugin.CanHandle(CryptoOperation.Sign, "HSM-RSA-SHA256"));
Assert.False(plugin.CanHandle(CryptoOperation.Sign, "GOST-R34.10-2012-256"));
var signature = await plugin.SignAsync(payload, new CryptoSignOptions("HSM-RSA-SHA256", "hsm-signing-key"), CancellationToken.None);
Assert.NotEmpty(signature);
var isValid = await plugin.VerifyAsync(
payload,
signature,
new CryptoVerifyOptions("HSM-RSA-SHA256", "hsm-signing-key"),
CancellationToken.None);
Assert.True(isValid);
var encrypted = await plugin.EncryptAsync(
payload,
new CryptoEncryptOptions("HSM-AES-256-GCM", "hsm-encryption-key"),
CancellationToken.None);
var decrypted = await plugin.DecryptAsync(
encrypted,
new CryptoDecryptOptions("HSM-AES-256-GCM", "hsm-encryption-key"),
CancellationToken.None);
Assert.Equal(payload, decrypted);
var health = await plugin.HealthCheckAsync(CancellationToken.None);
Assert.Equal("Healthy", health.Status.ToString());
var unsupported = Assert.ThrowsAsync<NotSupportedException>(() =>
plugin.SignAsync(payload, new CryptoSignOptions("HSM-UNKNOWN", "hsm-signing-key"), CancellationToken.None));
await unsupported;
await plugin.DisposeAsync();
Assert.Equal(PluginLifecycleState.Stopped, plugin.State);
}
[Fact]
public async Task EidasPlugin_WithoutCertificate_FailsClosedForSigning()
{
var plugin = new EidasPlugin();
var context = CreateContext();
var payload = "stella-eidas-payload"u8.ToArray();
await plugin.InitializeAsync(context, CancellationToken.None);
Assert.Equal(PluginLifecycleState.Active, plugin.State);
Assert.True(plugin.CanHandle(CryptoOperation.Sign, "eIDAS-RSA-SHA256"));
Assert.False(plugin.CanHandle(CryptoOperation.Sign, "RSA-SHA256"));
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
plugin.SignAsync(payload, new CryptoSignOptions("eIDAS-RSA-SHA256", "eidas-signing-key"), CancellationToken.None));
Assert.Contains("No signing certificate configured", exception.Message, StringComparison.OrdinalIgnoreCase);
await plugin.DisposeAsync();
Assert.Equal(PluginLifecycleState.Stopped, plugin.State);
}
[Fact]
public async Task MultiProfileSigner_SignsWithAllConfiguredProfiles_UsingFixedTimeProvider()
{
var fixedNow = new DateTimeOffset(2026, 2, 11, 8, 0, 0, TimeSpan.Zero);
var signers = new IContentSigner[]
{
new TestContentSigner("signer-ed", SignatureProfile.EdDsa, "Ed25519"),
new TestContentSigner("signer-gost", SignatureProfile.Gost2012, "GOST3410-2012-256")
};
using var signer = new MultiProfileSigner(signers, NullLogger<MultiProfileSigner>.Instance, new FixedTimeProvider(fixedNow));
var result = await signer.SignAllAsync("stella-multiprofile-payload"u8.ToArray(), CancellationToken.None);
Assert.Equal(2, result.Signatures.Count);
Assert.Equal(fixedNow, result.SignedAt);
Assert.Contains(result.Signatures, s => s.Profile == SignatureProfile.EdDsa);
Assert.Contains(result.Signatures, s => s.Profile == SignatureProfile.Gost2012);
}
[Fact]
public async Task MultiProfileSigner_WhenAnySignerFails_PropagatesException()
{
var signers = new IContentSigner[]
{
new TestContentSigner("signer-ed", SignatureProfile.EdDsa, "Ed25519"),
new TestContentSigner("signer-fail", SignatureProfile.Eidas, "eIDAS-RSA-SHA256", shouldThrow: true)
};
using var signer = new MultiProfileSigner(signers, NullLogger<MultiProfileSigner>.Instance);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
signer.SignAllAsync("stella-multiprofile-fail"u8.ToArray(), CancellationToken.None));
}
private static TestPluginContext CreateContext()
{
var options = new PluginTestHostOptions
{
EnableLogging = false
};
return new TestPluginContext(options);
}
private sealed class FixedTimeProvider(DateTimeOffset fixedNow) : TimeProvider
{
public override DateTimeOffset GetUtcNow() => fixedNow;
}
private sealed class TestContentSigner(
string keyId,
SignatureProfile profile,
string algorithm,
bool shouldThrow = false) : IContentSigner
{
public string KeyId => keyId;
public SignatureProfile Profile => profile;
public string Algorithm => algorithm;
public Task<SignatureResult> SignAsync(ReadOnlyMemory<byte> payload, CancellationToken ct = default)
{
if (shouldThrow)
{
throw new InvalidOperationException($"Signing failed for {keyId}");
}
var signature = payload.ToArray();
Array.Reverse(signature);
return Task.FromResult(new SignatureResult
{
KeyId = keyId,
Profile = profile,
Algorithm = algorithm,
Signature = signature,
SignedAt = new DateTimeOffset(2026, 2, 11, 8, 0, 0, TimeSpan.Zero)
});
}
public byte[]? GetPublicKey() => [1, 2, 3, 4];
public void Dispose()
{
}
}
}

View File

@@ -21,8 +21,13 @@
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin\StellaOps.Cryptography.Plugin.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.Eidas\StellaOps.Cryptography.Plugin.Eidas.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.Fips\StellaOps.Cryptography.Plugin.Fips.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.Gost\StellaOps.Cryptography.Plugin.Gost.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.Hsm\StellaOps.Cryptography.Plugin.Hsm.csproj" />
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.Sm\StellaOps.Cryptography.Plugin.Sm.csproj" />
<ProjectReference Include="..\..\..\Plugin\StellaOps.Plugin.Testing\StellaOps.Plugin.Testing.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,3 +6,4 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| --- | --- | --- |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Cryptography/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| QA-CRYPTO-RECHECK-004 | DONE | Added deterministic plugin behavior tests (FIPS/GOST/SM/HSM/eIDAS fail-closed + MultiProfileSigner) to close checked-feature replay coverage gaps. |