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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user