feat(eidas): Implement eIDAS Crypto Plugin with dependency injection and signing capabilities

- Added ServiceCollectionExtensions for eIDAS crypto providers.
- Implemented EidasCryptoProvider for handling eIDAS-compliant signatures.
- Created LocalEidasProvider for local signing using PKCS#12 keystores.
- Defined SignatureLevel and SignatureFormat enums for eIDAS compliance.
- Developed TrustServiceProviderClient for remote signing via TSP.
- Added configuration support for eIDAS options in the project file.
- Implemented unit tests for SM2 compliance and crypto operations.
- Introduced dependency injection extensions for SM software and remote plugins.
This commit is contained in:
master
2025-12-23 14:06:48 +02:00
parent ef933db0d8
commit 84d97fd22c
51 changed files with 4353 additions and 747 deletions

View File

@@ -2,6 +2,71 @@ using System.Text.Json.Serialization;
namespace StellaOps.EvidenceLocker.Api;
/// <summary>
/// Request for POST /api/v1/verdicts to store a verdict attestation.
/// </summary>
public sealed record StoreVerdictRequest
{
[JsonPropertyName("verdict_id")]
public required string VerdictId { get; init; }
[JsonPropertyName("tenant_id")]
public required string TenantId { get; init; }
[JsonPropertyName("policy_run_id")]
public required string PolicyRunId { get; init; }
[JsonPropertyName("policy_id")]
public required string PolicyId { get; init; }
[JsonPropertyName("policy_version")]
public required int PolicyVersion { get; init; }
[JsonPropertyName("finding_id")]
public required string FindingId { get; init; }
[JsonPropertyName("verdict_status")]
public required string VerdictStatus { get; init; }
[JsonPropertyName("verdict_severity")]
public required string VerdictSeverity { get; init; }
[JsonPropertyName("verdict_score")]
public required decimal VerdictScore { get; init; }
[JsonPropertyName("evaluated_at")]
public required DateTimeOffset EvaluatedAt { get; init; }
[JsonPropertyName("envelope")]
public required object Envelope { get; init; } // DSSE envelope as JSON object
[JsonPropertyName("predicate_digest")]
public required string PredicateDigest { get; init; }
[JsonPropertyName("determinism_hash")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? DeterminismHash { get; init; }
[JsonPropertyName("rekor_log_index")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public long? RekorLogIndex { get; init; }
}
/// <summary>
/// Response for POST /api/v1/verdicts.
/// </summary>
public sealed record StoreVerdictResponse
{
[JsonPropertyName("verdict_id")]
public required string VerdictId { get; init; }
[JsonPropertyName("created_at")]
public required DateTimeOffset CreatedAt { get; init; }
[JsonPropertyName("stored")]
public required bool Stored { get; init; }
}
/// <summary>
/// Response for GET /api/v1/verdicts/{verdictId}.
/// </summary>

View File

@@ -18,6 +18,14 @@ public static class VerdictEndpoints
.WithTags("Verdicts")
.WithOpenApi();
// POST /api/v1/verdicts
group.MapPost("/", StoreVerdictAsync)
.WithName("StoreVerdict")
.WithSummary("Store a verdict attestation")
.Produces<StoreVerdictResponse>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status500InternalServerError);
// GET /api/v1/verdicts/{verdictId}
group.MapGet("/{verdictId}", GetVerdictAsync)
.WithName("GetVerdict")
@@ -44,6 +52,75 @@ public static class VerdictEndpoints
.Produces(StatusCodes.Status500InternalServerError);
}
private static async Task<IResult> StoreVerdictAsync(
[FromBody] StoreVerdictRequest request,
[FromServices] IVerdictRepository repository,
[FromServices] ILogger<Program> logger,
CancellationToken cancellationToken)
{
try
{
logger.LogInformation("Storing verdict attestation {VerdictId}", request.VerdictId);
// Validate request
if (string.IsNullOrWhiteSpace(request.VerdictId))
{
return Results.BadRequest(new { error = "verdict_id is required" });
}
if (string.IsNullOrWhiteSpace(request.FindingId))
{
return Results.BadRequest(new { error = "finding_id is required" });
}
// Serialize envelope to JSON string
var envelopeJson = JsonSerializer.Serialize(request.Envelope);
// Create repository record
var record = new VerdictAttestationRecord
{
VerdictId = request.VerdictId,
TenantId = request.TenantId,
RunId = request.PolicyRunId,
PolicyId = request.PolicyId,
PolicyVersion = request.PolicyVersion,
FindingId = request.FindingId,
VerdictStatus = request.VerdictStatus,
VerdictSeverity = request.VerdictSeverity,
VerdictScore = request.VerdictScore,
EvaluatedAt = request.EvaluatedAt,
Envelope = envelopeJson,
PredicateDigest = request.PredicateDigest,
DeterminismHash = request.DeterminismHash,
RekorLogIndex = request.RekorLogIndex,
CreatedAt = DateTimeOffset.UtcNow
};
// Store in repository
var storedVerdictId = await repository.StoreVerdictAsync(record, cancellationToken);
logger.LogInformation("Successfully stored verdict attestation {VerdictId}", storedVerdictId);
var response = new StoreVerdictResponse
{
VerdictId = storedVerdictId,
CreatedAt = record.CreatedAt,
Stored = true
};
return Results.Created($"/api/v1/verdicts/{storedVerdictId}", response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error storing verdict attestation {VerdictId}", request.VerdictId);
return Results.Problem(
title: "Internal server error",
detail: "Failed to store verdict attestation",
statusCode: StatusCodes.Status500InternalServerError
);
}
}
private static async Task<IResult> GetVerdictAsync(
string verdictId,
[FromServices] IVerdictRepository repository,