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
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:
@@ -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>();
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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" };
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user