using System.Text.Json.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Linq; using StellaOps.Cryptography; using StellaOps.Cryptography.Plugin.SmSoft; using Microsoft.Extensions.Options; var builder = WebApplication.CreateBuilder(args); builder.Services.AddLogging(); // Minimal crypto registry: only SM2 soft provider, no remote/http probing builder.Services.AddSingleton(_ => { var smOpts = Options.Create(new StellaOps.Cryptography.Plugin.SmSoft.SmSoftProviderOptions { RequireEnvironmentGate = false }); var smProvider = new SmSoftCryptoProvider(smOpts); var providers = new ICryptoProvider[] { smProvider }; var preferred = new[] { "cn.sm.soft" }; return new CryptoProviderRegistry(providers, preferred); }); builder.Services.AddHttpContextAccessor(); builder.Services.AddEndpointsApiExplorer(); var app = builder.Build(); app.MapGet("/status", (ICryptoProviderRegistry registry) => { var algorithms = new[] { SignatureAlgorithms.Sm2 }; return Results.Ok(new SmStatusResponse(true, "cn.sm.soft", algorithms)); }); app.MapPost("/sign", async (SignRequest req, ICryptoProviderRegistry registry, CancellationToken ct) => { if (req is null || string.IsNullOrWhiteSpace(req.KeyId) || string.IsNullOrWhiteSpace(req.AlgorithmId) || string.IsNullOrWhiteSpace(req.PayloadBase64)) { return Results.BadRequest("missing fields"); } var provider = ResolveProvider(registry); EnsureKeySeeded(provider, req.KeyId); var resolution = registry.ResolveSigner(CryptoCapability.Signing, req.AlgorithmId, new CryptoKeyReference(req.KeyId, provider.Name), provider.Name); var signer = resolution.Signer; var payload = Convert.FromBase64String(req.PayloadBase64); var signature = await signer.SignAsync(payload, ct); return Results.Ok(new SignResponse(Convert.ToBase64String(signature))); }); app.MapPost("/verify", async (VerifyRequest req, ICryptoProviderRegistry registry, CancellationToken ct) => { if (req is null || string.IsNullOrWhiteSpace(req.KeyId) || string.IsNullOrWhiteSpace(req.AlgorithmId) || string.IsNullOrWhiteSpace(req.PayloadBase64) || string.IsNullOrWhiteSpace(req.Signature)) { return Results.BadRequest("missing fields"); } var provider = ResolveProvider(registry); EnsureKeySeeded(provider, req.KeyId); var resolution = registry.ResolveSigner(CryptoCapability.Signing, req.AlgorithmId, new CryptoKeyReference(req.KeyId, provider.Name), provider.Name); var signer = resolution.Signer; var payload = Convert.FromBase64String(req.PayloadBase64); var signature = Convert.FromBase64String(req.Signature); var ok = await signer.VerifyAsync(payload, signature, ct); return Results.Ok(new VerifyResponse(ok)); }); app.Run(); static ICryptoProvider ResolveProvider(ICryptoProviderRegistry registry) { if (registry.TryResolve("cn.sm.remote.http", out var remote) && remote is not null) { return remote; } if (registry.TryResolve("cn.sm.soft", out var soft) && soft is not null) { return soft; } return registry.ResolveOrThrow(CryptoCapability.Signing, SignatureAlgorithms.Sm2); } static void EnsureKeySeeded(ICryptoProvider provider, string keyId) { // The soft provider hides private material via GetSigningKeys(), so rely on diagnostics DescribeKeys() to detect presence. if (provider is ICryptoProviderDiagnostics diag && diag.DescribeKeys().Any(k => k.KeyId.Equals(keyId, StringComparison.OrdinalIgnoreCase))) return; var curve = Org.BouncyCastle.Asn1.GM.GMNamedCurves.GetByName("SM2P256V1"); var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); var generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator("EC"); generator.Init(new Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(domain, new Org.BouncyCastle.Security.SecureRandom())); var pair = generator.GenerateKeyPair(); var privateDer = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private).GetDerEncoded(); provider.UpsertSigningKey(new CryptoSigningKey( new CryptoKeyReference(keyId, provider.Name), SignatureAlgorithms.Sm2, privateDer, DateTimeOffset.UtcNow)); } public sealed record SmStatusResponse(bool Available, string Provider, IEnumerable Algorithms); public sealed record SignRequest([property: JsonPropertyName("keyId")] string KeyId, [property: JsonPropertyName("algorithmId")] string AlgorithmId, [property: JsonPropertyName("payloadBase64")] string PayloadBase64); public sealed record SignResponse([property: JsonPropertyName("signature")] string Signature); public sealed record VerifyRequest([property: JsonPropertyName("keyId")] string KeyId, [property: JsonPropertyName("algorithmId")] string AlgorithmId, [property: JsonPropertyName("payloadBase64")] string PayloadBase64, [property: JsonPropertyName("signature")] string Signature); public sealed record VerifyResponse([property: JsonPropertyName("valid")] bool Valid); public partial class Program;