Files
git.stella-ops.org/src/VexLens/__Libraries/StellaOps.VexLens.Spdx3/CvssMapper.cs
2026-01-08 20:46:43 +02:00

265 lines
9.3 KiB
C#

// <copyright file="CvssMapper.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Globalization;
using StellaOps.Spdx3.Model.Security;
namespace StellaOps.VexLens.Spdx3;
/// <summary>
/// Maps CVSS scores to SPDX 3.0.1 Security profile assessment relationships.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
/// </summary>
public static class CvssMapper
{
/// <summary>
/// Maps a CVSS v3 score to an SPDX 3.0.1 CVSS vulnerability assessment relationship.
/// </summary>
/// <param name="cvssData">The CVSS v3 data to map.</param>
/// <param name="vulnerabilitySpdxId">SPDX ID of the vulnerability being assessed.</param>
/// <param name="assessedElementSpdxId">SPDX ID of the element being assessed.</param>
/// <param name="spdxIdPrefix">Prefix for generating relationship SPDX IDs.</param>
/// <returns>The mapped CVSS assessment relationship.</returns>
public static Spdx3CvssV3VulnAssessmentRelationship MapToSpdx3(
CvssV3Data cvssData,
string vulnerabilitySpdxId,
string assessedElementSpdxId,
string spdxIdPrefix)
{
ArgumentNullException.ThrowIfNull(cvssData);
ArgumentException.ThrowIfNullOrWhiteSpace(vulnerabilitySpdxId);
ArgumentException.ThrowIfNullOrWhiteSpace(assessedElementSpdxId);
ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix);
var spdxId = GenerateSpdxId(spdxIdPrefix, vulnerabilitySpdxId, assessedElementSpdxId, "cvss");
return new Spdx3CvssV3VulnAssessmentRelationship
{
SpdxId = spdxId,
Type = Spdx3CvssV3VulnAssessmentRelationship.TypeName,
AssessedElement = assessedElementSpdxId,
From = vulnerabilitySpdxId,
To = [assessedElementSpdxId],
RelationshipType = "hasAssessmentFor",
Score = cvssData.BaseScore,
Severity = MapSeverity(cvssData.BaseScore),
VectorString = cvssData.VectorString,
PublishedTime = cvssData.PublishedTime,
ModifiedTime = cvssData.ModifiedTime,
SuppliedBy = cvssData.Source
};
}
/// <summary>
/// Maps an EPSS score to an SPDX 3.0.1 EPSS vulnerability assessment relationship.
/// </summary>
/// <param name="epssData">The EPSS data to map.</param>
/// <param name="vulnerabilitySpdxId">SPDX ID of the vulnerability being assessed.</param>
/// <param name="assessedElementSpdxId">SPDX ID of the element being assessed.</param>
/// <param name="spdxIdPrefix">Prefix for generating relationship SPDX IDs.</param>
/// <returns>The mapped EPSS assessment relationship.</returns>
public static Spdx3EpssVulnAssessmentRelationship MapEpssToSpdx3(
EpssData epssData,
string vulnerabilitySpdxId,
string assessedElementSpdxId,
string spdxIdPrefix)
{
ArgumentNullException.ThrowIfNull(epssData);
ArgumentException.ThrowIfNullOrWhiteSpace(vulnerabilitySpdxId);
ArgumentException.ThrowIfNullOrWhiteSpace(assessedElementSpdxId);
ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix);
var spdxId = GenerateSpdxId(spdxIdPrefix, vulnerabilitySpdxId, assessedElementSpdxId, "epss");
return new Spdx3EpssVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = Spdx3EpssVulnAssessmentRelationship.TypeName,
AssessedElement = assessedElementSpdxId,
From = vulnerabilitySpdxId,
To = [assessedElementSpdxId],
RelationshipType = "hasAssessmentFor",
Probability = epssData.Probability,
Percentile = epssData.Percentile,
PublishedTime = epssData.ScoreDate,
SuppliedBy = "https://www.first.org/epss"
};
}
/// <summary>
/// Maps a CVSS v3 base score to severity level.
/// </summary>
/// <param name="score">The CVSS v3 base score (0.0-10.0).</param>
/// <returns>The severity level.</returns>
public static Spdx3CvssSeverity MapSeverity(decimal? score)
{
return score switch
{
null => Spdx3CvssSeverity.None,
0.0m => Spdx3CvssSeverity.None,
>= 0.1m and <= 3.9m => Spdx3CvssSeverity.Low,
>= 4.0m and <= 6.9m => Spdx3CvssSeverity.Medium,
>= 7.0m and <= 8.9m => Spdx3CvssSeverity.High,
>= 9.0m => Spdx3CvssSeverity.Critical,
_ => Spdx3CvssSeverity.None
};
}
/// <summary>
/// Parses a CVSS v3 vector string to extract component scores.
/// </summary>
/// <param name="vectorString">The CVSS v3 vector string (e.g., "CVSS:3.1/AV:N/AC:L/...").</param>
/// <returns>Parsed vector components, or null if parsing fails.</returns>
public static CvssVectorComponents? ParseVectorString(string? vectorString)
{
if (string.IsNullOrWhiteSpace(vectorString))
{
return null;
}
try
{
var parts = vectorString.Split('/');
var components = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var part in parts)
{
var kv = part.Split(':');
if (kv.Length == 2)
{
components[kv[0]] = kv[1];
}
}
return new CvssVectorComponents
{
Version = components.GetValueOrDefault("CVSS", "3.1"),
AttackVector = components.GetValueOrDefault("AV"),
AttackComplexity = components.GetValueOrDefault("AC"),
PrivilegesRequired = components.GetValueOrDefault("PR"),
UserInteraction = components.GetValueOrDefault("UI"),
Scope = components.GetValueOrDefault("S"),
ConfidentialityImpact = components.GetValueOrDefault("C"),
IntegrityImpact = components.GetValueOrDefault("I"),
AvailabilityImpact = components.GetValueOrDefault("A")
};
}
catch
{
return null;
}
}
private static string GenerateSpdxId(
string prefix,
string vulnerabilityId,
string elementId,
string assessmentType)
{
using var sha = System.Security.Cryptography.SHA256.Create();
var input = $"{vulnerabilityId}:{elementId}:{assessmentType}";
var hash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
var shortHash = Convert.ToHexStringLower(hash)[..12];
return $"{prefix.TrimEnd('/')}/{assessmentType}/{shortHash}";
}
}
/// <summary>
/// CVSS v3 data input model.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
/// </summary>
public sealed record CvssV3Data
{
/// <summary>
/// Gets or sets the CVSS v3 base score (0.0-10.0).
/// </summary>
public decimal? BaseScore { get; init; }
/// <summary>
/// Gets or sets the CVSS v3 vector string.
/// </summary>
public string? VectorString { get; init; }
/// <summary>
/// Gets or sets the temporal score.
/// </summary>
public decimal? TemporalScore { get; init; }
/// <summary>
/// Gets or sets the environmental score.
/// </summary>
public decimal? EnvironmentalScore { get; init; }
/// <summary>
/// Gets or sets when the score was published.
/// </summary>
public DateTimeOffset? PublishedTime { get; init; }
/// <summary>
/// Gets or sets when the score was modified.
/// </summary>
public DateTimeOffset? ModifiedTime { get; init; }
/// <summary>
/// Gets or sets the source of the CVSS data.
/// </summary>
public string? Source { get; init; }
}
/// <summary>
/// EPSS data input model.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
/// </summary>
public sealed record EpssData
{
/// <summary>
/// Gets or sets the EPSS probability (0.0-1.0).
/// </summary>
public decimal? Probability { get; init; }
/// <summary>
/// Gets or sets the EPSS percentile (0.0-1.0).
/// </summary>
public decimal? Percentile { get; init; }
/// <summary>
/// Gets or sets the date of the EPSS score.
/// </summary>
public DateTimeOffset? ScoreDate { get; init; }
}
/// <summary>
/// Parsed CVSS v3 vector components.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
/// </summary>
public sealed record CvssVectorComponents
{
/// <summary>Gets or sets the CVSS version.</summary>
public string? Version { get; init; }
/// <summary>Gets or sets the Attack Vector (AV).</summary>
public string? AttackVector { get; init; }
/// <summary>Gets or sets the Attack Complexity (AC).</summary>
public string? AttackComplexity { get; init; }
/// <summary>Gets or sets the Privileges Required (PR).</summary>
public string? PrivilegesRequired { get; init; }
/// <summary>Gets or sets the User Interaction (UI).</summary>
public string? UserInteraction { get; init; }
/// <summary>Gets or sets the Scope (S).</summary>
public string? Scope { get; init; }
/// <summary>Gets or sets the Confidentiality Impact (C).</summary>
public string? ConfidentialityImpact { get; init; }
/// <summary>Gets or sets the Integrity Impact (I).</summary>
public string? IntegrityImpact { get; init; }
/// <summary>Gets or sets the Availability Impact (A).</summary>
public string? AvailabilityImpact { get; init; }
}