up the blokcing tasks
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-11 02:32:18 +02:00
parent 92bc4d3a07
commit 49922dff5a
474 changed files with 76071 additions and 12411 deletions

View File

@@ -1,3 +1,4 @@
using StellaOps.Cryptography.Plugin.SimRemote;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
@@ -86,6 +87,61 @@ public static class CryptoServiceCollectionExtensions
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, EidasSoftCryptoProvider>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, KcmvpHashOnlyProvider>());
// Unified simulation provider (sim-crypto-service)
services.AddOptions<SimRemoteProviderOptions>()
.Configure<IConfiguration>((opts, config) =>
{
config?.GetSection("StellaOps:Crypto:Sim").Bind(opts);
})
.PostConfigure(opts =>
{
var simUrl = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_SIM_URL");
if (!string.IsNullOrWhiteSpace(simUrl))
{
opts.BaseAddress = simUrl;
}
});
services.AddHttpClient<SimRemoteHttpClient>((sp, httpClient) =>
{
var opts = sp.GetService<IOptions<SimRemoteProviderOptions>>()?.Value;
if (opts is not null && !string.IsNullOrWhiteSpace(opts.BaseAddress))
{
httpClient.BaseAddress = new Uri(opts.BaseAddress);
}
});
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SimRemoteProvider>());
services.PostConfigure<CryptoProviderRegistryOptions>(opts =>
{
var enableSimEnv = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_ENABLE_SIM");
var enableSim = string.Equals(enableSimEnv, "1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(enableSimEnv, "true", StringComparison.OrdinalIgnoreCase);
if (!enableSim)
{
return;
}
void AddIfMissing(IList<string> list, string provider)
{
if (!list.Contains(provider, StringComparer.OrdinalIgnoreCase))
{
list.Add(provider);
}
}
if (!string.IsNullOrWhiteSpace(opts.ActiveProfile) &&
opts.Profiles.TryGetValue(opts.ActiveProfile, out var profile))
{
AddIfMissing(profile.PreferredProviders, "sim.crypto.remote");
}
else
{
AddIfMissing(opts.PreferredProviders, "sim.crypto.remote");
}
});
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
{
var providers = sp.GetServices<ICryptoProvider>();

View File

@@ -14,6 +14,7 @@
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.SmRemote\StellaOps.Cryptography.Plugin.SmRemote.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.SimRemote\StellaOps.Cryptography.Plugin.SimRemote.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.PqSoft\StellaOps.Cryptography.Plugin.PqSoft.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.WineCsp\StellaOps.Cryptography.Plugin.WineCsp.csproj" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />

View File

@@ -0,0 +1,38 @@
using System.Net.Http.Json;
namespace StellaOps.Cryptography.Plugin.SimRemote;
public sealed class SimRemoteHttpClient
{
private readonly HttpClient client;
public SimRemoteHttpClient(HttpClient client)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<string> SignAsync(string algorithmId, byte[] data, CancellationToken cancellationToken)
{
var payload = new SignRequest(Convert.ToBase64String(data), algorithmId);
var response = await client.PostAsJsonAsync("/sign", payload, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<SignResponse>(cancellationToken: cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Empty response from simulation signer.");
return result.SignatureBase64;
}
public async Task<bool> VerifyAsync(string algorithmId, byte[] data, string signatureBase64, CancellationToken cancellationToken)
{
var payload = new VerifyRequest(Convert.ToBase64String(data), signatureBase64, algorithmId);
var response = await client.PostAsJsonAsync("/verify", payload, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<VerifyResponse>(cancellationToken: cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Empty response from simulation verifier.");
return result.Ok;
}
private sealed record SignRequest(string MessageBase64, string Algorithm);
private sealed record SignResponse(string SignatureBase64, string Algorithm);
private sealed record VerifyRequest(string MessageBase64, string SignatureBase64, string Algorithm);
private sealed record VerifyResponse(bool Ok, string Algorithm);
}

View File

@@ -0,0 +1,69 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.SimRemote;
public sealed class SimRemoteProvider : ICryptoProvider, ICryptoProviderDiagnostics
{
private readonly SimRemoteHttpClient client;
private readonly SimRemoteProviderOptions options;
private readonly ILogger<SimRemoteProvider>? logger;
public SimRemoteProvider(
SimRemoteHttpClient client,
IOptions<SimRemoteProviderOptions>? optionsAccessor = null,
ILogger<SimRemoteProvider>? logger = null)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
this.logger = logger;
this.options = optionsAccessor?.Value ?? new SimRemoteProviderOptions();
}
public string Name => "sim.crypto.remote";
public bool Supports(CryptoCapability capability, string algorithmId)
{
if (capability is not (CryptoCapability.Signing or CryptoCapability.Verification))
{
return false;
}
return options.Algorithms.Contains(algorithmId, StringComparer.OrdinalIgnoreCase);
}
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
ArgumentNullException.ThrowIfNull(keyReference);
if (!Supports(CryptoCapability.Signing, algorithmId))
{
throw new InvalidOperationException($"Algorithm '{algorithmId}' is not enabled for simulation.");
}
var keyId = string.IsNullOrWhiteSpace(keyReference.KeyId) ? options.RemoteKeyId : keyReference.KeyId;
logger?.LogDebug("Using simulation signer for {Algorithm} with key {KeyId}", algorithmId, keyId);
return new SimRemoteSigner(client, algorithmId, keyId);
}
public void UpsertSigningKey(CryptoSigningKey signingKey) => throw new NotSupportedException("Simulation provider uses remote keys.");
public bool RemoveSigningKey(string keyId) => false;
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
public IPasswordHasher GetPasswordHasher(string algorithmId)
=> throw new NotSupportedException("Simulation provider does not handle password hashing.");
public ICryptoHasher GetHasher(string algorithmId)
=> throw new NotSupportedException("Simulation provider does not handle hashing.");
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
{
foreach (var alg in options.Algorithms)
{
yield return new CryptoProviderKeyDescriptor(Name, options.RemoteKeyId, alg, new Dictionary<string, string?>
{
["simulation"] = "true",
["endpoint"] = options.BaseAddress
});
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.SimRemote;
public sealed class SimRemoteProviderOptions
{
public string BaseAddress { get; set; } = "http://localhost:8080";
/// <summary>
/// Provider/algorithm IDs this simulation should serve.
/// Examples: pq.sim, ru.magma.sim, ru.kuznyechik.sim, sm.sim, fips.sim, eidas.sim, kcmvp.sim.
/// </summary>
public IList<string> Algorithms { get; set; } = new List<string>
{
SignatureAlgorithms.Dilithium3,
SignatureAlgorithms.Falcon512,
"pq.sim",
SignatureAlgorithms.GostR3410_2012_256,
SignatureAlgorithms.GostR3410_2012_512,
"ru.magma.sim",
"ru.kuznyechik.sim",
SignatureAlgorithms.Sm2,
"sm.sim",
"sm2.sim",
SignatureAlgorithms.Es256,
SignatureAlgorithms.Es384,
SignatureAlgorithms.Es512,
"fips.sim",
"eidas.sim",
"kcmvp.sim",
"world.sim"
};
public string RemoteKeyId { get; set; } = "sim-key";
}

View File

@@ -0,0 +1,33 @@
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.SimRemote;
internal sealed class SimRemoteSigner : ICryptoSigner
{
private readonly SimRemoteHttpClient client;
public SimRemoteSigner(SimRemoteHttpClient client, string algorithmId, string keyId)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
AlgorithmId = algorithmId ?? throw new ArgumentNullException(nameof(algorithmId));
KeyId = keyId ?? throw new ArgumentNullException(nameof(keyId));
}
public string KeyId { get; }
public string AlgorithmId { get; }
public async ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
var sig = await client.SignAsync(AlgorithmId, data.ToArray(), cancellationToken).ConfigureAwait(false);
return Convert.FromBase64String(sig);
}
public async ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
var sigBase64 = Convert.ToBase64String(signature.ToArray());
return await client.VerifyAsync(AlgorithmId, data.ToArray(), sigBase64, cancellationToken).ConfigureAwait(false);
}
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
=> new() { Kid = KeyId, Alg = AlgorithmId, Kty = "oct" };
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,113 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Plugin.SimRemote;
using StellaOps.Cryptography.DependencyInjection;
using Xunit;
namespace StellaOps.Cryptography.Tests;
public class SimRemoteProviderTests
{
[Fact]
public void Supports_DefaultAlgorithms_CoversStandardIds()
{
var handler = new NoopHandler();
var client = new HttpClient(handler) { BaseAddress = new Uri("http://sim.test") };
var options = Options.Create(new SimRemoteProviderOptions());
var provider = new SimRemoteProvider(new SimRemoteHttpClient(client), options);
Assert.True(provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Sm2));
Assert.True(provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_256));
Assert.True(provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Dilithium3));
}
[Fact]
public async Task SignAndVerify_WithSimProvider_Succeeds()
{
// Arrange
using var services = new ServiceCollection();
services.AddLogging();
services.Configure<SimRemoteProviderOptions>(opts =>
{
opts.BaseAddress = "http://sim.test";
opts.Algorithms.Clear();
opts.Algorithms.Add("pq.sim");
opts.RemoteKeyId = "sim-key";
});
services.AddHttpClient<SimRemoteHttpClient>()
.ConfigurePrimaryHttpMessageHandler(() => new SimHandler());
services.AddSingleton<IOptions<SimRemoteProviderOptions>>(sp => Options.Create(sp.GetRequiredService<IOptions<SimRemoteProviderOptions>>().Value));
services.AddSingleton<SimRemoteProvider>();
using var providerScope = services.BuildServiceProvider();
var provider = providerScope.GetRequiredService<SimRemoteProvider>();
var signer = provider.GetSigner("pq.sim", new CryptoKeyReference("sim-key"));
var payload = Encoding.UTF8.GetBytes("hello-sim");
// Act
var signature = await signer.SignAsync(payload);
var ok = await signer.VerifyAsync(payload, signature);
// Assert
Assert.True(ok);
Assert.Equal("sim-key", signer.KeyId);
Assert.Equal("pq.sim", signer.AlgorithmId);
}
private sealed class SimHandler : HttpMessageHandler
{
private static readonly byte[] Key = Encoding.UTF8.GetBytes("sim-hmac-key");
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var path = request.RequestUri?.AbsolutePath ?? string.Empty;
if (path.Contains("/sign", StringComparison.OrdinalIgnoreCase))
{
var payload = await request.Content!.ReadFromJsonAsync<SignPayload>(cancellationToken: cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Missing sign payload");
var data = Convert.FromBase64String(payload.MessageBase64);
var sig = HMACSHA256.HashData(Key, data);
var response = new SignResponse(Convert.ToBase64String(sig), payload.Algorithm);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = JsonContent.Create(response)
};
}
if (path.Contains("/verify", StringComparison.OrdinalIgnoreCase))
{
var payload = await request.Content!.ReadFromJsonAsync<VerifyPayload>(cancellationToken: cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Missing verify payload");
var data = Convert.FromBase64String(payload.MessageBase64);
var expected = HMACSHA256.HashData(Key, data);
var actual = Convert.FromBase64String(payload.SignatureBase64);
var ok = CryptographicOperations.FixedTimeEquals(expected, actual);
var response = new VerifyResponse(ok, payload.Algorithm);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = JsonContent.Create(response)
};
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
private sealed record SignPayload(string MessageBase64, string Algorithm);
private sealed record VerifyPayload(string MessageBase64, string SignatureBase64, string Algorithm);
private sealed record SignResponse(string SignatureBase64, string Algorithm);
private sealed record VerifyResponse(bool Ok, string Algorithm);
}
private sealed class NoopHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
=> Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}

View File

@@ -18,5 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.PqSoft\StellaOps.Cryptography.Plugin.PqSoft.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.DependencyInjection\StellaOps.Cryptography.DependencyInjection.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.SimRemote\StellaOps.Cryptography.Plugin.SimRemote.csproj" />
</ItemGroup>
</Project>