//
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
//
using System.Globalization;
using StellaOps.Spdx3.Model.Security;
namespace StellaOps.VexLens.Spdx3;
///
/// Maps CVSS scores to SPDX 3.0.1 Security profile assessment relationships.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
///
public static class CvssMapper
{
///
/// Maps a CVSS v3 score to an SPDX 3.0.1 CVSS vulnerability assessment relationship.
///
/// The CVSS v3 data to map.
/// SPDX ID of the vulnerability being assessed.
/// SPDX ID of the element being assessed.
/// Prefix for generating relationship SPDX IDs.
/// The mapped CVSS assessment relationship.
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
};
}
///
/// Maps an EPSS score to an SPDX 3.0.1 EPSS vulnerability assessment relationship.
///
/// The EPSS data to map.
/// SPDX ID of the vulnerability being assessed.
/// SPDX ID of the element being assessed.
/// Prefix for generating relationship SPDX IDs.
/// The mapped EPSS assessment relationship.
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"
};
}
///
/// Maps a CVSS v3 base score to severity level.
///
/// The CVSS v3 base score (0.0-10.0).
/// The severity level.
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
};
}
///
/// Parses a CVSS v3 vector string to extract component scores.
///
/// The CVSS v3 vector string (e.g., "CVSS:3.1/AV:N/AC:L/...").
/// Parsed vector components, or null if parsing fails.
public static CvssVectorComponents? ParseVectorString(string? vectorString)
{
if (string.IsNullOrWhiteSpace(vectorString))
{
return null;
}
try
{
var parts = vectorString.Split('/');
var components = new Dictionary(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}";
}
}
///
/// CVSS v3 data input model.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
///
public sealed record CvssV3Data
{
///
/// Gets or sets the CVSS v3 base score (0.0-10.0).
///
public decimal? BaseScore { get; init; }
///
/// Gets or sets the CVSS v3 vector string.
///
public string? VectorString { get; init; }
///
/// Gets or sets the temporal score.
///
public decimal? TemporalScore { get; init; }
///
/// Gets or sets the environmental score.
///
public decimal? EnvironmentalScore { get; init; }
///
/// Gets or sets when the score was published.
///
public DateTimeOffset? PublishedTime { get; init; }
///
/// Gets or sets when the score was modified.
///
public DateTimeOffset? ModifiedTime { get; init; }
///
/// Gets or sets the source of the CVSS data.
///
public string? Source { get; init; }
}
///
/// EPSS data input model.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
///
public sealed record EpssData
{
///
/// Gets or sets the EPSS probability (0.0-1.0).
///
public decimal? Probability { get; init; }
///
/// Gets or sets the EPSS percentile (0.0-1.0).
///
public decimal? Percentile { get; init; }
///
/// Gets or sets the date of the EPSS score.
///
public DateTimeOffset? ScoreDate { get; init; }
}
///
/// Parsed CVSS v3 vector components.
/// Sprint: SPRINT_20260107_004_004 Task SP-007
///
public sealed record CvssVectorComponents
{
/// Gets or sets the CVSS version.
public string? Version { get; init; }
/// Gets or sets the Attack Vector (AV).
public string? AttackVector { get; init; }
/// Gets or sets the Attack Complexity (AC).
public string? AttackComplexity { get; init; }
/// Gets or sets the Privileges Required (PR).
public string? PrivilegesRequired { get; init; }
/// Gets or sets the User Interaction (UI).
public string? UserInteraction { get; init; }
/// Gets or sets the Scope (S).
public string? Scope { get; init; }
/// Gets or sets the Confidentiality Impact (C).
public string? ConfidentialityImpact { get; init; }
/// Gets or sets the Integrity Impact (I).
public string? IntegrityImpact { get; init; }
/// Gets or sets the Availability Impact (A).
public string? AvailabilityImpact { get; init; }
}