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,476 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using StellaOps.VexLens.Consensus;
using StellaOps.VexLens.Models;
using StellaOps.VexLens.Normalization;
using StellaOps.VexLens.Storage;
using StellaOps.VexLens.Trust;
using StellaOps.VexLens.Verification;
namespace StellaOps.VexLens.Testing;
/// <summary>
/// Test harness for VexLens operations with determinism verification.
/// </summary>
public sealed class VexLensTestHarness : IDisposable
{
private readonly VexNormalizerRegistry _normalizerRegistry;
private readonly InMemoryIssuerDirectory _issuerDirectory;
private readonly InMemoryConsensusEventEmitter _eventEmitter;
private readonly InMemoryConsensusProjectionStore _projectionStore;
private readonly TrustWeightEngine _trustWeightEngine;
private readonly VexConsensusEngine _consensusEngine;
public VexLensTestHarness()
{
_normalizerRegistry = new VexNormalizerRegistry();
_normalizerRegistry.Register(new OpenVexNormalizer());
_normalizerRegistry.Register(new CsafVexNormalizer());
_normalizerRegistry.Register(new CycloneDxVexNormalizer());
_issuerDirectory = new InMemoryIssuerDirectory();
_eventEmitter = new InMemoryConsensusEventEmitter();
_projectionStore = new InMemoryConsensusProjectionStore(_eventEmitter);
_trustWeightEngine = new TrustWeightEngine();
_consensusEngine = new VexConsensusEngine();
}
public IVexNormalizerRegistry NormalizerRegistry => _normalizerRegistry;
public IIssuerDirectory IssuerDirectory => _issuerDirectory;
public IConsensusEventEmitter EventEmitter => _eventEmitter;
public InMemoryConsensusEventEmitter TestEventEmitter => _eventEmitter;
public IConsensusProjectionStore ProjectionStore => _projectionStore;
public ITrustWeightEngine TrustWeightEngine => _trustWeightEngine;
public IVexConsensusEngine ConsensusEngine => _consensusEngine;
/// <summary>
/// Normalizes VEX content and returns the result.
/// </summary>
public async Task<NormalizationResult> NormalizeAsync(
string content,
string? sourceUri = null,
CancellationToken cancellationToken = default)
{
var normalizer = _normalizerRegistry.DetectNormalizer(content);
if (normalizer == null)
{
throw new InvalidOperationException("No normalizer found for content");
}
var context = new NormalizationContext(
SourceUri: sourceUri,
NormalizedAt: DateTimeOffset.UtcNow,
Normalizer: "VexLensTestHarness",
Options: null);
return await normalizer.NormalizeAsync(content, context, cancellationToken);
}
/// <summary>
/// Computes trust weight for a statement.
/// </summary>
public async Task<TrustWeightResult> ComputeTrustWeightAsync(
NormalizedStatement statement,
VexIssuer? issuer = null,
DateTimeOffset? documentIssuedAt = null,
CancellationToken cancellationToken = default)
{
var request = new TrustWeightRequest(
Statement: statement,
Issuer: issuer,
SignatureVerification: null,
DocumentIssuedAt: documentIssuedAt,
Context: new TrustWeightContext(
TenantId: null,
EvaluationTime: DateTimeOffset.UtcNow,
CustomFactors: null));
return await _trustWeightEngine.ComputeWeightAsync(request, cancellationToken);
}
/// <summary>
/// Computes consensus from weighted statements.
/// </summary>
public async Task<VexConsensusResult> ComputeConsensusAsync(
string vulnerabilityId,
string productKey,
IEnumerable<WeightedStatement> statements,
ConsensusMode mode = ConsensusMode.WeightedVote,
CancellationToken cancellationToken = default)
{
var request = new VexConsensusRequest(
VulnerabilityId: vulnerabilityId,
ProductKey: productKey,
Statements: statements.ToList(),
Context: new ConsensusContext(
TenantId: null,
EvaluationTime: DateTimeOffset.UtcNow,
Policy: new ConsensusPolicy(
Mode: mode,
MinimumWeightThreshold: 0.1,
ConflictThreshold: 0.3,
RequireJustificationForNotAffected: false,
PreferredIssuers: null)));
return await _consensusEngine.ComputeConsensusAsync(request, cancellationToken);
}
/// <summary>
/// Registers a test issuer.
/// </summary>
public async Task<IssuerRecord> RegisterTestIssuerAsync(
string issuerId,
string name,
IssuerCategory category = IssuerCategory.Vendor,
TrustTier trustTier = TrustTier.Trusted,
CancellationToken cancellationToken = default)
{
var registration = new IssuerRegistration(
IssuerId: issuerId,
Name: name,
Category: category,
TrustTier: trustTier,
InitialKeys: null,
Metadata: null);
return await _issuerDirectory.RegisterIssuerAsync(registration, cancellationToken);
}
/// <summary>
/// Creates a test statement.
/// </summary>
public static NormalizedStatement CreateTestStatement(
string vulnerabilityId,
string productKey,
VexStatus status,
VexJustification? justification = null,
string? statementId = null)
{
return new NormalizedStatement(
StatementId: statementId ?? $"stmt-{Guid.NewGuid():N}",
VulnerabilityId: vulnerabilityId,
VulnerabilityAliases: null,
Product: new NormalizedProduct(
Key: productKey,
Name: null,
Version: null,
Purl: productKey.StartsWith("pkg:") ? productKey : null,
Cpe: null,
Hashes: null),
Status: status,
StatusNotes: null,
Justification: justification,
ImpactStatement: null,
ActionStatement: null,
ActionStatementTimestamp: null,
Versions: null,
Subcomponents: null,
FirstSeen: DateTimeOffset.UtcNow,
LastSeen: DateTimeOffset.UtcNow);
}
/// <summary>
/// Creates a test issuer.
/// </summary>
public static VexIssuer CreateTestIssuer(
string id,
string name,
IssuerCategory category = IssuerCategory.Vendor,
TrustTier trustTier = TrustTier.Trusted)
{
return new VexIssuer(
Id: id,
Name: name,
Category: category,
TrustTier: trustTier,
KeyFingerprints: null);
}
/// <summary>
/// Clears all test data.
/// </summary>
public void Reset()
{
_eventEmitter.Clear();
}
public void Dispose()
{
// Cleanup if needed
}
}
/// <summary>
/// Determinism verification harness for VexLens operations.
/// </summary>
public sealed class DeterminismHarness
{
private readonly VexLensTestHarness _harness;
public DeterminismHarness()
{
_harness = new VexLensTestHarness();
}
/// <summary>
/// Verifies that normalization produces deterministic results.
/// </summary>
public async Task<DeterminismResult> VerifyNormalizationDeterminismAsync(
string content,
int iterations = 3,
CancellationToken cancellationToken = default)
{
var results = new List<string>();
for (var i = 0; i < iterations; i++)
{
var result = await _harness.NormalizeAsync(content, cancellationToken: cancellationToken);
if (result.Success && result.Document != null)
{
var hash = ComputeDocumentHash(result.Document);
results.Add(hash);
}
else
{
results.Add($"error:{result.Errors.FirstOrDefault()?.Code}");
}
}
var isEqual = results.Distinct().Count() == 1;
return new DeterminismResult(
Operation: "normalization",
IsDeterministic: isEqual,
Iterations: iterations,
DistinctResults: results.Distinct().Count(),
FirstResult: results.FirstOrDefault(),
Discrepancies: isEqual ? null : results);
}
/// <summary>
/// Verifies that consensus produces deterministic results.
/// </summary>
public async Task<DeterminismResult> VerifyConsensusDeterminismAsync(
string vulnerabilityId,
string productKey,
IEnumerable<(NormalizedStatement Statement, VexIssuer? Issuer)> statements,
int iterations = 3,
CancellationToken cancellationToken = default)
{
var results = new List<string>();
var stmtList = statements.ToList();
for (var i = 0; i < iterations; i++)
{
var weighted = new List<WeightedStatement>();
foreach (var (stmt, issuer) in stmtList)
{
var weight = await _harness.ComputeTrustWeightAsync(stmt, issuer, cancellationToken: cancellationToken);
weighted.Add(new WeightedStatement(stmt, weight, issuer, null));
}
var result = await _harness.ComputeConsensusAsync(
vulnerabilityId,
productKey,
weighted,
cancellationToken: cancellationToken);
var hash = ComputeConsensusHash(result);
results.Add(hash);
}
var isEqual = results.Distinct().Count() == 1;
return new DeterminismResult(
Operation: "consensus",
IsDeterministic: isEqual,
Iterations: iterations,
DistinctResults: results.Distinct().Count(),
FirstResult: results.FirstOrDefault(),
Discrepancies: isEqual ? null : results);
}
/// <summary>
/// Verifies that trust weight computation produces deterministic results.
/// </summary>
public async Task<DeterminismResult> VerifyTrustWeightDeterminismAsync(
NormalizedStatement statement,
VexIssuer? issuer,
int iterations = 3,
CancellationToken cancellationToken = default)
{
var results = new List<string>();
for (var i = 0; i < iterations; i++)
{
var result = await _harness.ComputeTrustWeightAsync(statement, issuer, cancellationToken: cancellationToken);
var hash = $"{result.Weight:F10}";
results.Add(hash);
}
var isEqual = results.Distinct().Count() == 1;
return new DeterminismResult(
Operation: "trust_weight",
IsDeterministic: isEqual,
Iterations: iterations,
DistinctResults: results.Distinct().Count(),
FirstResult: results.FirstOrDefault(),
Discrepancies: isEqual ? null : results);
}
/// <summary>
/// Runs all determinism checks.
/// </summary>
public async Task<DeterminismReport> RunFullDeterminismCheckAsync(
string vexContent,
CancellationToken cancellationToken = default)
{
var results = new List<DeterminismResult>();
// Normalization
var normResult = await VerifyNormalizationDeterminismAsync(vexContent, cancellationToken: cancellationToken);
results.Add(normResult);
// If normalization succeeded, test downstream operations
if (normResult.IsDeterministic)
{
var normalizeResult = await _harness.NormalizeAsync(vexContent, cancellationToken: cancellationToken);
if (normalizeResult.Success && normalizeResult.Document != null && normalizeResult.Document.Statements.Count > 0)
{
var statement = normalizeResult.Document.Statements[0];
var issuer = normalizeResult.Document.Issuer;
// Trust weight
var trustResult = await VerifyTrustWeightDeterminismAsync(statement, issuer, cancellationToken: cancellationToken);
results.Add(trustResult);
// Consensus
var consensusResult = await VerifyConsensusDeterminismAsync(
statement.VulnerabilityId,
statement.Product.Key,
[(statement, issuer)],
cancellationToken: cancellationToken);
results.Add(consensusResult);
}
}
return new DeterminismReport(
Results: results,
AllDeterministic: results.All(r => r.IsDeterministic),
GeneratedAt: DateTimeOffset.UtcNow);
}
private static string ComputeDocumentHash(NormalizedVexDocument doc)
{
// Create a stable representation for hashing
var sb = new StringBuilder();
sb.Append(doc.DocumentId);
sb.Append(doc.SourceFormat);
sb.Append(doc.Issuer?.Id ?? "null");
foreach (var stmt in doc.Statements.OrderBy(s => s.StatementId))
{
sb.Append(stmt.VulnerabilityId);
sb.Append(stmt.Product.Key);
sb.Append(stmt.Status);
sb.Append(stmt.Justification?.ToString() ?? "null");
}
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sb.ToString()));
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static string ComputeConsensusHash(VexConsensusResult result)
{
var sb = new StringBuilder();
sb.Append(result.ConsensusStatus);
sb.Append(result.ConsensusJustification?.ToString() ?? "null");
sb.Append($"{result.ConfidenceScore:F10}");
sb.Append(result.Outcome);
foreach (var contrib in result.Contributions.OrderBy(c => c.StatementId))
{
sb.Append(contrib.StatementId);
sb.Append($"{contrib.Weight:F10}");
sb.Append(contrib.IsWinner);
}
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sb.ToString()));
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
/// <summary>
/// Result of a determinism check.
/// </summary>
public sealed record DeterminismResult(
string Operation,
bool IsDeterministic,
int Iterations,
int DistinctResults,
string? FirstResult,
IReadOnlyList<string>? Discrepancies);
/// <summary>
/// Report of determinism checks.
/// </summary>
public sealed record DeterminismReport(
IReadOnlyList<DeterminismResult> Results,
bool AllDeterministic,
DateTimeOffset GeneratedAt);
/// <summary>
/// Test data generators for VexLens.
/// </summary>
public static class VexLensTestData
{
/// <summary>
/// Generates a sample OpenVEX document.
/// </summary>
public static string GenerateOpenVexDocument(
string vulnerabilityId,
string productPurl,
VexStatus status,
VexJustification? justification = null)
{
var doc = new
{
@context = "https://openvex.dev/ns/v0.2.0",
@id = $"urn:uuid:{Guid.NewGuid()}",
author = new { @id = "test-vendor", name = "Test Vendor" },
timestamp = DateTimeOffset.UtcNow.ToString("O"),
statements = new[]
{
new
{
vulnerability = vulnerabilityId,
products = new[] { productPurl },
status = status.ToString().ToLowerInvariant().Replace("notaffected", "not_affected").Replace("underinvestigation", "under_investigation"),
justification = justification?.ToString().ToLowerInvariant()
}
}
};
return JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true });
}
/// <summary>
/// Generates sample statements for consensus testing.
/// </summary>
public static IEnumerable<(NormalizedStatement Statement, VexIssuer Issuer)> GenerateConflictingStatements(
string vulnerabilityId,
string productKey)
{
yield return (
VexLensTestHarness.CreateTestStatement(vulnerabilityId, productKey, VexStatus.NotAffected, VexJustification.ComponentNotPresent, "stmt-1"),
VexLensTestHarness.CreateTestIssuer("vendor-1", "Vendor A", IssuerCategory.Vendor, TrustTier.Authoritative));
yield return (
VexLensTestHarness.CreateTestStatement(vulnerabilityId, productKey, VexStatus.Affected, null, "stmt-2"),
VexLensTestHarness.CreateTestIssuer("researcher-1", "Security Researcher", IssuerCategory.Community, TrustTier.Trusted));
yield return (
VexLensTestHarness.CreateTestStatement(vulnerabilityId, productKey, VexStatus.UnderInvestigation, null, "stmt-3"),
VexLensTestHarness.CreateTestIssuer("aggregator-1", "VEX Aggregator", IssuerCategory.Aggregator, TrustTier.Unknown));
}
}