Implement VEX document verification system with issuer management and signature verification

- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation.
- Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments.
- Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats.
- Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats.
- Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction.
- Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
This commit is contained in:
StellaOps Bot
2025-12-06 13:41:22 +02:00
parent 2141196496
commit 5e514532df
112 changed files with 24861 additions and 211 deletions

View File

@@ -0,0 +1,183 @@
using System.Text.Json.Serialization;
namespace StellaOps.VexLens.Models;
/// <summary>
/// Normalized VEX document per vex-normalization.schema.json.
/// Supports OpenVEX, CSAF VEX, CycloneDX VEX, SPDX VEX, and StellaOps formats.
/// </summary>
public sealed record NormalizedVexDocument(
[property: JsonPropertyName("schemaVersion")] int SchemaVersion,
[property: JsonPropertyName("documentId")] string DocumentId,
[property: JsonPropertyName("sourceFormat")] VexSourceFormat SourceFormat,
[property: JsonPropertyName("sourceDigest")] string? SourceDigest,
[property: JsonPropertyName("sourceUri")] string? SourceUri,
[property: JsonPropertyName("issuer")] VexIssuer? Issuer,
[property: JsonPropertyName("issuedAt")] DateTimeOffset? IssuedAt,
[property: JsonPropertyName("lastUpdatedAt")] DateTimeOffset? LastUpdatedAt,
[property: JsonPropertyName("statements")] IReadOnlyList<NormalizedStatement> Statements,
[property: JsonPropertyName("provenance")] NormalizationProvenance? Provenance)
{
public const int CurrentSchemaVersion = 1;
}
/// <summary>
/// Original VEX document format before normalization.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<VexSourceFormat>))]
public enum VexSourceFormat
{
[JsonPropertyName("OPENVEX")]
OpenVex,
[JsonPropertyName("CSAF_VEX")]
CsafVex,
[JsonPropertyName("CYCLONEDX_VEX")]
CycloneDxVex,
[JsonPropertyName("SPDX_VEX")]
SpdxVex,
[JsonPropertyName("STELLAOPS")]
StellaOps
}
/// <summary>
/// Issuing authority for a VEX document.
/// </summary>
public sealed record VexIssuer(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("category")] IssuerCategory? Category,
[property: JsonPropertyName("trustTier")] TrustTier? TrustTier,
[property: JsonPropertyName("keyFingerprints")] IReadOnlyList<string>? KeyFingerprints);
/// <summary>
/// Issuer category for trust weighting.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<IssuerCategory>))]
public enum IssuerCategory
{
[JsonPropertyName("VENDOR")]
Vendor,
[JsonPropertyName("DISTRIBUTOR")]
Distributor,
[JsonPropertyName("COMMUNITY")]
Community,
[JsonPropertyName("INTERNAL")]
Internal,
[JsonPropertyName("AGGREGATOR")]
Aggregator
}
/// <summary>
/// Trust tier for policy evaluation.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<TrustTier>))]
public enum TrustTier
{
[JsonPropertyName("AUTHORITATIVE")]
Authoritative,
[JsonPropertyName("TRUSTED")]
Trusted,
[JsonPropertyName("UNTRUSTED")]
Untrusted,
[JsonPropertyName("UNKNOWN")]
Unknown
}
/// <summary>
/// Normalized VEX statement extracted from source.
/// </summary>
public sealed record NormalizedStatement(
[property: JsonPropertyName("statementId")] string StatementId,
[property: JsonPropertyName("vulnerabilityId")] string VulnerabilityId,
[property: JsonPropertyName("vulnerabilityAliases")] IReadOnlyList<string>? VulnerabilityAliases,
[property: JsonPropertyName("product")] NormalizedProduct Product,
[property: JsonPropertyName("status")] VexStatus Status,
[property: JsonPropertyName("statusNotes")] string? StatusNotes,
[property: JsonPropertyName("justification")] VexJustification? Justification,
[property: JsonPropertyName("impactStatement")] string? ImpactStatement,
[property: JsonPropertyName("actionStatement")] string? ActionStatement,
[property: JsonPropertyName("actionStatementTimestamp")] DateTimeOffset? ActionStatementTimestamp,
[property: JsonPropertyName("versions")] VersionRange? Versions,
[property: JsonPropertyName("subcomponents")] IReadOnlyList<NormalizedProduct>? Subcomponents,
[property: JsonPropertyName("firstSeen")] DateTimeOffset? FirstSeen,
[property: JsonPropertyName("lastSeen")] DateTimeOffset? LastSeen);
/// <summary>
/// Normalized VEX status using OpenVEX terminology.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<VexStatus>))]
public enum VexStatus
{
[JsonPropertyName("not_affected")]
NotAffected,
[JsonPropertyName("affected")]
Affected,
[JsonPropertyName("fixed")]
Fixed,
[JsonPropertyName("under_investigation")]
UnderInvestigation
}
/// <summary>
/// Normalized justification when status is not_affected.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<VexJustification>))]
public enum VexJustification
{
[JsonPropertyName("component_not_present")]
ComponentNotPresent,
[JsonPropertyName("vulnerable_code_not_present")]
VulnerableCodeNotPresent,
[JsonPropertyName("vulnerable_code_not_in_execute_path")]
VulnerableCodeNotInExecutePath,
[JsonPropertyName("vulnerable_code_cannot_be_controlled_by_adversary")]
VulnerableCodeCannotBeControlledByAdversary,
[JsonPropertyName("inline_mitigations_already_exist")]
InlineMitigationsAlreadyExist
}
/// <summary>
/// Normalized product reference.
/// </summary>
public sealed record NormalizedProduct(
[property: JsonPropertyName("key")] string Key,
[property: JsonPropertyName("name")] string? Name,
[property: JsonPropertyName("version")] string? Version,
[property: JsonPropertyName("purl")] string? Purl,
[property: JsonPropertyName("cpe")] string? Cpe,
[property: JsonPropertyName("hashes")] IReadOnlyDictionary<string, string>? Hashes);
/// <summary>
/// Version constraints for a statement.
/// </summary>
public sealed record VersionRange(
[property: JsonPropertyName("affected")] IReadOnlyList<string>? Affected,
[property: JsonPropertyName("fixed")] IReadOnlyList<string>? Fixed,
[property: JsonPropertyName("unaffected")] IReadOnlyList<string>? Unaffected);
/// <summary>
/// Metadata about the normalization process.
/// </summary>
public sealed record NormalizationProvenance(
[property: JsonPropertyName("normalizedAt")] DateTimeOffset NormalizedAt,
[property: JsonPropertyName("normalizer")] string Normalizer,
[property: JsonPropertyName("sourceRevision")] string? SourceRevision,
[property: JsonPropertyName("transformationRules")] IReadOnlyList<string>? TransformationRules);