Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography.CertificateStatus.Abstractions/CertificateStatusResult.cs
2026-01-20 00:45:38 +02:00

176 lines
5.7 KiB
C#

// -----------------------------------------------------------------------------
// CertificateStatusResult.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Result model for certificate status checking.
// -----------------------------------------------------------------------------
using System.Security.Cryptography.X509Certificates;
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Result of a certificate revocation status check.
/// </summary>
public sealed record CertificateStatusResult
{
/// <summary>
/// Gets the revocation status.
/// </summary>
public required RevocationStatus Status { get; init; }
/// <summary>
/// Gets the source of the revocation information.
/// </summary>
public required RevocationSource Source { get; init; }
/// <summary>
/// Gets the time the status was produced.
/// </summary>
public DateTimeOffset? ProducedAt { get; init; }
/// <summary>
/// Gets when this status information was last updated.
/// </summary>
public DateTimeOffset? ThisUpdate { get; init; }
/// <summary>
/// Gets when this status information will next be updated.
/// </summary>
public DateTimeOffset? NextUpdate { get; init; }
/// <summary>
/// Gets the revocation time if the certificate is revoked.
/// </summary>
public DateTimeOffset? RevocationTime { get; init; }
/// <summary>
/// Gets the revocation reason if the certificate is revoked.
/// </summary>
public RevocationReason? RevocationReason { get; init; }
/// <summary>
/// Gets the raw OCSP response if available.
/// </summary>
public ReadOnlyMemory<byte>? RawOcspResponse { get; init; }
/// <summary>
/// Gets the raw CRL if available.
/// </summary>
public ReadOnlyMemory<byte>? RawCrl { get; init; }
/// <summary>
/// Gets error details if the check failed.
/// </summary>
public string? Error { get; init; }
/// <summary>
/// Gets the responder URL that was used.
/// </summary>
public string? ResponderUrl { get; init; }
/// <summary>
/// Gets whether the status is considered valid.
/// </summary>
public bool IsValid => Status == RevocationStatus.Good;
/// <summary>
/// Gets whether the response is still fresh.
/// </summary>
public bool IsFresh => NextUpdate.HasValue && NextUpdate.Value > DateTimeOffset.UtcNow;
/// <summary>
/// Creates a result indicating the certificate is good.
/// </summary>
public static CertificateStatusResult Good(
RevocationSource source,
DateTimeOffset? thisUpdate = null,
DateTimeOffset? nextUpdate = null,
ReadOnlyMemory<byte>? rawResponse = null) => new()
{
Status = RevocationStatus.Good,
Source = source,
ProducedAt = DateTimeOffset.UtcNow,
ThisUpdate = thisUpdate,
NextUpdate = nextUpdate,
RawOcspResponse = source is RevocationSource.Ocsp or RevocationSource.OcspStapled ? rawResponse : null,
RawCrl = source is RevocationSource.Crl or RevocationSource.CrlCached ? rawResponse : null
};
/// <summary>
/// Creates a result indicating the certificate is revoked.
/// </summary>
public static CertificateStatusResult Revoked(
RevocationSource source,
DateTimeOffset revocationTime,
RevocationReason reason = Abstractions.RevocationReason.Unspecified) => new()
{
Status = RevocationStatus.Revoked,
Source = source,
ProducedAt = DateTimeOffset.UtcNow,
RevocationTime = revocationTime,
RevocationReason = reason
};
/// <summary>
/// Creates a result indicating the status is unknown.
/// </summary>
public static CertificateStatusResult Unknown(RevocationSource source, string? error = null) => new()
{
Status = RevocationStatus.Unknown,
Source = source,
ProducedAt = DateTimeOffset.UtcNow,
Error = error
};
/// <summary>
/// Creates a result indicating the check is unavailable.
/// </summary>
public static CertificateStatusResult Unavailable(string error) => new()
{
Status = RevocationStatus.Unavailable,
Source = RevocationSource.None,
ProducedAt = DateTimeOffset.UtcNow,
Error = error
};
}
/// <summary>
/// Result of checking an entire certificate chain.
/// </summary>
public sealed record ChainStatusResult
{
/// <summary>
/// Gets whether the entire chain is valid.
/// </summary>
public bool IsValid => CertificateResults.All(r => r.Status == RevocationStatus.Good);
/// <summary>
/// Gets the status results for each certificate in the chain.
/// </summary>
public required IReadOnlyList<CertificateStatusResult> CertificateResults { get; init; }
/// <summary>
/// Gets the overall chain status.
/// </summary>
public RevocationStatus OverallStatus
{
get
{
if (CertificateResults.Any(r => r.Status == RevocationStatus.Revoked))
return RevocationStatus.Revoked;
if (CertificateResults.Any(r => r.Status == RevocationStatus.Unknown))
return RevocationStatus.Unknown;
if (CertificateResults.Any(r => r.Status == RevocationStatus.Unavailable))
return RevocationStatus.Unavailable;
return RevocationStatus.Good;
}
}
/// <summary>
/// Gets the first revoked certificate result if any.
/// </summary>
public CertificateStatusResult? FirstRevoked =>
CertificateResults.FirstOrDefault(r => r.Status == RevocationStatus.Revoked);
}