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:
@@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Attestor;
|
||||
using StellaOps.Scanner.Core.Configuration;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Reachability.Models;
|
||||
using StellaOps.Attestor;
|
||||
using StellaOps.Signals.Storage;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Orchestration;
|
||||
@@ -42,7 +42,7 @@ public class PoEOrchestrator
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>List of generated PoE hashes</returns>
|
||||
public async Task<IReadOnlyList<PoEResult>> GeneratePoEArtifactsAsync(
|
||||
ScanContext context,
|
||||
PoEScanContext context,
|
||||
IReadOnlyList<VulnerabilityMatch> vulnerabilities,
|
||||
PoEConfiguration configuration,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -129,8 +129,8 @@ public class PoEOrchestrator
|
||||
/// Generate a single PoE artifact for a subgraph.
|
||||
/// </summary>
|
||||
private async Task<PoEResult> GenerateSinglePoEAsync(
|
||||
Subgraph subgraph,
|
||||
ScanContext context,
|
||||
PoESubgraph subgraph,
|
||||
PoEScanContext context,
|
||||
PoEConfiguration configuration,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -201,7 +201,7 @@ public class PoEOrchestrator
|
||||
);
|
||||
}
|
||||
|
||||
private string[] GenerateReproSteps(ScanContext context, Subgraph subgraph)
|
||||
private string[] GenerateReproSteps(PoEScanContext context, PoESubgraph subgraph)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scanner.Core.Configuration;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Reachability.Models;
|
||||
using StellaOps.Attestor;
|
||||
using StellaOps.Scanner.Worker.Orchestration;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Processing.PoE;
|
||||
@@ -138,7 +138,7 @@ public sealed class PoEGenerationStageExecutor : IScanStageExecutor
|
||||
}
|
||||
}
|
||||
|
||||
private ScanContext BuildScanContext(ScanJobContext context)
|
||||
private PoEScanContext BuildScanContext(ScanJobContext context)
|
||||
{
|
||||
// Extract scan metadata from job context
|
||||
var scanId = context.ScanId;
|
||||
@@ -169,7 +169,7 @@ public sealed class PoEGenerationStageExecutor : IScanStageExecutor
|
||||
// Get configuration path
|
||||
var configPath = "etc/scanner.yaml"; // Default
|
||||
|
||||
return new ScanContext(
|
||||
return new PoEScanContext(
|
||||
ScanId: scanId,
|
||||
GraphHash: graphHash ?? "blake3:unknown",
|
||||
BuildId: buildId ?? "gnu-build-id:unknown",
|
||||
|
||||
@@ -33,5 +33,7 @@
|
||||
<ProjectReference Include="../StellaOps.Scanner.Analyzers.Native/StellaOps.Scanner.Analyzers.Native.csproj" />
|
||||
<ProjectReference Include="../../Unknowns/__Libraries/StellaOps.Unknowns.Core/StellaOps.Unknowns.Core.csproj" />
|
||||
<ProjectReference Include="../../BinaryIndex/__Libraries/StellaOps.BinaryIndex.Core/StellaOps.BinaryIndex.Core.csproj" />
|
||||
<ProjectReference Include="../../Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/StellaOps.Attestor.Core.csproj" />
|
||||
<ProjectReference Include="../../Signals/StellaOps.Signals/StellaOps.Signals.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
namespace StellaOps.Scanner.ProofIntegration;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Attestor.ProofChain.Generators;
|
||||
using StellaOps.Attestor.ProofChain.Models;
|
||||
using StellaOps.Attestor.ProofChain.Statements;
|
||||
using StellaOps.Concelier.ProofService;
|
||||
|
||||
/// <summary>
|
||||
/// Generates VEX verdicts with cryptographic proof references.
|
||||
/// Integrates Scanner vulnerability detection with proof-driven backport detection.
|
||||
/// </summary>
|
||||
public sealed class ProofAwareVexGenerator
|
||||
{
|
||||
private readonly ILogger<ProofAwareVexGenerator> _logger;
|
||||
private readonly BackportProofService _proofService;
|
||||
|
||||
public ProofAwareVexGenerator(
|
||||
ILogger<ProofAwareVexGenerator> logger,
|
||||
BackportProofService proofService)
|
||||
{
|
||||
_logger = logger;
|
||||
_proofService = proofService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate VEX verdict with proof for a vulnerability finding.
|
||||
/// </summary>
|
||||
/// <param name="finding">Vulnerability finding from scanner</param>
|
||||
/// <param name="sbomEntryId">SBOM entry ID for the component</param>
|
||||
/// <param name="policyVersion">Policy version used for decisioning</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>VEX verdict statement with embedded proof reference</returns>
|
||||
public async Task<VexVerdictWithProof> GenerateVexWithProofAsync(
|
||||
VulnerabilityFinding finding,
|
||||
string sbomEntryId,
|
||||
string policyVersion,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Generating proof-carrying VEX verdict for {CveId} in {Package}",
|
||||
finding.CveId, finding.PackagePurl);
|
||||
|
||||
// Step 1: Generate cryptographic proof using four-tier detection
|
||||
var proof = await _proofService.GenerateProofAsync(
|
||||
finding.CveId,
|
||||
finding.PackagePurl,
|
||||
cancellationToken);
|
||||
|
||||
if (proof == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"No proof generated for {CveId} in {Package}, using fallback verdict",
|
||||
finding.CveId, finding.PackagePurl);
|
||||
|
||||
// Fallback: Generate VEX without proof
|
||||
return GenerateFallbackVex(finding, sbomEntryId, policyVersion);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Generated proof {ProofId} with confidence {Confidence:P0} for {CveId}",
|
||||
proof.ProofId, proof.Confidence, finding.CveId);
|
||||
|
||||
// Step 2: Generate VEX verdict with proof reference
|
||||
var reasoningId = GenerateReasoningId(finding, proof);
|
||||
var (statement, proofPayload) = VexProofIntegrator.GenerateWithProofMetadata(
|
||||
proof,
|
||||
sbomEntryId,
|
||||
policyVersion,
|
||||
reasoningId);
|
||||
|
||||
return new VexVerdictWithProof
|
||||
{
|
||||
Statement = statement,
|
||||
ProofPayload = proofPayload,
|
||||
Proof = proof,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate VEX verdicts for multiple findings in batch.
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<VexVerdictWithProof>> GenerateBatchVexWithProofAsync(
|
||||
IEnumerable<VulnerabilityFinding> findings,
|
||||
string policyVersion,
|
||||
Func<VulnerabilityFinding, string> sbomEntryIdResolver,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tasks = findings.Select(finding =>
|
||||
{
|
||||
var sbomEntryId = sbomEntryIdResolver(finding);
|
||||
return GenerateVexWithProofAsync(finding, sbomEntryId, policyVersion, cancellationToken);
|
||||
});
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve existing proof for a CVE + package combination.
|
||||
/// Useful for audit replay and verification.
|
||||
/// </summary>
|
||||
public async Task<ProofBlob?> RetrieveProofAsync(
|
||||
string cveId,
|
||||
string packagePurl,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _proofService.GenerateProofAsync(cveId, packagePurl, cancellationToken);
|
||||
}
|
||||
|
||||
private VexVerdictWithProof GenerateFallbackVex(
|
||||
VulnerabilityFinding finding,
|
||||
string sbomEntryId,
|
||||
string policyVersion)
|
||||
{
|
||||
// Generate basic VEX without proof
|
||||
// This is used when no evidence is available (e.g., newly disclosed CVE)
|
||||
|
||||
var unknownProof = BackportProofGenerator.Unknown(
|
||||
finding.CveId,
|
||||
finding.PackagePurl,
|
||||
"no_evidence_available",
|
||||
Array.Empty<ProofEvidence>());
|
||||
|
||||
var reasoningId = $"reasoning:{finding.CveId}:{finding.PackagePurl}";
|
||||
var (statement, proofPayload) = VexProofIntegrator.GenerateWithProofMetadata(
|
||||
unknownProof,
|
||||
sbomEntryId,
|
||||
policyVersion,
|
||||
reasoningId);
|
||||
|
||||
return new VexVerdictWithProof
|
||||
{
|
||||
Statement = statement,
|
||||
ProofPayload = proofPayload,
|
||||
Proof = unknownProof,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateReasoningId(VulnerabilityFinding finding, ProofBlob proof)
|
||||
{
|
||||
// Reasoning ID format: reasoning:{cve}:{method}:{snapshot}
|
||||
return $"reasoning:{finding.CveId}:{proof.Method}:{proof.SnapshotId}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vulnerability finding from scanner.
|
||||
/// </summary>
|
||||
public sealed record VulnerabilityFinding
|
||||
{
|
||||
public required string CveId { get; init; }
|
||||
public required string PackagePurl { get; init; }
|
||||
public required string PackageName { get; init; }
|
||||
public required string PackageVersion { get; init; }
|
||||
public required string Severity { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX verdict with associated proof.
|
||||
/// </summary>
|
||||
public sealed record VexVerdictWithProof
|
||||
{
|
||||
public required VexVerdictStatement Statement { get; init; }
|
||||
public required VexVerdictProofPayload ProofPayload { get; init; }
|
||||
public required ProofBlob Proof { get; init; }
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<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="..\..\..\Concelier\__Libraries\StellaOps.Concelier.ProofService\StellaOps.Concelier.ProofService.csproj" />
|
||||
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
|
||||
using StellaOps.Scanner.Reachability.Models;
|
||||
using StellaOps.Attestor;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability;
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface IReachabilityResolver
|
||||
/// <exception cref="SubgraphExtractionException">
|
||||
/// Thrown when resolution fails due to missing data, invalid graph, or configuration errors.
|
||||
/// </exception>
|
||||
Task<Subgraph?> ResolveAsync(
|
||||
Task<PoESubgraph?> ResolveAsync(
|
||||
ReachabilityResolutionRequest request,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
@@ -36,7 +36,7 @@ public interface IReachabilityResolver
|
||||
/// <returns>
|
||||
/// Dictionary mapping vuln_id to resolved subgraph (or null if unreachable).
|
||||
/// </returns>
|
||||
Task<IReadOnlyDictionary<string, Subgraph?>> ResolveBatchAsync(
|
||||
Task<IReadOnlyDictionary<string, PoESubgraph?>> ResolveBatchAsync(
|
||||
IReadOnlyList<ReachabilityResolutionRequest> requests,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a function identifier in a subgraph with module, symbol, address, and optional source location.
|
||||
/// </summary>
|
||||
/// <param name="ModuleHash">SHA-256 hash of the module/library containing this function</param>
|
||||
/// <param name="Symbol">Human-readable symbol name (e.g., "main()", "Foo.bar()")</param>
|
||||
/// <param name="Addr">Hexadecimal address (e.g., "0x401000")</param>
|
||||
/// <param name="File">Optional source file path</param>
|
||||
/// <param name="Line">Optional source line number</param>
|
||||
[method: JsonConstructor]
|
||||
public record FunctionId(
|
||||
[property: JsonPropertyName("moduleHash")] string ModuleHash,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("addr")] string Addr,
|
||||
[property: JsonPropertyName("file")] string? File = null,
|
||||
[property: JsonPropertyName("line")] int? Line = null
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the canonical identifier for this function (symbol_id or code_id).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Id => Symbol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a call edge between two functions with optional guard predicates.
|
||||
/// </summary>
|
||||
/// <param name="Caller">Calling function identifier</param>
|
||||
/// <param name="Callee">Called function identifier</param>
|
||||
/// <param name="Guards">Guard predicates controlling this edge (e.g., ["feature:dark-mode", "platform:linux"])</param>
|
||||
/// <param name="Confidence">Confidence score for this edge [0.0, 1.0]</param>
|
||||
[method: JsonConstructor]
|
||||
public record Edge(
|
||||
[property: JsonPropertyName("from")] string Caller,
|
||||
[property: JsonPropertyName("to")] string Callee,
|
||||
[property: JsonPropertyName("guards")] string[] Guards,
|
||||
[property: JsonPropertyName("confidence")] double Confidence = 1.0
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a minimal subgraph showing call paths from entry points to vulnerable sinks.
|
||||
/// </summary>
|
||||
/// <param name="BuildId">Deterministic build identifier (e.g., "gnu-build-id:5f0c7c3c...")</param>
|
||||
/// <param name="ComponentRef">PURL package reference (e.g., "pkg:maven/log4j@2.14.1")</param>
|
||||
/// <param name="VulnId">CVE identifier (e.g., "CVE-2021-44228")</param>
|
||||
/// <param name="Nodes">Function nodes in the subgraph</param>
|
||||
/// <param name="Edges">Call edges in the subgraph</param>
|
||||
/// <param name="EntryRefs">Entry point node IDs (where execution begins)</param>
|
||||
/// <param name="SinkRefs">Vulnerable sink node IDs (CVE-affected functions)</param>
|
||||
/// <param name="PolicyDigest">SHA-256 hash of policy version used during extraction</param>
|
||||
/// <param name="ToolchainDigest">SHA-256 hash of scanner version/toolchain</param>
|
||||
[method: JsonConstructor]
|
||||
public record Subgraph(
|
||||
[property: JsonPropertyName("buildId")] string BuildId,
|
||||
[property: JsonPropertyName("componentRef")] string ComponentRef,
|
||||
[property: JsonPropertyName("vulnId")] string VulnId,
|
||||
[property: JsonPropertyName("nodes")] IReadOnlyList<FunctionId> Nodes,
|
||||
[property: JsonPropertyName("edges")] IReadOnlyList<Edge> Edges,
|
||||
[property: JsonPropertyName("entryRefs")] string[] EntryRefs,
|
||||
[property: JsonPropertyName("sinkRefs")] string[] SinkRefs,
|
||||
[property: JsonPropertyName("policyDigest")] string PolicyDigest,
|
||||
[property: JsonPropertyName("toolchainDigest")] string ToolchainDigest
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Metadata for Proof of Exposure artifact generation.
|
||||
/// </summary>
|
||||
/// <param name="GeneratedAt">Timestamp when PoE was generated</param>
|
||||
/// <param name="AnalyzerName">Analyzer identifier (e.g., "stellaops-scanner")</param>
|
||||
/// <param name="AnalyzerVersion">Semantic version (e.g., "1.2.0")</param>
|
||||
/// <param name="ToolchainDigest">SHA-256 hash of analyzer binary/container</param>
|
||||
/// <param name="PolicyDigest">SHA-256 hash of policy document</param>
|
||||
/// <param name="ReproSteps">Minimal steps to reproduce this PoE</param>
|
||||
[method: JsonConstructor]
|
||||
public record ProofMetadata(
|
||||
[property: JsonPropertyName("generatedAt")] DateTime GeneratedAt,
|
||||
[property: JsonPropertyName("analyzer")] AnalyzerInfo Analyzer,
|
||||
[property: JsonPropertyName("policy")] PolicyInfo Policy,
|
||||
[property: JsonPropertyName("reproSteps")] string[] ReproSteps
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzer information for PoE provenance.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record AnalyzerInfo(
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("version")] string Version,
|
||||
[property: JsonPropertyName("toolchainDigest")] string ToolchainDigest
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Policy information for PoE provenance.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record PolicyInfo(
|
||||
[property: JsonPropertyName("policyId")] string PolicyId,
|
||||
[property: JsonPropertyName("policyDigest")] string PolicyDigest,
|
||||
[property: JsonPropertyName("evaluatedAt")] DateTime EvaluatedAt
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Complete Proof of Exposure artifact.
|
||||
/// </summary>
|
||||
/// <param name="Schema">Schema version (e.g., "stellaops.dev/poe@v1")</param>
|
||||
/// <param name="Subgraph">Minimal subgraph with call paths</param>
|
||||
/// <param name="Metadata">Provenance and reproduction metadata</param>
|
||||
/// <param name="GraphHash">Parent richgraph-v1 BLAKE3 hash</param>
|
||||
/// <param name="SbomRef">Optional reference to SBOM artifact</param>
|
||||
/// <param name="VexClaimUri">Optional reference to VEX claim</param>
|
||||
[method: JsonConstructor]
|
||||
public record ProofOfExposure(
|
||||
[property: JsonPropertyName("@type")] string Type,
|
||||
[property: JsonPropertyName("schema")] string Schema,
|
||||
[property: JsonPropertyName("subject")] SubjectInfo Subject,
|
||||
[property: JsonPropertyName("subgraph")] SubgraphData SubgraphData,
|
||||
[property: JsonPropertyName("metadata")] ProofMetadata Metadata,
|
||||
[property: JsonPropertyName("evidence")] EvidenceInfo Evidence
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Subject information identifying what this PoE is about.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record SubjectInfo(
|
||||
[property: JsonPropertyName("buildId")] string BuildId,
|
||||
[property: JsonPropertyName("componentRef")] string ComponentRef,
|
||||
[property: JsonPropertyName("vulnId")] string VulnId,
|
||||
[property: JsonPropertyName("imageDigest")] string? ImageDigest = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Subgraph data structure for PoE JSON.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record SubgraphData(
|
||||
[property: JsonPropertyName("nodes")] NodeData[] Nodes,
|
||||
[property: JsonPropertyName("edges")] EdgeData[] Edges,
|
||||
[property: JsonPropertyName("entryRefs")] string[] EntryRefs,
|
||||
[property: JsonPropertyName("sinkRefs")] string[] SinkRefs
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Node data for PoE JSON serialization.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record NodeData(
|
||||
[property: JsonPropertyName("id")] string Id,
|
||||
[property: JsonPropertyName("moduleHash")] string ModuleHash,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("addr")] string Addr,
|
||||
[property: JsonPropertyName("file")] string? File = null,
|
||||
[property: JsonPropertyName("line")] int? Line = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Edge data for PoE JSON serialization.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record EdgeData(
|
||||
[property: JsonPropertyName("from")] string From,
|
||||
[property: JsonPropertyName("to")] string To,
|
||||
[property: JsonPropertyName("guards")] string[]? Guards = null,
|
||||
[property: JsonPropertyName("confidence")] double Confidence = 1.0
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Evidence links to related artifacts.
|
||||
/// </summary>
|
||||
[method: JsonConstructor]
|
||||
public record EvidenceInfo(
|
||||
[property: JsonPropertyName("graphHash")] string GraphHash,
|
||||
[property: JsonPropertyName("sbomRef")] string? SbomRef = null,
|
||||
[property: JsonPropertyName("vexClaimUri")] string? VexClaimUri = null,
|
||||
[property: JsonPropertyName("runtimeFactsUri")] string? RuntimeFactsUri = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a matched vulnerability for PoE generation.
|
||||
/// </summary>
|
||||
/// <param name="VulnId">Vulnerability identifier (CVE, GHSA, etc.)</param>
|
||||
/// <param name="ComponentRef">Component package URL (PURL)</param>
|
||||
/// <param name="IsReachable">Whether the vulnerability is reachable from entry points</param>
|
||||
/// <param name="Severity">Vulnerability severity (Critical, High, Medium, Low, Info)</param>
|
||||
[method: JsonConstructor]
|
||||
public record VulnerabilityMatch(
|
||||
[property: JsonPropertyName("vulnId")] string VulnId,
|
||||
[property: JsonPropertyName("componentRef")] string ComponentRef,
|
||||
[property: JsonPropertyName("isReachable")] bool IsReachable,
|
||||
[property: JsonPropertyName("severity")] string Severity
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Scan context for PoE generation.
|
||||
/// </summary>
|
||||
/// <param name="ScanId">Unique scan identifier</param>
|
||||
/// <param name="GraphHash">BLAKE3 hash of the reachability graph</param>
|
||||
/// <param name="BuildId">GNU build ID or equivalent</param>
|
||||
/// <param name="ImageDigest">Container image digest</param>
|
||||
/// <param name="PolicyId">Policy identifier</param>
|
||||
/// <param name="PolicyDigest">Policy content digest</param>
|
||||
/// <param name="ScannerVersion">Scanner version</param>
|
||||
/// <param name="ConfigPath">Scanner configuration path</param>
|
||||
[method: JsonConstructor]
|
||||
public record ScanContext(
|
||||
[property: JsonPropertyName("scanId")] string ScanId,
|
||||
[property: JsonPropertyName("graphHash")] string GraphHash,
|
||||
[property: JsonPropertyName("buildId")] string BuildId,
|
||||
[property: JsonPropertyName("imageDigest")] string ImageDigest,
|
||||
[property: JsonPropertyName("policyId")] string PolicyId,
|
||||
[property: JsonPropertyName("policyDigest")] string PolicyDigest,
|
||||
[property: JsonPropertyName("scannerVersion")] string ScannerVersion,
|
||||
[property: JsonPropertyName("configPath")] string ConfigPath
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Result from PoE generation for a single vulnerability.
|
||||
/// </summary>
|
||||
/// <param name="VulnId">Vulnerability identifier</param>
|
||||
/// <param name="ComponentRef">Component package URL</param>
|
||||
/// <param name="PoEHash">Content hash of the PoE artifact</param>
|
||||
/// <param name="PoERef">CAS reference to the PoE artifact</param>
|
||||
/// <param name="IsSigned">Whether the PoE is cryptographically signed</param>
|
||||
/// <param name="PathCount">Number of paths in the subgraph</param>
|
||||
[method: JsonConstructor]
|
||||
public record PoEResult(
|
||||
[property: JsonPropertyName("vulnId")] string VulnId,
|
||||
[property: JsonPropertyName("componentRef")] string ComponentRef,
|
||||
[property: JsonPropertyName("poeHash")] string PoEHash,
|
||||
[property: JsonPropertyName("poeRef")] string? PoERef,
|
||||
[property: JsonPropertyName("isSigned")] bool IsSigned,
|
||||
[property: JsonPropertyName("pathCount")] int? PathCount = null
|
||||
);
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Reachability.Models;
|
||||
using StellaOps.Attestor;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<Subgraph?> ResolveAsync(
|
||||
public async Task<PoESubgraph?> ResolveAsync(
|
||||
ReachabilityResolutionRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -129,14 +129,14 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyDictionary<string, Subgraph?>> ResolveBatchAsync(
|
||||
public async Task<IReadOnlyDictionary<string, PoESubgraph?>> ResolveBatchAsync(
|
||||
IReadOnlyList<ReachabilityResolutionRequest> requests,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(requests);
|
||||
|
||||
if (requests.Count == 0)
|
||||
return new Dictionary<string, Subgraph?>();
|
||||
return new Dictionary<string, PoESubgraph?>();
|
||||
|
||||
// Verify all requests are for the same graph
|
||||
var graphHash = requests[0].GraphHash;
|
||||
@@ -151,7 +151,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
"Batch resolving {Count} subgraphs for graph {GraphHash}",
|
||||
requests.Count, graphHash);
|
||||
|
||||
var results = new ConcurrentDictionary<string, Subgraph?>();
|
||||
var results = new ConcurrentDictionary<string, PoESubgraph?>();
|
||||
|
||||
// Process requests in parallel (limit concurrency to avoid memory pressure)
|
||||
var parallelOptions = new ParallelOptions
|
||||
@@ -297,7 +297,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
/// <summary>
|
||||
/// Build subgraph from selected paths.
|
||||
/// </summary>
|
||||
private Subgraph BuildSubgraphFromPaths(
|
||||
private PoESubgraph BuildSubgraphFromPaths(
|
||||
List<CallPath> paths,
|
||||
string buildId,
|
||||
string componentRef,
|
||||
@@ -343,7 +343,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
Line: null
|
||||
)).ToList();
|
||||
|
||||
return new Subgraph(
|
||||
return new PoESubgraph(
|
||||
BuildId: buildId,
|
||||
ComponentRef: componentRef,
|
||||
VulnId: vulnId,
|
||||
@@ -359,7 +359,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
/// <summary>
|
||||
/// Normalize subgraph for deterministic ordering.
|
||||
/// </summary>
|
||||
private Subgraph NormalizeSubgraph(Subgraph subgraph)
|
||||
private PoESubgraph NormalizeSubgraph(PoESubgraph subgraph)
|
||||
{
|
||||
// Sort nodes by symbol
|
||||
var sortedNodes = subgraph.Nodes
|
||||
@@ -473,7 +473,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
/// <summary>
|
||||
/// Represents a call path from entry to sink.
|
||||
/// </summary>
|
||||
internal record CallPath(
|
||||
public record CallPath(
|
||||
string PathId,
|
||||
List<string> Nodes,
|
||||
List<Edge> Edges,
|
||||
|
||||
@@ -12,7 +12,7 @@ using StellaOps.Attestor;
|
||||
using StellaOps.Scanner.Core.Configuration;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Reachability.Models;
|
||||
using StellaOps.Attestor;
|
||||
using StellaOps.Scanner.Worker.Orchestration;
|
||||
using StellaOps.Scanner.Worker.Processing;
|
||||
using StellaOps.Scanner.Worker.Processing.PoE;
|
||||
@@ -115,7 +115,7 @@ public class PoEGenerationStageExecutorTests : IDisposable
|
||||
|
||||
_resolverMock
|
||||
.Setup(x => x.ResolveBatchAsync(It.IsAny<IReadOnlyList<ReachabilityResolutionRequest>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Dictionary<string, Subgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
.ReturnsAsync(new Dictionary<string, PoESubgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
|
||||
_emitterMock
|
||||
.Setup(x => x.EmitPoEAsync(It.IsAny<Subgraph>(), It.IsAny<ProofMetadata>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
@@ -169,7 +169,7 @@ public class PoEGenerationStageExecutorTests : IDisposable
|
||||
|
||||
_resolverMock
|
||||
.Setup(x => x.ResolveBatchAsync(It.Is<IReadOnlyList<ReachabilityResolutionRequest>>(r => r.Count == 1), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Dictionary<string, Subgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
.ReturnsAsync(new Dictionary<string, PoESubgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
|
||||
_emitterMock
|
||||
.Setup(x => x.EmitPoEAsync(It.IsAny<Subgraph>(), It.IsAny<ProofMetadata>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
@@ -219,7 +219,7 @@ public class PoEGenerationStageExecutorTests : IDisposable
|
||||
|
||||
_resolverMock
|
||||
.Setup(x => x.ResolveBatchAsync(It.IsAny<IReadOnlyList<ReachabilityResolutionRequest>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Dictionary<string, Subgraph?>
|
||||
.ReturnsAsync(new Dictionary<string, PoESubgraph?>
|
||||
{
|
||||
["CVE-2021-44228"] = subgraph1,
|
||||
["CVE-2023-12345"] = subgraph2
|
||||
@@ -270,7 +270,7 @@ public class PoEGenerationStageExecutorTests : IDisposable
|
||||
|
||||
_resolverMock
|
||||
.Setup(x => x.ResolveBatchAsync(It.IsAny<IReadOnlyList<ReachabilityResolutionRequest>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Dictionary<string, Subgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
.ReturnsAsync(new Dictionary<string, PoESubgraph?> { ["CVE-2021-44228"] = subgraph });
|
||||
|
||||
_emitterMock
|
||||
.Setup(x => x.EmitPoEAsync(It.IsAny<Subgraph>(), It.IsAny<ProofMetadata>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
@@ -306,9 +306,9 @@ public class PoEGenerationStageExecutorTests : IDisposable
|
||||
);
|
||||
}
|
||||
|
||||
private Subgraph CreateTestSubgraph(string vulnId, string componentRef)
|
||||
private PoESubgraph CreateTestSubgraph(string vulnId, string componentRef)
|
||||
{
|
||||
return new Subgraph(
|
||||
return new PoESubgraph(
|
||||
BuildId: "gnu-build-id:test",
|
||||
ComponentRef: componentRef,
|
||||
VulnId: vulnId,
|
||||
|
||||
Reference in New Issue
Block a user