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(opts => { opts.BaseAddress = "http://sim.test"; opts.Algorithms.Clear(); opts.Algorithms.Add("pq.sim"); opts.RemoteKeyId = "sim-key"; }); services.AddHttpClient() .ConfigurePrimaryHttpMessageHandler(() => new SimHandler()); services.AddSingleton>(sp => Options.Create(sp.GetRequiredService>().Value)); services.AddSingleton(); using var providerScope = services.BuildServiceProvider(); var provider = providerScope.GetRequiredService(); 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 SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var path = request.RequestUri?.AbsolutePath ?? string.Empty; if (path.Contains("/sign", StringComparison.OrdinalIgnoreCase)) { var payload = await request.Content!.ReadFromJsonAsync(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(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 SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); } }