save progress

This commit is contained in:
StellaOps Bot
2026-01-06 09:42:02 +02:00
parent 94d68bee8b
commit 37e11918e0
443 changed files with 85863 additions and 897 deletions

View File

@@ -0,0 +1,295 @@
// <copyright file="DsseVerifierTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// </copyright>
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace StellaOps.Attestation.Tests;
/// <summary>
/// Unit tests for DsseVerifier.
/// Sprint: SPRINT_20260105_002_001_REPLAY, Tasks RPL-006 through RPL-010.
/// </summary>
[Trait("Category", "Unit")]
public class DsseVerifierTests
{
private readonly DsseVerifier _verifier;
public DsseVerifierTests()
{
_verifier = new DsseVerifier(NullLogger<DsseVerifier>.Instance);
}
[Fact]
public async Task VerifyAsync_WithValidEcdsaSignature_ReturnsSuccess()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, publicKeyPem) = CreateSignedEnvelope(ecdsa);
// Act
var result = await _verifier.VerifyAsync(envelope, publicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeTrue();
result.ValidSignatureCount.Should().Be(1);
result.TotalSignatureCount.Should().Be(1);
result.PayloadType.Should().Be("https://in-toto.io/Statement/v1");
result.Issues.Should().BeEmpty();
}
[Fact]
public async Task VerifyAsync_WithInvalidSignature_ReturnsFail()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, _) = CreateSignedEnvelope(ecdsa);
// Use a different key for verification
using var differentKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var differentPublicKeyPem = ExportPublicKeyPem(differentKey);
// Act
var result = await _verifier.VerifyAsync(envelope, differentPublicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.ValidSignatureCount.Should().Be(0);
result.Issues.Should().NotBeEmpty();
}
[Fact]
public async Task VerifyAsync_WithMalformedJson_ReturnsParseError()
{
// Arrange
var malformedJson = "{ not valid json }";
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var publicKeyPem = ExportPublicKeyPem(ecdsa);
// Act
var result = await _verifier.VerifyAsync(malformedJson, publicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.Issues.Should().Contain(i => i.Contains("envelope_parse_error"));
}
[Fact]
public async Task VerifyAsync_WithMissingPayload_ReturnsFail()
{
// Arrange
var envelope = JsonSerializer.Serialize(new
{
payloadType = "https://in-toto.io/Statement/v1",
signatures = new[] { new { keyId = "key-001", sig = "YWJj" } }
});
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var publicKeyPem = ExportPublicKeyPem(ecdsa);
// Act
var result = await _verifier.VerifyAsync(envelope, publicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.Issues.Should().Contain(i => i.Contains("envelope_missing_payload"));
}
[Fact]
public async Task VerifyAsync_WithMissingSignatures_ReturnsFail()
{
// Arrange
var payload = Convert.ToBase64String(Encoding.UTF8.GetBytes("{}"));
var envelope = JsonSerializer.Serialize(new
{
payloadType = "https://in-toto.io/Statement/v1",
payload,
signatures = Array.Empty<object>()
});
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var publicKeyPem = ExportPublicKeyPem(ecdsa);
// Act
var result = await _verifier.VerifyAsync(envelope, publicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.Issues.Should().Contain("envelope_missing_signatures");
}
[Fact]
public async Task VerifyAsync_WithNoTrustedKeys_ReturnsFail()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, _) = CreateSignedEnvelope(ecdsa);
// Act
var result = await _verifier.VerifyAsync(envelope, Array.Empty<string>(), TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.Issues.Should().Contain("no_trusted_keys_provided");
}
[Fact]
public async Task VerifyAsync_WithMultipleTrustedKeys_SucceedsWithMatchingKey()
{
// Arrange
using var signingKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var otherKey1 = ECDsa.Create(ECCurve.NamedCurves.nistP256);
using var otherKey2 = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, signingKeyPem) = CreateSignedEnvelope(signingKey);
var trustedKeys = new[]
{
ExportPublicKeyPem(otherKey1),
signingKeyPem,
ExportPublicKeyPem(otherKey2),
};
// Act
var result = await _verifier.VerifyAsync(envelope, trustedKeys, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeTrue();
result.ValidSignatureCount.Should().Be(1);
}
[Fact]
public async Task VerifyAsync_WithKeyResolver_UsesResolverForVerification()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, publicKeyPem) = CreateSignedEnvelope(ecdsa);
Task<string?> KeyResolver(string? keyId, CancellationToken ct)
{
return Task.FromResult<string?>(publicKeyPem);
}
// Act
var result = await _verifier.VerifyAsync(envelope, KeyResolver, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeTrue();
}
[Fact]
public async Task VerifyAsync_WithKeyResolverReturningNull_ReturnsFail()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, _) = CreateSignedEnvelope(ecdsa);
static Task<string?> KeyResolver(string? keyId, CancellationToken ct)
{
return Task.FromResult<string?>(null);
}
// Act
var result = await _verifier.VerifyAsync(envelope, KeyResolver, TestContext.Current.CancellationToken);
// Assert
result.IsValid.Should().BeFalse();
result.Issues.Should().Contain(i => i.Contains("key_not_found"));
}
[Fact]
public async Task VerifyAsync_ReturnsPayloadHash()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, publicKeyPem) = CreateSignedEnvelope(ecdsa);
// Act
var result = await _verifier.VerifyAsync(envelope, publicKeyPem, TestContext.Current.CancellationToken);
// Assert
result.PayloadHash.Should().StartWith("sha256:");
result.PayloadHash.Should().HaveLength("sha256:".Length + 64);
}
[Fact]
public async Task VerifyAsync_ThrowsOnNullEnvelope()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var publicKeyPem = ExportPublicKeyPem(ecdsa);
// Act & Assert - null envelope throws ArgumentNullException
await Assert.ThrowsAsync<ArgumentNullException>(
() => _verifier.VerifyAsync(null!, publicKeyPem, TestContext.Current.CancellationToken));
// Empty envelope throws ArgumentException (whitespace check)
await Assert.ThrowsAsync<ArgumentException>(
() => _verifier.VerifyAsync("", publicKeyPem, TestContext.Current.CancellationToken));
}
[Fact]
public async Task VerifyAsync_ThrowsOnNullKeys()
{
// Arrange
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var (envelope, _) = CreateSignedEnvelope(ecdsa);
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(
() => _verifier.VerifyAsync(envelope, (IEnumerable<string>)null!, TestContext.Current.CancellationToken));
await Assert.ThrowsAsync<ArgumentNullException>(
() => _verifier.VerifyAsync(envelope, (Func<string?, CancellationToken, Task<string?>>)null!, TestContext.Current.CancellationToken));
}
private static (string EnvelopeJson, string PublicKeyPem) CreateSignedEnvelope(ECDsa signingKey)
{
var payloadType = "https://in-toto.io/Statement/v1";
var payloadContent = "{\"_type\":\"https://in-toto.io/Statement/v1\",\"subject\":[]}";
var payloadBytes = Encoding.UTF8.GetBytes(payloadContent);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
// Compute PAE
var pae = DsseHelper.PreAuthenticationEncoding(payloadType, payloadBytes);
// Sign
var signatureBytes = signingKey.SignData(pae, HashAlgorithmName.SHA256);
var signatureBase64 = Convert.ToBase64String(signatureBytes);
// Build envelope
var envelope = JsonSerializer.Serialize(new
{
payloadType,
payload = payloadBase64,
signatures = new[]
{
new { keyId = "test-key-001", sig = signatureBase64 }
}
});
var publicKeyPem = ExportPublicKeyPem(signingKey);
return (envelope, publicKeyPem);
}
private static string ExportPublicKeyPem(ECDsa key)
{
var publicKeyBytes = key.ExportSubjectPublicKeyInfo();
var base64 = Convert.ToBase64String(publicKeyBytes);
var builder = new StringBuilder();
builder.AppendLine("-----BEGIN PUBLIC KEY-----");
for (var i = 0; i < base64.Length; i += 64)
{
builder.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i)));
}
builder.AppendLine("-----END PUBLIC KEY-----");
return builder.ToString();
}
}

View File

@@ -0,0 +1,301 @@
// <copyright file="DsseVerifier.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace StellaOps.Attestation;
/// <summary>
/// Implementation of DSSE signature verification.
/// Uses the existing DsseHelper for PAE computation.
/// </summary>
public sealed class DsseVerifier : IDsseVerifier
{
private readonly ILogger<DsseVerifier> _logger;
/// <summary>
/// JSON serializer options for parsing DSSE envelopes.
/// </summary>
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
};
public DsseVerifier(ILogger<DsseVerifier> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
string publicKeyPem,
CancellationToken cancellationToken = default)
{
return VerifyAsync(envelopeJson, new[] { publicKeyPem }, cancellationToken);
}
/// <inheritdoc />
public async Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
IEnumerable<string> trustedKeysPem,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(envelopeJson);
ArgumentNullException.ThrowIfNull(trustedKeysPem);
var trustedKeys = trustedKeysPem.ToList();
if (trustedKeys.Count == 0)
{
return DsseVerificationResult.Failure(0, ImmutableArray.Create("no_trusted_keys_provided"));
}
return await VerifyWithAllKeysAsync(envelopeJson, trustedKeys, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
Func<string?, CancellationToken, Task<string?>> keyResolver,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(envelopeJson);
ArgumentNullException.ThrowIfNull(keyResolver);
// Parse the envelope
DsseEnvelopeDto? envelope;
try
{
envelope = JsonSerializer.Deserialize<DsseEnvelopeDto>(envelopeJson, JsonOptions);
if (envelope is null)
{
return DsseVerificationResult.ParseError("Failed to deserialize envelope");
}
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "Failed to parse DSSE envelope JSON");
return DsseVerificationResult.ParseError(ex.Message);
}
if (string.IsNullOrWhiteSpace(envelope.Payload))
{
return DsseVerificationResult.Failure(0, ImmutableArray.Create("envelope_missing_payload"));
}
if (envelope.Signatures is null || envelope.Signatures.Count == 0)
{
return DsseVerificationResult.Failure(0, ImmutableArray.Create("envelope_missing_signatures"));
}
// Decode payload
byte[] payloadBytes;
try
{
payloadBytes = Convert.FromBase64String(envelope.Payload);
}
catch (FormatException)
{
return DsseVerificationResult.Failure(envelope.Signatures.Count, ImmutableArray.Create("payload_invalid_base64"));
}
// Compute PAE for signature verification
var payloadType = envelope.PayloadType ?? "https://in-toto.io/Statement/v1";
var pae = DsseHelper.PreAuthenticationEncoding(payloadType, payloadBytes);
// Verify each signature
var verifiedKeyIds = new List<string>();
var issues = new List<string>();
foreach (var sig in envelope.Signatures)
{
if (string.IsNullOrWhiteSpace(sig.Sig))
{
issues.Add($"signature_{sig.KeyId ?? "unknown"}_empty");
continue;
}
// Resolve the public key for this signature
var publicKeyPem = await keyResolver(sig.KeyId, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(publicKeyPem))
{
issues.Add($"key_not_found_{sig.KeyId ?? "unknown"}");
continue;
}
// Verify the signature
try
{
var signatureBytes = Convert.FromBase64String(sig.Sig);
if (VerifySignature(pae, signatureBytes, publicKeyPem))
{
verifiedKeyIds.Add(sig.KeyId ?? "unknown");
_logger.LogDebug("DSSE signature verified for keyId: {KeyId}", sig.KeyId ?? "unknown");
}
else
{
issues.Add($"signature_invalid_{sig.KeyId ?? "unknown"}");
}
}
catch (FormatException)
{
issues.Add($"signature_invalid_base64_{sig.KeyId ?? "unknown"}");
}
catch (CryptographicException ex)
{
issues.Add($"signature_crypto_error_{sig.KeyId ?? "unknown"}: {ex.Message}");
}
}
// Compute payload hash for result
var payloadHash = $"sha256:{Convert.ToHexString(SHA256.HashData(payloadBytes)).ToLowerInvariant()}";
if (verifiedKeyIds.Count > 0)
{
return DsseVerificationResult.Success(
verifiedKeyIds.Count,
envelope.Signatures.Count,
verifiedKeyIds.ToImmutableArray(),
payloadType,
payloadHash);
}
return new DsseVerificationResult
{
IsValid = false,
ValidSignatureCount = 0,
TotalSignatureCount = envelope.Signatures.Count,
VerifiedKeyIds = ImmutableArray<string>.Empty,
PayloadType = payloadType,
PayloadHash = payloadHash,
Issues = issues.ToImmutableArray(),
};
}
/// <summary>
/// Verifies against all trusted keys, returning success if any key validates any signature.
/// </summary>
private async Task<DsseVerificationResult> VerifyWithAllKeysAsync(
string envelopeJson,
List<string> trustedKeys,
CancellationToken cancellationToken)
{
// Parse envelope first to get signature keyIds
DsseEnvelopeDto? envelope;
try
{
envelope = JsonSerializer.Deserialize<DsseEnvelopeDto>(envelopeJson, JsonOptions);
if (envelope is null)
{
return DsseVerificationResult.ParseError("Failed to deserialize envelope");
}
}
catch (JsonException ex)
{
return DsseVerificationResult.ParseError(ex.Message);
}
if (envelope.Signatures is null || envelope.Signatures.Count == 0)
{
return DsseVerificationResult.Failure(0, ImmutableArray.Create("envelope_missing_signatures"));
}
// Try each trusted key
var allIssues = new List<string>();
foreach (var key in trustedKeys)
{
var keyIndex = trustedKeys.IndexOf(key);
async Task<string?> SingleKeyResolver(string? keyId, CancellationToken ct)
{
await Task.CompletedTask.ConfigureAwait(false);
return key;
}
var result = await VerifyAsync(envelopeJson, SingleKeyResolver, cancellationToken).ConfigureAwait(false);
if (result.IsValid)
{
return result;
}
// Collect issues for debugging
foreach (var issue in result.Issues)
{
allIssues.Add($"key{keyIndex}: {issue}");
}
}
return DsseVerificationResult.Failure(envelope.Signatures.Count, allIssues.ToImmutableArray());
}
/// <summary>
/// Verifies a signature against PAE using the provided public key.
/// Supports ECDSA P-256 and RSA keys.
/// </summary>
private bool VerifySignature(byte[] pae, byte[] signature, string publicKeyPem)
{
// Try ECDSA first (most common for Sigstore/Fulcio)
try
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(publicKeyPem);
return ecdsa.VerifyData(pae, signature, HashAlgorithmName.SHA256);
}
catch (CryptographicException)
{
// Not an ECDSA key, try RSA
}
// Try RSA
try
{
using var rsa = RSA.Create();
rsa.ImportFromPem(publicKeyPem);
return rsa.VerifyData(pae, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
catch (CryptographicException)
{
// Not an RSA key either
}
// Try Ed25519 if available (.NET 9+)
try
{
// Ed25519 support via System.Security.Cryptography
// Note: Ed25519 verification requires different handling
// For now, we log and return false - can be extended later
_logger.LogDebug("Ed25519 signature verification not yet implemented");
return false;
}
catch
{
// Ed25519 not available
}
return false;
}
/// <summary>
/// DTO for deserializing DSSE envelope JSON.
/// </summary>
private sealed class DsseEnvelopeDto
{
public string? PayloadType { get; set; }
public string? Payload { get; set; }
public List<DsseSignatureDto>? Signatures { get; set; }
}
/// <summary>
/// DTO for DSSE signature.
/// </summary>
private sealed class DsseSignatureDto
{
public string? KeyId { get; set; }
public string? Sig { get; set; }
}
}

View File

@@ -0,0 +1,151 @@
// <copyright file="IDsseVerifier.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
namespace StellaOps.Attestation;
/// <summary>
/// Interface for verifying DSSE (Dead Simple Signing Envelope) signatures.
/// </summary>
public interface IDsseVerifier
{
/// <summary>
/// Verifies a DSSE envelope against a public key.
/// </summary>
/// <param name="envelopeJson">The serialized DSSE envelope JSON.</param>
/// <param name="publicKeyPem">The PEM-encoded public key for verification.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Verification result containing status and details.</returns>
Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
string publicKeyPem,
CancellationToken cancellationToken = default);
/// <summary>
/// Verifies a DSSE envelope against multiple trusted public keys.
/// Returns success if at least one signature is valid.
/// </summary>
/// <param name="envelopeJson">The serialized DSSE envelope JSON.</param>
/// <param name="trustedKeysPem">Collection of PEM-encoded public keys.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Verification result containing status and details.</returns>
Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
IEnumerable<string> trustedKeysPem,
CancellationToken cancellationToken = default);
/// <summary>
/// Verifies a DSSE envelope using a key resolver function.
/// </summary>
/// <param name="envelopeJson">The serialized DSSE envelope JSON.</param>
/// <param name="keyResolver">Function to resolve public key by key ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Verification result containing status and details.</returns>
Task<DsseVerificationResult> VerifyAsync(
string envelopeJson,
Func<string?, CancellationToken, Task<string?>> keyResolver,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Result of DSSE signature verification.
/// </summary>
public sealed record DsseVerificationResult
{
/// <summary>
/// Whether the verification succeeded (at least one valid signature).
/// </summary>
public required bool IsValid { get; init; }
/// <summary>
/// Number of signatures that passed verification.
/// </summary>
public required int ValidSignatureCount { get; init; }
/// <summary>
/// Total number of signatures in the envelope.
/// </summary>
public required int TotalSignatureCount { get; init; }
/// <summary>
/// Key IDs of signatures that passed verification.
/// </summary>
public required ImmutableArray<string> VerifiedKeyIds { get; init; }
/// <summary>
/// Key ID used for the primary verified signature (first one that passed).
/// </summary>
public string? PrimaryKeyId { get; init; }
/// <summary>
/// Payload type from the envelope.
/// </summary>
public string? PayloadType { get; init; }
/// <summary>
/// SHA-256 hash of the payload.
/// </summary>
public string? PayloadHash { get; init; }
/// <summary>
/// Issues encountered during verification.
/// </summary>
public required ImmutableArray<string> Issues { get; init; }
/// <summary>
/// Creates a successful verification result.
/// </summary>
public static DsseVerificationResult Success(
int validCount,
int totalCount,
ImmutableArray<string> verifiedKeyIds,
string? payloadType = null,
string? payloadHash = null)
{
return new DsseVerificationResult
{
IsValid = true,
ValidSignatureCount = validCount,
TotalSignatureCount = totalCount,
VerifiedKeyIds = verifiedKeyIds,
PrimaryKeyId = verifiedKeyIds.Length > 0 ? verifiedKeyIds[0] : null,
PayloadType = payloadType,
PayloadHash = payloadHash,
Issues = ImmutableArray<string>.Empty,
};
}
/// <summary>
/// Creates a failed verification result.
/// </summary>
public static DsseVerificationResult Failure(
int totalCount,
ImmutableArray<string> issues)
{
return new DsseVerificationResult
{
IsValid = false,
ValidSignatureCount = 0,
TotalSignatureCount = totalCount,
VerifiedKeyIds = ImmutableArray<string>.Empty,
Issues = issues,
};
}
/// <summary>
/// Creates a failure result for a parsing error.
/// </summary>
public static DsseVerificationResult ParseError(string message)
{
return new DsseVerificationResult
{
IsValid = false,
ValidSignatureCount = 0,
TotalSignatureCount = 0,
VerifiedKeyIds = ImmutableArray<string>.Empty,
Issues = ImmutableArray.Create($"envelope_parse_error: {message}"),
};
}
}

View File

@@ -6,6 +6,10 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
</ItemGroup>

View File

@@ -25,6 +25,12 @@ using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Attestor.Tests;
/// <summary>
/// Integration tests for time skew validation in attestation submission and verification.
/// </summary>
[Trait("Category", TestCategories.Integration)]
[Trait("BlastRadius", TestCategories.BlastRadius.Evidence)]
[Trait("BlastRadius", TestCategories.BlastRadius.Crypto)]
public sealed class TimeSkewValidationIntegrationTests
{
private static readonly DateTimeOffset FixedNow = new(2025, 12, 18, 12, 0, 0, TimeSpan.Zero);