up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
cryptopro-linux-csp / build-and-test (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-09 09:38:09 +02:00
parent bc0762e97d
commit 108d1c64b3
193 changed files with 7265 additions and 13029 deletions

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<InvariantGlobalization>true</InvariantGlobalization>
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
</PropertyGroup>
</Project>

View File

@@ -1,31 +1,36 @@
# syntax=docker/dockerfile:1.7
FROM mcr.microsoft.com/dotnet/nightly/sdk:10.0 AS build
WORKDIR /src
COPY ops/cryptopro/linux-csp-service/CryptoProLinuxApi.csproj .
RUN dotnet restore CryptoProLinuxApi.csproj
COPY ops/cryptopro/linux-csp-service/ .
RUN dotnet publish CryptoProLinuxApi.csproj -c Release -r linux-x64 --self-contained true \
/p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false -o /app/publish
FROM ubuntu:22.04
ARG CRYPTOPRO_ACCEPT_EULA=0
ENV DEBIAN_FRONTEND=noninteractive \
CRYPTOPRO_ACCEPT_EULA=1 \
CRYPTOPRO_ACCEPT_EULA=${CRYPTOPRO_ACCEPT_EULA} \
CRYPTOPRO_MINIMAL=1
WORKDIR /app
# System deps
# System deps for CryptoPro installer
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip tar xz-utils && \
apt-get install -y --no-install-recommends tar xz-utils ca-certificates && \
rm -rf /var/lib/apt/lists/*
# Copy CryptoPro packages (provided in repo) and installer
# CryptoPro packages (provided in repo) and installer
COPY opt/cryptopro/downloads/*.tgz /opt/cryptopro/downloads/
COPY ops/cryptopro/install-linux-csp.sh /usr/local/bin/install-linux-csp.sh
RUN chmod +x /usr/local/bin/install-linux-csp.sh
# Install CryptoPro CSP
RUN /usr/local/bin/install-linux-csp.sh
# Install CryptoPro CSP (requires CRYPTOPRO_ACCEPT_EULA=1 at build/runtime)
RUN CRYPTOPRO_ACCEPT_EULA=${CRYPTOPRO_ACCEPT_EULA} /usr/local/bin/install-linux-csp.sh
# Python deps
COPY ops/cryptopro/linux-csp-service/requirements.txt /app/requirements.txt
RUN pip3 install --no-cache-dir -r /app/requirements.txt
# App
COPY ops/cryptopro/linux-csp-service/app.py /app/app.py
# Copy published .NET app
COPY --from=build /app/publish/ /app/
EXPOSE 8080
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
ENTRYPOINT ["/app/CryptoProLinuxApi"]

View File

@@ -0,0 +1,118 @@
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);

View File

@@ -1,6 +1,6 @@
# CryptoPro Linux CSP Service (experimental)
# CryptoPro Linux CSP Service (.NET minimal API)
Minimal FastAPI wrapper around the Linux CryptoPro CSP binaries to prove installation and expose simple operations.
Minimal HTTP wrapper around the Linux CryptoPro CSP binaries to prove installation and hash operations.
## Build
@@ -8,18 +8,26 @@ Minimal FastAPI wrapper around the Linux CryptoPro CSP binaries to prove install
docker build -t cryptopro-linux-csp -f ops/cryptopro/linux-csp-service/Dockerfile .
```
`CRYPTOPRO_ACCEPT_EULA` defaults to `0` (build will fail); set to `1` only if you hold a valid CryptoPro license and accept the vendor EULA:
```bash
docker build -t cryptopro-linux-csp \
--build-arg CRYPTOPRO_ACCEPT_EULA=1 \
-f ops/cryptopro/linux-csp-service/Dockerfile .
```
## Run
```bash
docker run --rm -p 8080:8080 cryptopro-linux-csp
docker run --rm -p 18080:8080 --name cryptopro-linux-csp-test cryptopro-linux-csp
```
Endpoints:
- `GET /health` — checks `csptest` presence.
- `GET /license` — runs `csptest -license`.
- `POST /hash` with `{ "data_b64": "<base64>" }`runs `csptest -hash -hash_alg gost12_256`.
- `GET /license` — runs `csptest -keyset -info` (reports errors if no keyset/token present).
- `POST /hash` with `{"data_b64":"<base64>"}`hashes using `csptest -hash -alg GOST12_256`.
- `POST /keyset/init` with optional `{"name":"<container>"}` — creates an empty keyset (`-keytype none`) to silence missing-container warnings.
## Notes
- Uses the provided CryptoPro `.tgz` bundles under `opt/cryptopro/downloads`. Ensure you have rights to these binaries; the image builds with `CRYPTOPRO_ACCEPT_EULA=1`.
- Default install is minimal (no browser/plugin). Set `CRYPTOPRO_INCLUDE_PLUGIN=1` if you need plugin packages.
- This is not a production service; intended for validation only.
Notes:
- Uses the provided CryptoPro `.tgz` bundles under `opt/cryptopro/downloads`. Do not set `CRYPTOPRO_ACCEPT_EULA=1` unless you are licensed to use these binaries.
- Minimal, headless install; browser/plugin packages are not included.

View File

@@ -1,57 +0,0 @@
import base64
import subprocess
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="CryptoPro Linux CSP Service", version="0.1.0")
CSPTEST = Path("/opt/cprocsp/bin/amd64/csptest")
def run_cmd(cmd: list[str], input_bytes: Optional[bytes] = None, allow_fail: bool = False) -> str:
try:
proc = subprocess.run(
cmd,
input=input_bytes,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
)
return proc.stdout.decode("utf-8", errors="replace")
except subprocess.CalledProcessError as exc:
output = exc.stdout.decode("utf-8", errors="replace") if exc.stdout else ""
if allow_fail:
return output
raise HTTPException(status_code=500, detail={"cmd": cmd, "output": output})
@app.get("/health")
def health():
if not CSPTEST.exists():
raise HTTPException(status_code=500, detail="csptest binary not found; ensure CryptoPro CSP is installed")
return {"status": "ok", "csptest": str(CSPTEST)}
@app.get("/license")
def license_info():
output = run_cmd([str(CSPTEST), "-keyset", "-info"], allow_fail=True)
return {"output": output}
class HashRequest(BaseModel):
data_b64: str
@app.post("/hash")
def hash_data(body: HashRequest):
try:
data = base64.b64decode(body.data_b64)
except Exception:
raise HTTPException(status_code=400, detail="Invalid base64")
cmd = [str(CSPTEST), "-hash", "-in", "-", "-hash_alg", "gost12_256"]
output = run_cmd(cmd, input_bytes=data)
return {"output": output}

View File

@@ -1,2 +0,0 @@
fastapi==0.111.0
uvicorn[standard]==0.30.1