CD/CD consolidation
This commit is contained in:
13
devops/services/crypto/sim-crypto-service/Dockerfile
Normal file
13
devops/services/crypto/sim-crypto-service/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
||||
WORKDIR /src
|
||||
COPY SimCryptoService.csproj .
|
||||
RUN dotnet restore
|
||||
COPY . .
|
||||
RUN dotnet publish -c Release -o /app/publish
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
EXPOSE 8080
|
||||
ENV ASPNETCORE_URLS=http://0.0.0.0:8080
|
||||
ENTRYPOINT ["dotnet", "SimCryptoService.dll"]
|
||||
128
devops/services/crypto/sim-crypto-service/Program.cs
Normal file
128
devops/services/crypto/sim-crypto-service/Program.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
|
||||
// Static key material for simulations (not for production use).
|
||||
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
var ecdsaPublic = ecdsa.ExportSubjectPublicKeyInfo();
|
||||
|
||||
byte[] Sign(string message, string algorithm)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(message);
|
||||
var lower = algorithm.Trim().ToLowerInvariant();
|
||||
var upper = algorithm.Trim().ToUpperInvariant();
|
||||
|
||||
if (lower is "pq.dilithium3" or "pq.falcon512" or "pq.sim" || upper is "DILITHIUM3" or "FALCON512")
|
||||
{
|
||||
return HMACSHA256.HashData(Encoding.UTF8.GetBytes("pq-sim-key"), data);
|
||||
}
|
||||
|
||||
if (lower is "ru.magma.sim" or "ru.kuznyechik.sim" || upper is "GOST12-256" or "GOST12-512")
|
||||
{
|
||||
return HMACSHA256.HashData(Encoding.UTF8.GetBytes("gost-sim-key"), data);
|
||||
}
|
||||
|
||||
if (lower is "sm.sim" or "sm2.sim" || upper is "SM2")
|
||||
{
|
||||
return HMACSHA256.HashData(Encoding.UTF8.GetBytes("sm-sim-key"), data);
|
||||
}
|
||||
|
||||
return ecdsa.SignData(data, HashAlgorithmName.SHA256);
|
||||
}
|
||||
|
||||
bool Verify(string message, string algorithm, byte[] signature)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(message);
|
||||
var lower = algorithm.Trim().ToLowerInvariant();
|
||||
var upper = algorithm.Trim().ToUpperInvariant();
|
||||
|
||||
if (lower is "pq.dilithium3" or "pq.falcon512" or "pq.sim" || upper is "DILITHIUM3" or "FALCON512")
|
||||
{
|
||||
return CryptographicOperations.FixedTimeEquals(HMACSHA256.HashData(Encoding.UTF8.GetBytes("pq-sim-key"), data), signature);
|
||||
}
|
||||
|
||||
if (lower is "ru.magma.sim" or "ru.kuznyechik.sim" || upper is "GOST12-256" or "GOST12-512")
|
||||
{
|
||||
return CryptographicOperations.FixedTimeEquals(HMACSHA256.HashData(Encoding.UTF8.GetBytes("gost-sim-key"), data), signature);
|
||||
}
|
||||
|
||||
if (lower is "sm.sim" or "sm2.sim" || upper is "SM2")
|
||||
{
|
||||
return CryptographicOperations.FixedTimeEquals(HMACSHA256.HashData(Encoding.UTF8.GetBytes("sm-sim-key"), data), signature);
|
||||
}
|
||||
|
||||
return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
|
||||
}
|
||||
|
||||
app.MapPost("/sign", (SignRequest request) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Algorithm) || string.IsNullOrWhiteSpace(request.Message))
|
||||
{
|
||||
return Results.BadRequest("Algorithm and message are required.");
|
||||
}
|
||||
|
||||
var sig = Sign(request.Message, request.Algorithm);
|
||||
return Results.Json(new SignResponse(Convert.ToBase64String(sig), request.Algorithm));
|
||||
});
|
||||
|
||||
app.MapPost("/verify", (VerifyRequest request) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Algorithm) || string.IsNullOrWhiteSpace(request.Message) || string.IsNullOrWhiteSpace(request.SignatureBase64))
|
||||
{
|
||||
return Results.BadRequest("Algorithm, message, and signature are required.");
|
||||
}
|
||||
|
||||
var sig = Convert.FromBase64String(request.SignatureBase64);
|
||||
var ok = Verify(request.Message, request.Algorithm, sig);
|
||||
return Results.Json(new VerifyResponse(ok, request.Algorithm));
|
||||
});
|
||||
|
||||
app.MapGet("/keys", () =>
|
||||
{
|
||||
return Results.Json(new KeysResponse(
|
||||
Convert.ToBase64String(ecdsaPublic),
|
||||
"nistp256",
|
||||
new[]
|
||||
{
|
||||
"pq.sim",
|
||||
"DILITHIUM3",
|
||||
"FALCON512",
|
||||
"ru.magma.sim",
|
||||
"ru.kuznyechik.sim",
|
||||
"GOST12-256",
|
||||
"GOST12-512",
|
||||
"sm.sim",
|
||||
"SM2",
|
||||
"fips.sim",
|
||||
"eidas.sim",
|
||||
"kcmvp.sim",
|
||||
"world.sim"
|
||||
}));
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
public record SignRequest(
|
||||
[property: JsonPropertyName("message")] string Message,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
public record SignResponse(
|
||||
[property: JsonPropertyName("signature_b64")] string SignatureBase64,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
public record VerifyRequest(
|
||||
[property: JsonPropertyName("message")] string Message,
|
||||
[property: JsonPropertyName("signature_b64")] string SignatureBase64,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
public record VerifyResponse(
|
||||
[property: JsonPropertyName("ok")] bool Ok,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
public record KeysResponse(
|
||||
[property: JsonPropertyName("public_key_b64")] string PublicKeyBase64,
|
||||
[property: JsonPropertyName("curve")] string Curve,
|
||||
[property: JsonPropertyName("simulated_providers")] IEnumerable<string> Providers);
|
||||
32
devops/services/crypto/sim-crypto-service/README.md
Normal file
32
devops/services/crypto/sim-crypto-service/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Sim Crypto Service · 2025-12-11
|
||||
|
||||
Minimal HTTP service to simulate sovereign crypto providers when licensed hardware or certified modules are unavailable.
|
||||
|
||||
## Endpoints
|
||||
- `POST /sign` — body: `{"message":"<string>","algorithm":"<id>"}`; returns `{"signature_b64":"...","algorithm":"<id>"}`.
|
||||
- `POST /verify` — body: `{"message":"<string>","algorithm":"<id>","signature_b64":"..."}`; returns `{"ok":true/false,"algorithm":"<id>"}`.
|
||||
- `GET /keys` — returns public key info for simulated providers.
|
||||
|
||||
## Supported simulated provider IDs
|
||||
- GOST: `GOST12-256`, `GOST12-512`, `ru.magma.sim`, `ru.kuznyechik.sim` — deterministic HMAC-SHA256.
|
||||
- SM: `SM2`, `sm.sim`, `sm2.sim` — deterministic HMAC-SHA256.
|
||||
- PQ: `DILITHIUM3`, `FALCON512`, `pq.sim` — deterministic HMAC-SHA256.
|
||||
- FIPS/eIDAS/KCMVP/world: `ES256`, `ES384`, `ES512`, `fips.sim`, `eidas.sim`, `kcmvp.sim`, `world.sim` — ECDSA P-256 with a static key.
|
||||
|
||||
## Build & run
|
||||
```bash
|
||||
dotnet run -c Release --project ops/crypto/sim-crypto-service/SimCryptoService.csproj
|
||||
# or
|
||||
docker build -t sim-crypto -f ops/crypto/sim-crypto-service/Dockerfile ops/crypto/sim-crypto-service
|
||||
docker run --rm -p 8080:8080 sim-crypto
|
||||
```
|
||||
|
||||
## Wiring
|
||||
- Set `STELLAOPS_CRYPTO_ENABLE_SIM=1` to append `sim.crypto.remote` to the registry preference order.
|
||||
- Point the provider at the service: `STELLAOPS_CRYPTO_SIM_URL=http://localhost:8080` (or bind `StellaOps:Crypto:Sim:BaseAddress` in config).
|
||||
- `SimRemoteProviderOptions.Algorithms` already includes the IDs above; extend if you need extra aliases.
|
||||
|
||||
## Notes
|
||||
- Replaces the legacy SM-only simulator; use this unified service for SM, PQ, GOST, and FIPS/eIDAS/KCMVP placeholders.
|
||||
- Deterministic HMAC for SM/PQ/GOST; static ECDSA key for the rest. Not for production use.
|
||||
- No licensed binaries are shipped; everything is BCL-only.
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<AssetTargetFallback></AssetTargetFallback>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
96
devops/services/crypto/sim-crypto-smoke/Program.cs
Normal file
96
devops/services/crypto/sim-crypto-smoke/Program.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
var baseUrl = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_SIM_URL") ?? "http://localhost:8080";
|
||||
var profile = (Environment.GetEnvironmentVariable("SIM_PROFILE") ?? "sm").ToLowerInvariant();
|
||||
var algList = Environment.GetEnvironmentVariable("SIM_ALGORITHMS")?
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
?? profile switch
|
||||
{
|
||||
"ru-free" or "ru-paid" or "gost" or "ru" => new[] { "GOST12-256", "ru.magma.sim", "ru.kuznyechik.sim" },
|
||||
"sm" or "cn" => new[] { "SM2" },
|
||||
"eidas" => new[] { "ES256" },
|
||||
"fips" => new[] { "ES256" },
|
||||
"kcmvp" => new[] { "ES256" },
|
||||
"pq" => new[] { "pq.sim", "DILITHIUM3", "FALCON512" },
|
||||
_ => new[] { "ES256", "SM2", "pq.sim" }
|
||||
};
|
||||
var message = Environment.GetEnvironmentVariable("SIM_MESSAGE") ?? "stellaops-sim-smoke";
|
||||
|
||||
using var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
|
||||
|
||||
static async Task<(bool Ok, string Error)> SignAndVerify(HttpClient client, string algorithm, string message, CancellationToken ct)
|
||||
{
|
||||
var signPayload = new SignRequest(message, algorithm);
|
||||
var signResponse = await client.PostAsJsonAsync("/sign", signPayload, ct).ConfigureAwait(false);
|
||||
if (!signResponse.IsSuccessStatusCode)
|
||||
{
|
||||
return (false, $"sign failed: {(int)signResponse.StatusCode} {signResponse.ReasonPhrase}");
|
||||
}
|
||||
|
||||
var signResult = await signResponse.Content.ReadFromJsonAsync<SignResponse>(cancellationToken: ct).ConfigureAwait(false);
|
||||
if (signResult is null || string.IsNullOrWhiteSpace(signResult.SignatureBase64))
|
||||
{
|
||||
return (false, "sign returned empty payload");
|
||||
}
|
||||
|
||||
var verifyPayload = new VerifyRequest(message, signResult.SignatureBase64, algorithm);
|
||||
var verifyResponse = await client.PostAsJsonAsync("/verify", verifyPayload, ct).ConfigureAwait(false);
|
||||
if (!verifyResponse.IsSuccessStatusCode)
|
||||
{
|
||||
return (false, $"verify failed: {(int)verifyResponse.StatusCode} {verifyResponse.ReasonPhrase}");
|
||||
}
|
||||
|
||||
var verifyResult = await verifyResponse.Content.ReadFromJsonAsync<VerifyResponse>(cancellationToken: ct).ConfigureAwait(false);
|
||||
if (verifyResult?.Ok is not true)
|
||||
{
|
||||
return (false, "verify returned false");
|
||||
}
|
||||
|
||||
return (true, "");
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
var failures = new List<string>();
|
||||
|
||||
foreach (var alg in algList)
|
||||
{
|
||||
var (ok, error) = await SignAndVerify(client, alg, message, cts.Token);
|
||||
if (!ok)
|
||||
{
|
||||
failures.Add($"{alg}: {error}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[ok] {alg} via {baseUrl}");
|
||||
}
|
||||
|
||||
if (failures.Count > 0)
|
||||
{
|
||||
Console.Error.WriteLine("Simulation smoke failed:");
|
||||
foreach (var f in failures)
|
||||
{
|
||||
Console.Error.WriteLine($" - {f}");
|
||||
}
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
Console.WriteLine("Simulation smoke passed.");
|
||||
|
||||
internal sealed record SignRequest(
|
||||
[property: JsonPropertyName("message")] string Message,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
internal sealed record SignResponse(
|
||||
[property: JsonPropertyName("signature_b64")] string SignatureBase64,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
internal sealed record VerifyRequest(
|
||||
[property: JsonPropertyName("message")] string Message,
|
||||
[property: JsonPropertyName("signature_b64")] string SignatureBase64,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
|
||||
internal sealed record VerifyResponse(
|
||||
[property: JsonPropertyName("ok")] bool Ok,
|
||||
[property: JsonPropertyName("algorithm")] string Algorithm);
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<AssetTargetFallback></AssetTargetFallback>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user