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

@@ -0,0 +1,324 @@
namespace StellaOps.Concelier.ProofService;
using Microsoft.Extensions.Logging;
using StellaOps.Attestor.ProofChain.Generators;
using StellaOps.Attestor.ProofChain.Models;
using StellaOps.Concelier.SourceIntel;
using StellaOps.Feedser.BinaryAnalysis;
using StellaOps.Feedser.Core;
using System.Text.Json;
/// <summary>
/// Orchestrates four-tier backport detection and proof generation.
/// Queries all evidence tiers and produces cryptographic ProofBlobs.
/// </summary>
public sealed class BackportProofService
{
private readonly ILogger<BackportProofService> _logger;
private readonly IDistroAdvisoryRepository _advisoryRepo;
private readonly ISourceArtifactRepository _sourceRepo;
private readonly IPatchRepository _patchRepo;
private readonly BinaryFingerprintFactory _fingerprintFactory;
public BackportProofService(
ILogger<BackportProofService> logger,
IDistroAdvisoryRepository advisoryRepo,
ISourceArtifactRepository sourceRepo,
IPatchRepository patchRepo,
BinaryFingerprintFactory fingerprintFactory)
{
_logger = logger;
_advisoryRepo = advisoryRepo;
_sourceRepo = sourceRepo;
_patchRepo = patchRepo;
_fingerprintFactory = fingerprintFactory;
}
/// <summary>
/// Generate proof for a CVE + package combination using all available evidence.
/// </summary>
/// <param name="cveId">CVE identifier (e.g., CVE-2024-1234)</param>
/// <param name="packagePurl">Package URL (e.g., pkg:deb/debian/curl@7.64.0-4)</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>ProofBlob with aggregated evidence, or null if no evidence found</returns>
public async Task<ProofBlob?> GenerateProofAsync(
string cveId,
string packagePurl,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Generating proof for {CveId} in {Package}", cveId, packagePurl);
var evidences = new List<ProofEvidence>();
// Tier 1: Query distro advisories
var advisoryEvidence = await QueryDistroAdvisoriesAsync(cveId, packagePurl, cancellationToken);
if (advisoryEvidence != null)
{
evidences.Add(advisoryEvidence);
_logger.LogInformation("Found Tier 1 evidence (distro advisory) for {CveId}", cveId);
}
// Tier 2: Query changelog mentions
var changelogEvidences = await QueryChangelogsAsync(cveId, packagePurl, cancellationToken);
evidences.AddRange(changelogEvidences);
if (changelogEvidences.Count > 0)
{
_logger.LogInformation("Found {Count} Tier 2 evidence(s) (changelog) for {CveId}",
changelogEvidences.Count, cveId);
}
// Tier 3: Query patch headers and HunkSig
var patchEvidences = await QueryPatchesAsync(cveId, packagePurl, cancellationToken);
evidences.AddRange(patchEvidences);
if (patchEvidences.Count > 0)
{
_logger.LogInformation("Found {Count} Tier 3 evidence(s) (patches) for {CveId}",
patchEvidences.Count, cveId);
}
// Tier 4: Query binary fingerprints (if binary path available)
// Note: Binary fingerprinting requires actual binary, skipped if unavailable
var binaryPath = await ResolveBinaryPathAsync(packagePurl, cancellationToken);
if (binaryPath != null)
{
var binaryEvidences = await QueryBinaryFingerprintsAsync(cveId, binaryPath, cancellationToken);
evidences.AddRange(binaryEvidences);
if (binaryEvidences.Count > 0)
{
_logger.LogInformation("Found {Count} Tier 4 evidence(s) (binary) for {CveId}",
binaryEvidences.Count, cveId);
}
}
// If no evidence found, return unknown proof
if (evidences.Count == 0)
{
_logger.LogWarning("No evidence found for {CveId} in {Package}", cveId, packagePurl);
return BackportProofGenerator.Unknown(
cveId,
packagePurl,
"no_evidence_found",
Array.Empty<ProofEvidence>()
);
}
// Aggregate evidences into combined proof
var proof = BackportProofGenerator.CombineEvidence(cveId, packagePurl, evidences);
_logger.LogInformation(
"Generated proof {ProofId} for {CveId} with confidence {Confidence:P0} from {EvidenceCount} evidence(s)",
proof.ProofId, cveId, proof.Confidence, evidences.Count);
return proof;
}
/// <summary>
/// Generate proofs for multiple CVE + package combinations in batch.
/// </summary>
public async Task<IReadOnlyList<ProofBlob>> GenerateProofBatchAsync(
IEnumerable<(string CveId, string PackagePurl)> requests,
CancellationToken cancellationToken = default)
{
var tasks = requests.Select(req =>
GenerateProofAsync(req.CveId, req.PackagePurl, cancellationToken));
var results = await Task.WhenAll(tasks);
return results.Where(p => p != null).ToList()!;
}
private async Task<ProofEvidence?> QueryDistroAdvisoriesAsync(
string cveId,
string packagePurl,
CancellationToken cancellationToken)
{
var advisory = await _advisoryRepo.FindByCveAndPackageAsync(cveId, packagePurl, cancellationToken);
if (advisory == null) return null;
// Create evidence from advisory data
var advisoryData = JsonDocument.Parse(JsonSerializer.Serialize(advisory));
var dataHash = StellaOps.Canonical.Json.CanonJson.Sha256Prefixed(
StellaOps.Canonical.Json.CanonJson.Canonicalize(advisoryData));
return new ProofEvidence
{
EvidenceId = $"evidence:distro:{advisory.DistroName}:{advisory.AdvisoryId}",
Type = EvidenceType.DistroAdvisory,
Source = advisory.DistroName,
Timestamp = advisory.PublishedAt,
Data = advisoryData,
DataHash = dataHash
};
}
private async Task<List<ProofEvidence>> QueryChangelogsAsync(
string cveId,
string packagePurl,
CancellationToken cancellationToken)
{
var evidences = new List<ProofEvidence>();
var changelogs = await _sourceRepo.FindChangelogsByCveAsync(cveId, packagePurl, cancellationToken);
foreach (var changelog in changelogs)
{
var changelogData = JsonDocument.Parse(JsonSerializer.Serialize(changelog));
var dataHash = StellaOps.Canonical.Json.CanonJson.Sha256Prefixed(
StellaOps.Canonical.Json.CanonJson.Canonicalize(changelogData));
evidences.Add(new ProofEvidence
{
EvidenceId = $"evidence:changelog:{changelog.Format}:{changelog.Version}",
Type = EvidenceType.ChangelogMention,
Source = changelog.Format,
Timestamp = changelog.Date,
Data = changelogData,
DataHash = dataHash
});
}
return evidences;
}
private async Task<List<ProofEvidence>> QueryPatchesAsync(
string cveId,
string packagePurl,
CancellationToken cancellationToken)
{
var evidences = new List<ProofEvidence>();
// Query patch headers
var patchHeaders = await _patchRepo.FindPatchHeadersByCveAsync(cveId, cancellationToken);
foreach (var header in patchHeaders)
{
var headerData = JsonDocument.Parse(JsonSerializer.Serialize(header));
var dataHash = StellaOps.Canonical.Json.CanonJson.Sha256Prefixed(
StellaOps.Canonical.Json.CanonJson.Canonicalize(headerData));
evidences.Add(new ProofEvidence
{
EvidenceId = $"evidence:patch_header:{header.PatchFilePath}",
Type = EvidenceType.PatchHeader,
Source = header.Origin ?? "unknown",
Timestamp = header.ParsedAt,
Data = headerData,
DataHash = dataHash
});
}
// Query HunkSig matches
var patchSigs = await _patchRepo.FindPatchSignaturesByCveAsync(cveId, cancellationToken);
foreach (var sig in patchSigs)
{
var sigData = JsonDocument.Parse(JsonSerializer.Serialize(sig));
var dataHash = StellaOps.Canonical.Json.CanonJson.Sha256Prefixed(
StellaOps.Canonical.Json.CanonJson.Canonicalize(sigData));
evidences.Add(new ProofEvidence
{
EvidenceId = $"evidence:hunksig:{sig.CommitSha}",
Type = EvidenceType.PatchHeader, // Reuse PatchHeader type
Source = sig.UpstreamRepo,
Timestamp = sig.ExtractedAt,
Data = sigData,
DataHash = dataHash
});
}
return evidences;
}
private async Task<List<ProofEvidence>> QueryBinaryFingerprintsAsync(
string cveId,
string binaryPath,
CancellationToken cancellationToken)
{
var evidences = new List<ProofEvidence>();
// Query known fingerprints for this CVE
var knownFingerprints = await _patchRepo.FindBinaryFingerprintsByCveAsync(cveId, cancellationToken);
if (knownFingerprints.Count == 0) return evidences;
// Match candidate binary against known fingerprints
var matchResult = await _fingerprintFactory.MatchBestAsync(binaryPath, knownFingerprints, cancellationToken);
if (matchResult?.IsMatch == true)
{
var fingerprintData = JsonDocument.Parse(JsonSerializer.Serialize(matchResult));
var dataHash = StellaOps.Canonical.Json.CanonJson.Sha256Prefixed(
StellaOps.Canonical.Json.CanonJson.Canonicalize(fingerprintData));
evidences.Add(new ProofEvidence
{
EvidenceId = $"evidence:binary:{matchResult.Method}:{matchResult.MatchedFingerprintId}",
Type = EvidenceType.BinaryFingerprint,
Source = matchResult.Method.ToString(),
Timestamp = DateTimeOffset.UtcNow,
Data = fingerprintData,
DataHash = dataHash
});
}
return evidences;
}
private async Task<string?> ResolveBinaryPathAsync(string packagePurl, CancellationToken cancellationToken)
{
// Resolve PURL to actual binary path
// This would query package metadata or local package cache
// Simplified: return null if not available
await Task.CompletedTask;
return null;
}
}
// Repository interfaces (to be implemented by storage layer)
public interface IDistroAdvisoryRepository
{
Task<DistroAdvisoryDto?> FindByCveAndPackageAsync(string cveId, string packagePurl, CancellationToken ct);
}
public interface ISourceArtifactRepository
{
Task<IReadOnlyList<ChangelogDto>> FindChangelogsByCveAsync(string cveId, string packagePurl, CancellationToken ct);
}
public interface IPatchRepository
{
Task<IReadOnlyList<PatchHeaderDto>> FindPatchHeadersByCveAsync(string cveId, CancellationToken ct);
Task<IReadOnlyList<PatchSigDto>> FindPatchSignaturesByCveAsync(string cveId, CancellationToken ct);
Task<IReadOnlyList<StellaOps.Feedser.BinaryAnalysis.Models.BinaryFingerprint>> FindBinaryFingerprintsByCveAsync(string cveId, CancellationToken ct);
}
// DTOs for repository results
public sealed record DistroAdvisoryDto
{
public required string AdvisoryId { get; init; }
public required string DistroName { get; init; }
public required DateTimeOffset PublishedAt { get; init; }
public required string Status { get; init; }
}
public sealed record ChangelogDto
{
public required string Format { get; init; }
public required string Version { get; init; }
public required DateTimeOffset Date { get; init; }
public required IReadOnlyList<string> CveIds { get; init; }
}
public sealed record PatchHeaderDto
{
public required string PatchFilePath { get; init; }
public required string? Origin { get; init; }
public required DateTimeOffset ParsedAt { get; init; }
public required IReadOnlyList<string> CveIds { get; init; }
}
public sealed record PatchSigDto
{
public required string CommitSha { get; init; }
public required string UpstreamRepo { get; init; }
public required DateTimeOffset ExtractedAt { get; init; }
public required string HunkHash { get; init; }
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.SourceIntel\StellaOps.Concelier.SourceIntel.csproj" />
<ProjectReference Include="..\..\..\Feedser\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj" />
<ProjectReference Include="..\..\..\Feedser\StellaOps.Feedser.BinaryAnalysis\StellaOps.Feedser.BinaryAnalysis.csproj" />
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
</ItemGroup>
</Project>