using System.Diagnostics; using System.Text.Json.Serialization; var builder = WebApplication.CreateSlimBuilder(args); builder.Services.ConfigureHttpJsonOptions(opts => { opts.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); var app = builder.Build(); const string CsptestPath = "/opt/cprocsp/bin/amd64/csptest"; app.MapGet("/health", () => { if (!File.Exists(CsptestPath)) { return Results.Problem(statusCode: 500, detail: "csptest not found; ensure CryptoPro CSP is installed"); } return Results.Ok(new { status = "ok", csptest = CsptestPath }); }); app.MapGet("/license", () => { var result = RunProcess([CsptestPath, "-keyset", "-info"], allowFailure: true); return Results.Json(result); }); app.MapPost("/hash", async (HashRequest request) => { byte[] data; try { data = Convert.FromBase64String(request.DataBase64); } catch (FormatException) { return Results.BadRequest(new { error = "Invalid base64" }); } var inputPath = Path.GetTempFileName(); var outputPath = Path.GetTempFileName(); await File.WriteAllBytesAsync(inputPath, data); var result = RunProcess([CsptestPath, "-hash", "-alg", "GOST12_256", "-in", inputPath, "-out", outputPath], allowFailure: true); string? digestBase64 = null; if (File.Exists(outputPath)) { var digestBytes = await File.ReadAllBytesAsync(outputPath); digestBase64 = Convert.ToBase64String(digestBytes); } TryDelete(inputPath); TryDelete(outputPath); return Results.Json(new { result.ExitCode, result.Output, digest_b64 = digestBase64 }); }); app.MapPost("/keyset/init", (KeysetRequest request) => { var name = string.IsNullOrWhiteSpace(request.Name) ? "default" : request.Name!; var result = RunProcess([CsptestPath, "-keyset", "-newkeyset", "-container", name, "-keytype", "none"], allowFailure: true); return Results.Json(result); }); app.Run("http://0.0.0.0:8080"); static void TryDelete(string path) { try { File.Delete(path); } catch { /* ignore */ } } static ProcessResult RunProcess(string[] args, bool allowFailure = false) { try { var psi = new ProcessStartInfo { FileName = args[0], RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, ArgumentList = { } }; for (var i = 1; i < args.Length; i++) { psi.ArgumentList.Add(args[i]); } using var proc = Process.Start(psi)!; var output = proc.StandardOutput.ReadToEnd(); output += proc.StandardError.ReadToEnd(); proc.WaitForExit(); if (proc.ExitCode != 0 && !allowFailure) { throw new InvalidOperationException($"Command failed with exit {proc.ExitCode}: {output}"); } return new ProcessResult(proc.ExitCode, output); } catch (Exception ex) { if (!allowFailure) { throw; } return new ProcessResult(-1, ex.ToString()); } } sealed record HashRequest([property: JsonPropertyName("data_b64")] string DataBase64); sealed record KeysetRequest([property: JsonPropertyName("name")] string? Name); sealed record ProcessResult(int ExitCode, string Output);