up
This commit is contained in:
121
src/SmRemote/StellaOps.SmRemote.Service/Program.cs
Normal file
121
src/SmRemote/StellaOps.SmRemote.Service/Program.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
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<ICryptoProviderRegistry>(_ =>
|
||||
{
|
||||
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<string> 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;
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography.Plugin.SmSoft\\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography.DependencyInjection\\StellaOps.Cryptography.DependencyInjection.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user