Files
git.stella-ops.org/src/VexLens/__Libraries/StellaOps.VexLens.Spdx3/VexToSpdx3Mapper.cs

184 lines
6.3 KiB
C#

// <copyright file="VexToSpdx3Mapper.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
using StellaOps.Spdx3.Model;
using StellaOps.Spdx3.Model.Security;
namespace StellaOps.VexLens.Spdx3;
/// <summary>
/// Maps VEX consensus results to SPDX 3.0.1 Security profile documents.
/// Sprint: SPRINT_20260107_004_004 Task SP-006
/// </summary>
public sealed class VexToSpdx3Mapper : IVexToSpdx3Mapper
{
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="VexToSpdx3Mapper"/> class.
/// </summary>
/// <param name="timeProvider">Time provider for timestamps.</param>
public VexToSpdx3Mapper(TimeProvider timeProvider)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
/// <summary>
/// Initializes a new instance using the system time provider.
/// </summary>
public VexToSpdx3Mapper() : this(TimeProvider.System)
{
}
/// <inheritdoc />
public Task<Spdx3Document> MapConsensusAsync(
VexConsensus consensus,
VexToSpdx3Options options,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(consensus);
ArgumentNullException.ThrowIfNull(options);
// Filter statements if filters are specified
var statements = FilterStatements(consensus.Statements, options);
// Map statements to SPDX 3.0.1 elements
var mappingResult = MapStatements(statements, options.SpdxIdPrefix);
// Build creation info
var creationInfo = new Spdx3CreationInfo
{
Id = $"{options.SpdxIdPrefix}/creationInfo/{consensus.DocumentId}",
SpecVersion = Spdx3CreationInfo.Spdx301Version,
Created = consensus.Timestamp ?? _timeProvider.GetUtcNow(),
CreatedBy = consensus.Author is not null
? ImmutableArray.Create(consensus.Author)
: ImmutableArray<string>.Empty,
CreatedUsing = ImmutableArray.Create(options.ToolId),
Profile = ImmutableArray.Create(
Spdx3ProfileIdentifier.Core,
Spdx3ProfileIdentifier.Security),
DataLicense = Spdx3CreationInfo.Spdx301DataLicense
};
// Include CVSS assessments if requested
var allElements = mappingResult.AllElements.ToList();
if (options.IncludeCvss)
{
var cvssAssessments = BuildCvssAssessments(statements, options.SpdxIdPrefix);
allElements.AddRange(cvssAssessments);
}
if (options.IncludeEpss)
{
var epssAssessments = BuildEpssAssessments(statements, options.SpdxIdPrefix);
allElements.AddRange(epssAssessments);
}
var document = new Spdx3Document(
elements: allElements,
creationInfos: new[] { creationInfo },
profiles: new[] { Spdx3ProfileIdentifier.Core, Spdx3ProfileIdentifier.Security });
return Task.FromResult(document);
}
/// <inheritdoc />
public VexMappingResult MapStatements(
IEnumerable<OpenVexStatement> statements,
string spdxIdPrefix)
{
ArgumentNullException.ThrowIfNull(statements);
ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix);
var vulnerabilities = new List<Spdx3Element>();
var assessments = new List<Spdx3Element>();
var seenVulnerabilities = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var statement in statements)
{
// Create vulnerability element if not already created
if (!seenVulnerabilities.Contains(statement.VulnerabilityId))
{
var vulnerability = BuildVulnerability(statement, spdxIdPrefix);
vulnerabilities.Add(vulnerability);
seenVulnerabilities.Add(statement.VulnerabilityId);
}
// Create VEX assessment relationship
var assessment = VexStatusMapper.MapToSpdx3(statement, spdxIdPrefix);
assessments.Add(assessment);
}
return new VexMappingResult
{
Vulnerabilities = vulnerabilities,
Assessments = assessments
};
}
private static IEnumerable<OpenVexStatement> FilterStatements(
IReadOnlyList<OpenVexStatement> statements,
VexToSpdx3Options options)
{
IEnumerable<OpenVexStatement> result = statements;
if (options.ProductFilter is { Count: > 0 })
{
var productSet = new HashSet<string>(options.ProductFilter, StringComparer.OrdinalIgnoreCase);
result = result.Where(s => productSet.Contains(s.ProductId));
}
if (options.VulnerabilityFilter is { Count: > 0 })
{
var vulnSet = new HashSet<string>(options.VulnerabilityFilter, StringComparer.OrdinalIgnoreCase);
result = result.Where(s => vulnSet.Contains(s.VulnerabilityId));
}
return result;
}
private static Spdx3Vulnerability BuildVulnerability(OpenVexStatement statement, string spdxIdPrefix)
{
return new VulnerabilityElementBuilder(spdxIdPrefix)
.WithVulnerabilityId(statement.VulnerabilityId)
.WithPublishedTime(statement.Timestamp)
.Build();
}
private static IEnumerable<Spdx3Element> BuildCvssAssessments(
IEnumerable<OpenVexStatement> statements,
string spdxIdPrefix)
{
foreach (var statement in statements.Where(s => s.CvssV3 is not null))
{
var cvss = CvssMapper.MapToSpdx3(
statement.CvssV3!,
statement.VulnerabilityId,
statement.ProductId,
spdxIdPrefix);
yield return cvss;
}
}
private static IEnumerable<Spdx3Element> BuildEpssAssessments(
IEnumerable<OpenVexStatement> statements,
string spdxIdPrefix)
{
foreach (var statement in statements.Where(s => s.Epss is not null))
{
var epss = CvssMapper.MapEpssToSpdx3(
statement.Epss!,
statement.VulnerabilityId,
statement.ProductId,
spdxIdPrefix);
yield return epss;
}
}
}