Add post-quantum cryptography support with PqSoftCryptoProvider
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled

- Implemented PqSoftCryptoProvider for software-only post-quantum algorithms (Dilithium3, Falcon512) using BouncyCastle.
- Added PqSoftProviderOptions and PqSoftKeyOptions for configuration.
- Created unit tests for Dilithium3 and Falcon512 signing and verification.
- Introduced EcdsaPolicyCryptoProvider for compliance profiles (FIPS/eIDAS) with explicit allow-lists.
- Added KcmvpHashOnlyProvider for KCMVP baseline compliance.
- Updated project files and dependencies for new libraries and testing frameworks.
This commit is contained in:
StellaOps Bot
2025-12-07 15:04:19 +02:00
parent 862bb6ed80
commit 98e6b76584
119 changed files with 11436 additions and 1732 deletions

View File

@@ -9,6 +9,10 @@ internal static class OrchestratorEventKinds
{
public const string ScannerReportReady = "scanner.event.report.ready";
public const string ScannerScanCompleted = "scanner.event.scan.completed";
public const string ScannerScanStarted = "scanner.event.scan.started";
public const string ScannerScanFailed = "scanner.event.scan.failed";
public const string ScannerSbomGenerated = "scanner.event.sbom.generated";
public const string ScannerVulnerabilityDetected = "scanner.event.vulnerability.detected";
}
internal sealed record OrchestratorEvent
@@ -74,6 +78,39 @@ internal sealed record OrchestratorEvent
[JsonPropertyOrder(13)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ImmutableSortedDictionary<string, string>? Attributes { get; init; }
[JsonPropertyName("notifier")]
[JsonPropertyOrder(14)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public NotifierIngestionMetadata? Notifier { get; init; }
}
/// <summary>
/// Metadata for Notifier service ingestion per orchestrator-envelope.schema.json.
/// </summary>
internal sealed record NotifierIngestionMetadata
{
[JsonPropertyName("severityThresholdMet")]
[JsonPropertyOrder(0)]
public bool SeverityThresholdMet { get; init; }
[JsonPropertyName("notificationChannels")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IReadOnlyList<string>? NotificationChannels { get; init; }
[JsonPropertyName("digestEligible")]
[JsonPropertyOrder(2)]
public bool DigestEligible { get; init; } = true;
[JsonPropertyName("immediateDispatch")]
[JsonPropertyOrder(3)]
public bool ImmediateDispatch { get; init; }
[JsonPropertyName("priority")]
[JsonPropertyOrder(4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Priority { get; init; }
}
internal sealed record OrchestratorEventScope
@@ -226,40 +263,40 @@ internal sealed record ReportDeltaPayload
public IReadOnlyList<string>? Kev { get; init; }
}
internal sealed record ReportLinksPayload
{
[JsonPropertyName("report")]
[JsonPropertyOrder(0)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Report { get; init; }
[JsonPropertyName("policy")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Policy { get; init; }
[JsonPropertyName("attestation")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Attestation { get; init; }
}
internal sealed record LinkTarget(
[property: JsonPropertyName("ui"), JsonPropertyOrder(0), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Ui,
[property: JsonPropertyName("api"), JsonPropertyOrder(1), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Api)
{
public static LinkTarget? Create(string? ui, string? api)
{
if (string.IsNullOrWhiteSpace(ui) && string.IsNullOrWhiteSpace(api))
{
return null;
}
return new LinkTarget(
string.IsNullOrWhiteSpace(ui) ? null : ui,
string.IsNullOrWhiteSpace(api) ? null : api);
}
}
internal sealed record ReportLinksPayload
{
[JsonPropertyName("report")]
[JsonPropertyOrder(0)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Report { get; init; }
[JsonPropertyName("policy")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Policy { get; init; }
[JsonPropertyName("attestation")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public LinkTarget? Attestation { get; init; }
}
internal sealed record LinkTarget(
[property: JsonPropertyName("ui"), JsonPropertyOrder(0), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Ui,
[property: JsonPropertyName("api"), JsonPropertyOrder(1), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Api)
{
public static LinkTarget? Create(string? ui, string? api)
{
if (string.IsNullOrWhiteSpace(ui) && string.IsNullOrWhiteSpace(api))
{
return null;
}
return new LinkTarget(
string.IsNullOrWhiteSpace(ui) ? null : ui,
string.IsNullOrWhiteSpace(api) ? null : api);
}
}
internal sealed record FindingSummaryPayload
{
@@ -287,3 +324,274 @@ internal sealed record FindingSummaryPayload
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Reachability { get; init; }
}
/// <summary>
/// Payload for scanner.event.scan.started events.
/// </summary>
internal sealed record ScanStartedEventPayload : OrchestratorEventPayload
{
[JsonPropertyName("scanId")]
[JsonPropertyOrder(0)]
public string ScanId { get; init; } = string.Empty;
[JsonPropertyName("jobId")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? JobId { get; init; }
[JsonPropertyName("target")]
[JsonPropertyOrder(2)]
public ScanTargetPayload Target { get; init; } = new();
[JsonPropertyName("startedAt")]
[JsonPropertyOrder(3)]
public DateTimeOffset StartedAt { get; init; }
[JsonPropertyName("status")]
[JsonPropertyOrder(4)]
public string Status { get; init; } = "started";
}
/// <summary>
/// Payload for scanner.event.scan.failed events.
/// </summary>
internal sealed record ScanFailedEventPayload : OrchestratorEventPayload
{
[JsonPropertyName("scanId")]
[JsonPropertyOrder(0)]
public string ScanId { get; init; } = string.Empty;
[JsonPropertyName("jobId")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? JobId { get; init; }
[JsonPropertyName("target")]
[JsonPropertyOrder(2)]
public ScanTargetPayload Target { get; init; } = new();
[JsonPropertyName("startedAt")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public DateTimeOffset? StartedAt { get; init; }
[JsonPropertyName("failedAt")]
[JsonPropertyOrder(4)]
public DateTimeOffset FailedAt { get; init; }
[JsonPropertyName("durationMs")]
[JsonPropertyOrder(5)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public long? DurationMs { get; init; }
[JsonPropertyName("status")]
[JsonPropertyOrder(6)]
public string Status { get; init; } = "failed";
[JsonPropertyName("error")]
[JsonPropertyOrder(7)]
public ScanErrorPayload Error { get; init; } = new();
}
/// <summary>
/// Payload for scanner.event.sbom.generated events.
/// </summary>
internal sealed record SbomGeneratedEventPayload : OrchestratorEventPayload
{
[JsonPropertyName("scanId")]
[JsonPropertyOrder(0)]
public string ScanId { get; init; } = string.Empty;
[JsonPropertyName("sbomId")]
[JsonPropertyOrder(1)]
public string SbomId { get; init; } = string.Empty;
[JsonPropertyName("target")]
[JsonPropertyOrder(2)]
public ScanTargetPayload Target { get; init; } = new();
[JsonPropertyName("generatedAt")]
[JsonPropertyOrder(3)]
public DateTimeOffset GeneratedAt { get; init; }
[JsonPropertyName("format")]
[JsonPropertyOrder(4)]
public string Format { get; init; } = "cyclonedx";
[JsonPropertyName("specVersion")]
[JsonPropertyOrder(5)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SpecVersion { get; init; }
[JsonPropertyName("componentCount")]
[JsonPropertyOrder(6)]
public int ComponentCount { get; init; }
[JsonPropertyName("sbomRef")]
[JsonPropertyOrder(7)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SbomRef { get; init; }
[JsonPropertyName("digest")]
[JsonPropertyOrder(8)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Digest { get; init; }
}
/// <summary>
/// Payload for scanner.event.vulnerability.detected events.
/// </summary>
internal sealed record VulnerabilityDetectedEventPayload : OrchestratorEventPayload
{
[JsonPropertyName("scanId")]
[JsonPropertyOrder(0)]
public string ScanId { get; init; } = string.Empty;
[JsonPropertyName("vulnerability")]
[JsonPropertyOrder(1)]
public VulnerabilityInfoPayload Vulnerability { get; init; } = new();
[JsonPropertyName("affectedComponent")]
[JsonPropertyOrder(2)]
public ComponentInfoPayload AffectedComponent { get; init; } = new();
[JsonPropertyName("reachability")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Reachability { get; init; }
[JsonPropertyName("detectedAt")]
[JsonPropertyOrder(4)]
public DateTimeOffset DetectedAt { get; init; }
}
/// <summary>
/// Target being scanned.
/// </summary>
internal sealed record ScanTargetPayload
{
[JsonPropertyName("type")]
[JsonPropertyOrder(0)]
public string Type { get; init; } = "container_image";
[JsonPropertyName("identifier")]
[JsonPropertyOrder(1)]
public string Identifier { get; init; } = string.Empty;
[JsonPropertyName("digest")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Digest { get; init; }
[JsonPropertyName("tag")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Tag { get; init; }
[JsonPropertyName("platform")]
[JsonPropertyOrder(4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Platform { get; init; }
}
/// <summary>
/// Error information for failed scans.
/// </summary>
internal sealed record ScanErrorPayload
{
[JsonPropertyName("code")]
[JsonPropertyOrder(0)]
public string Code { get; init; } = "SCAN_FAILED";
[JsonPropertyName("message")]
[JsonPropertyOrder(1)]
public string Message { get; init; } = string.Empty;
[JsonPropertyName("details")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ImmutableDictionary<string, string>? Details { get; init; }
[JsonPropertyName("recoverable")]
[JsonPropertyOrder(3)]
public bool Recoverable { get; init; }
}
/// <summary>
/// Vulnerability information.
/// </summary>
internal sealed record VulnerabilityInfoPayload
{
[JsonPropertyName("id")]
[JsonPropertyOrder(0)]
public string Id { get; init; } = string.Empty;
[JsonPropertyName("severity")]
[JsonPropertyOrder(1)]
public string Severity { get; init; } = "unknown";
[JsonPropertyName("cvssScore")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? CvssScore { get; init; }
[JsonPropertyName("cvssVector")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? CvssVector { get; init; }
[JsonPropertyName("title")]
[JsonPropertyOrder(4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Title { get; init; }
[JsonPropertyName("fixAvailable")]
[JsonPropertyOrder(5)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? FixAvailable { get; init; }
[JsonPropertyName("fixedVersion")]
[JsonPropertyOrder(6)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? FixedVersion { get; init; }
[JsonPropertyName("kevListed")]
[JsonPropertyOrder(7)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? KevListed { get; init; }
[JsonPropertyName("epssScore")]
[JsonPropertyOrder(8)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? EpssScore { get; init; }
}
/// <summary>
/// Component information.
/// </summary>
internal sealed record ComponentInfoPayload
{
[JsonPropertyName("purl")]
[JsonPropertyOrder(0)]
public string Purl { get; init; } = string.Empty;
[JsonPropertyName("name")]
[JsonPropertyOrder(1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; init; }
[JsonPropertyName("version")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Version { get; init; }
[JsonPropertyName("ecosystem")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Ecosystem { get; init; }
[JsonPropertyName("location")]
[JsonPropertyOrder(4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Location { get; init; }
}

View File

@@ -98,17 +98,17 @@ internal static class OrchestratorEventSerializer
"newHigh",
"kev"
},
[typeof(ReportLinksPayload)] = new[]
{
"report",
"policy",
"attestation"
},
[typeof(LinkTarget)] = new[]
{
"ui",
"api"
},
[typeof(ReportLinksPayload)] = new[]
{
"report",
"policy",
"attestation"
},
[typeof(LinkTarget)] = new[]
{
"ui",
"api"
},
[typeof(FindingSummaryPayload)] = new[]
{
"id",
@@ -162,12 +162,12 @@ internal static class OrchestratorEventSerializer
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
}
public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var info = _inner.GetTypeInfo(type, options)
?? throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'.");
if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 })
public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var info = _inner.GetTypeInfo(type, options)
?? throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'.");
if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 })
{
var ordered = info.Properties
.OrderBy(property => GetOrder(type, property.Name))
@@ -178,49 +178,53 @@ internal static class OrchestratorEventSerializer
foreach (var property in ordered)
{
info.Properties.Add(property);
}
}
ConfigurePolymorphism(info);
return info;
}
private static int GetOrder(Type type, string propertyName)
{
}
}
ConfigurePolymorphism(info);
return info;
}
private static int GetOrder(Type type, string propertyName)
{
if (PropertyOrder.TryGetValue(type, out var order) && Array.IndexOf(order, propertyName) is { } index and >= 0)
{
return index;
}
if (type.BaseType is not null)
{
return GetOrder(type.BaseType, propertyName);
}
return int.MaxValue;
}
private static void ConfigurePolymorphism(JsonTypeInfo info)
{
if (info.Type != typeof(OrchestratorEventPayload))
{
return;
}
info.PolymorphismOptions ??= new JsonPolymorphismOptions();
AddDerivedType(info.PolymorphismOptions, typeof(ReportReadyEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(ScanCompletedEventPayload));
}
private static void AddDerivedType(JsonPolymorphismOptions options, Type derivedType)
{
if (options.DerivedTypes.Any(d => d.DerivedType == derivedType))
{
return;
}
options.DerivedTypes.Add(new JsonDerivedType(derivedType));
}
}
}
if (type.BaseType is not null)
{
return GetOrder(type.BaseType, propertyName);
}
return int.MaxValue;
}
private static void ConfigurePolymorphism(JsonTypeInfo info)
{
if (info.Type != typeof(OrchestratorEventPayload))
{
return;
}
info.PolymorphismOptions ??= new JsonPolymorphismOptions();
AddDerivedType(info.PolymorphismOptions, typeof(ReportReadyEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(ScanCompletedEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(ScanStartedEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(ScanFailedEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(SbomGeneratedEventPayload));
AddDerivedType(info.PolymorphismOptions, typeof(VulnerabilityDetectedEventPayload));
}
private static void AddDerivedType(JsonPolymorphismOptions options, Type derivedType)
{
if (options.DerivedTypes.Any(d => d.DerivedType == derivedType))
{
return;
}
options.DerivedTypes.Add(new JsonDerivedType(derivedType));
}
}
}