more audit work
This commit is contained in:
264
src/VexLens/__Libraries/StellaOps.VexLens.Spdx3/CvssMapper.cs
Normal file
264
src/VexLens/__Libraries/StellaOps.VexLens.Spdx3/CvssMapper.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
// <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; }
|
||||
}
|
||||
Reference in New Issue
Block a user