Rename Vexer to Excititor
This commit is contained in:
326
src/StellaOps.Excititor.Core/VexClaim.cs
Normal file
326
src/StellaOps.Excititor.Core/VexClaim.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace StellaOps.Excititor.Core;
|
||||
|
||||
public sealed record VexClaim
|
||||
{
|
||||
public VexClaim(
|
||||
string vulnerabilityId,
|
||||
string providerId,
|
||||
VexProduct product,
|
||||
VexClaimStatus status,
|
||||
VexClaimDocument document,
|
||||
DateTimeOffset firstSeen,
|
||||
DateTimeOffset lastSeen,
|
||||
VexJustification? justification = null,
|
||||
string? detail = null,
|
||||
VexConfidence? confidence = null,
|
||||
ImmutableDictionary<string, string>? additionalMetadata = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(vulnerabilityId))
|
||||
{
|
||||
throw new ArgumentException("Vulnerability id must be provided.", nameof(vulnerabilityId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
throw new ArgumentException("Provider id must be provided.", nameof(providerId));
|
||||
}
|
||||
|
||||
if (lastSeen < firstSeen)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lastSeen), "Last seen timestamp cannot be earlier than first seen.");
|
||||
}
|
||||
|
||||
VulnerabilityId = vulnerabilityId.Trim();
|
||||
ProviderId = providerId.Trim();
|
||||
Product = product ?? throw new ArgumentNullException(nameof(product));
|
||||
Status = status;
|
||||
Document = document ?? throw new ArgumentNullException(nameof(document));
|
||||
FirstSeen = firstSeen;
|
||||
LastSeen = lastSeen;
|
||||
Justification = justification;
|
||||
Detail = string.IsNullOrWhiteSpace(detail) ? null : detail.Trim();
|
||||
Confidence = confidence;
|
||||
AdditionalMetadata = NormalizeMetadata(additionalMetadata);
|
||||
}
|
||||
|
||||
public string VulnerabilityId { get; }
|
||||
|
||||
public string ProviderId { get; }
|
||||
|
||||
public VexProduct Product { get; }
|
||||
|
||||
public VexClaimStatus Status { get; }
|
||||
|
||||
public VexJustification? Justification { get; }
|
||||
|
||||
public string? Detail { get; }
|
||||
|
||||
public VexClaimDocument Document { get; }
|
||||
|
||||
public DateTimeOffset FirstSeen { get; }
|
||||
|
||||
public DateTimeOffset LastSeen { get; }
|
||||
|
||||
public VexConfidence? Confidence { get; }
|
||||
|
||||
public ImmutableSortedDictionary<string, string> AdditionalMetadata { get; }
|
||||
|
||||
private static ImmutableSortedDictionary<string, string> NormalizeMetadata(
|
||||
ImmutableDictionary<string, string>? additionalMetadata)
|
||||
{
|
||||
if (additionalMetadata is null || additionalMetadata.Count == 0)
|
||||
{
|
||||
return ImmutableSortedDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
var builder = ImmutableSortedDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
|
||||
foreach (var (key, value) in additionalMetadata)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder[key.Trim()] = value?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record VexProduct
|
||||
{
|
||||
public VexProduct(
|
||||
string key,
|
||||
string? name,
|
||||
string? version = null,
|
||||
string? purl = null,
|
||||
string? cpe = null,
|
||||
IEnumerable<string>? componentIdentifiers = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
throw new ArgumentException("Product key must be provided.", nameof(key));
|
||||
}
|
||||
|
||||
Key = key.Trim();
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name.Trim();
|
||||
Version = string.IsNullOrWhiteSpace(version) ? null : version.Trim();
|
||||
Purl = string.IsNullOrWhiteSpace(purl) ? null : purl.Trim();
|
||||
Cpe = string.IsNullOrWhiteSpace(cpe) ? null : cpe.Trim();
|
||||
ComponentIdentifiers = NormalizeComponentIdentifiers(componentIdentifiers);
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public string? Name { get; }
|
||||
|
||||
public string? Version { get; }
|
||||
|
||||
public string? Purl { get; }
|
||||
|
||||
public string? Cpe { get; }
|
||||
|
||||
public ImmutableArray<string> ComponentIdentifiers { get; }
|
||||
|
||||
private static ImmutableArray<string> NormalizeComponentIdentifiers(IEnumerable<string>? identifiers)
|
||||
{
|
||||
if (identifiers is null)
|
||||
{
|
||||
return ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
var set = new SortedSet<string>(StringComparer.Ordinal);
|
||||
foreach (var identifier in identifiers)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
set.Add(identifier.Trim());
|
||||
}
|
||||
|
||||
return set.Count == 0 ? ImmutableArray<string>.Empty : set.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record VexClaimDocument
|
||||
{
|
||||
public VexClaimDocument(
|
||||
VexDocumentFormat format,
|
||||
string digest,
|
||||
Uri sourceUri,
|
||||
string? revision = null,
|
||||
VexSignatureMetadata? signature = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(digest))
|
||||
{
|
||||
throw new ArgumentException("Document digest must be provided.", nameof(digest));
|
||||
}
|
||||
|
||||
Format = format;
|
||||
Digest = digest.Trim();
|
||||
SourceUri = sourceUri ?? throw new ArgumentNullException(nameof(sourceUri));
|
||||
Revision = string.IsNullOrWhiteSpace(revision) ? null : revision.Trim();
|
||||
Signature = signature;
|
||||
}
|
||||
|
||||
public VexDocumentFormat Format { get; }
|
||||
|
||||
public string Digest { get; }
|
||||
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public string? Revision { get; }
|
||||
|
||||
public VexSignatureMetadata? Signature { get; }
|
||||
}
|
||||
|
||||
public sealed record VexSignatureMetadata
|
||||
{
|
||||
public VexSignatureMetadata(
|
||||
string type,
|
||||
string? subject = null,
|
||||
string? issuer = null,
|
||||
string? keyId = null,
|
||||
DateTimeOffset? verifiedAt = null,
|
||||
string? transparencyLogReference = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(type))
|
||||
{
|
||||
throw new ArgumentException("Signature type must be provided.", nameof(type));
|
||||
}
|
||||
|
||||
Type = type.Trim();
|
||||
Subject = string.IsNullOrWhiteSpace(subject) ? null : subject.Trim();
|
||||
Issuer = string.IsNullOrWhiteSpace(issuer) ? null : issuer.Trim();
|
||||
KeyId = string.IsNullOrWhiteSpace(keyId) ? null : keyId.Trim();
|
||||
VerifiedAt = verifiedAt;
|
||||
TransparencyLogReference = string.IsNullOrWhiteSpace(transparencyLogReference)
|
||||
? null
|
||||
: transparencyLogReference.Trim();
|
||||
}
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public string? Subject { get; }
|
||||
|
||||
public string? Issuer { get; }
|
||||
|
||||
public string? KeyId { get; }
|
||||
|
||||
public DateTimeOffset? VerifiedAt { get; }
|
||||
|
||||
public string? TransparencyLogReference { get; }
|
||||
}
|
||||
|
||||
public sealed record VexConfidence
|
||||
{
|
||||
public VexConfidence(string level, double? score = null, string? method = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(level))
|
||||
{
|
||||
throw new ArgumentException("Confidence level must be provided.", nameof(level));
|
||||
}
|
||||
|
||||
if (score is not null && (double.IsNaN(score.Value) || double.IsInfinity(score.Value)))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(score), "Confidence score must be a finite number.");
|
||||
}
|
||||
|
||||
Level = level.Trim();
|
||||
Score = score;
|
||||
Method = string.IsNullOrWhiteSpace(method) ? null : method.Trim();
|
||||
}
|
||||
|
||||
public string Level { get; }
|
||||
|
||||
public double? Score { get; }
|
||||
|
||||
public string? Method { get; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public enum VexDocumentFormat
|
||||
{
|
||||
[EnumMember(Value = "csaf")]
|
||||
Csaf,
|
||||
|
||||
[EnumMember(Value = "cyclonedx")]
|
||||
CycloneDx,
|
||||
|
||||
[EnumMember(Value = "openvex")]
|
||||
OpenVex,
|
||||
|
||||
[EnumMember(Value = "oci_attestation")]
|
||||
OciAttestation,
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public enum VexClaimStatus
|
||||
{
|
||||
[EnumMember(Value = "affected")]
|
||||
Affected,
|
||||
|
||||
[EnumMember(Value = "not_affected")]
|
||||
NotAffected,
|
||||
|
||||
[EnumMember(Value = "fixed")]
|
||||
Fixed,
|
||||
|
||||
[EnumMember(Value = "under_investigation")]
|
||||
UnderInvestigation,
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public enum VexJustification
|
||||
{
|
||||
[EnumMember(Value = "component_not_present")]
|
||||
ComponentNotPresent,
|
||||
|
||||
[EnumMember(Value = "component_not_configured")]
|
||||
ComponentNotConfigured,
|
||||
|
||||
[EnumMember(Value = "vulnerable_code_not_present")]
|
||||
VulnerableCodeNotPresent,
|
||||
|
||||
[EnumMember(Value = "vulnerable_code_not_in_execute_path")]
|
||||
VulnerableCodeNotInExecutePath,
|
||||
|
||||
[EnumMember(Value = "vulnerable_code_cannot_be_controlled_by_adversary")]
|
||||
VulnerableCodeCannotBeControlledByAdversary,
|
||||
|
||||
[EnumMember(Value = "inline_mitigations_already_exist")]
|
||||
InlineMitigationsAlreadyExist,
|
||||
|
||||
[EnumMember(Value = "protected_by_mitigating_control")]
|
||||
ProtectedByMitigatingControl,
|
||||
|
||||
[EnumMember(Value = "code_not_present")]
|
||||
CodeNotPresent,
|
||||
|
||||
[EnumMember(Value = "code_not_reachable")]
|
||||
CodeNotReachable,
|
||||
|
||||
[EnumMember(Value = "requires_configuration")]
|
||||
RequiresConfiguration,
|
||||
|
||||
[EnumMember(Value = "requires_dependency")]
|
||||
RequiresDependency,
|
||||
|
||||
[EnumMember(Value = "requires_environment")]
|
||||
RequiresEnvironment,
|
||||
|
||||
[EnumMember(Value = "protected_by_compensating_control")]
|
||||
ProtectedByCompensatingControl,
|
||||
|
||||
[EnumMember(Value = "protected_at_perimeter")]
|
||||
ProtectedAtPerimeter,
|
||||
|
||||
[EnumMember(Value = "protected_at_runtime")]
|
||||
ProtectedAtRuntime,
|
||||
}
|
||||
Reference in New Issue
Block a user