265 lines
9.3 KiB
C#
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; }
|
|
}
|