// ----------------------------------------------------------------------------- // 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; /// /// Result of a certificate revocation status check. /// public sealed record CertificateStatusResult { /// /// Gets the revocation status. /// public required RevocationStatus Status { get; init; } /// /// Gets the source of the revocation information. /// public required RevocationSource Source { get; init; } /// /// Gets the time the status was produced. /// public DateTimeOffset? ProducedAt { get; init; } /// /// Gets when this status information was last updated. /// public DateTimeOffset? ThisUpdate { get; init; } /// /// Gets when this status information will next be updated. /// public DateTimeOffset? NextUpdate { get; init; } /// /// Gets the revocation time if the certificate is revoked. /// public DateTimeOffset? RevocationTime { get; init; } /// /// Gets the revocation reason if the certificate is revoked. /// public RevocationReason? RevocationReason { get; init; } /// /// Gets the raw OCSP response if available. /// public ReadOnlyMemory? RawOcspResponse { get; init; } /// /// Gets the raw CRL if available. /// public ReadOnlyMemory? RawCrl { get; init; } /// /// Gets error details if the check failed. /// public string? Error { get; init; } /// /// Gets the responder URL that was used. /// public string? ResponderUrl { get; init; } /// /// Gets whether the status is considered valid. /// public bool IsValid => Status == RevocationStatus.Good; /// /// Gets whether the response is still fresh. /// public bool IsFresh => NextUpdate.HasValue && NextUpdate.Value > DateTimeOffset.UtcNow; /// /// Creates a result indicating the certificate is good. /// public static CertificateStatusResult Good( RevocationSource source, DateTimeOffset? thisUpdate = null, DateTimeOffset? nextUpdate = null, ReadOnlyMemory? 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 }; /// /// Creates a result indicating the certificate is revoked. /// 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 }; /// /// Creates a result indicating the status is unknown. /// public static CertificateStatusResult Unknown(RevocationSource source, string? error = null) => new() { Status = RevocationStatus.Unknown, Source = source, ProducedAt = DateTimeOffset.UtcNow, Error = error }; /// /// Creates a result indicating the check is unavailable. /// public static CertificateStatusResult Unavailable(string error) => new() { Status = RevocationStatus.Unavailable, Source = RevocationSource.None, ProducedAt = DateTimeOffset.UtcNow, Error = error }; } /// /// Result of checking an entire certificate chain. /// public sealed record ChainStatusResult { /// /// Gets whether the entire chain is valid. /// public bool IsValid => CertificateResults.All(r => r.Status == RevocationStatus.Good); /// /// Gets the status results for each certificate in the chain. /// public required IReadOnlyList CertificateResults { get; init; } /// /// Gets the overall chain status. /// 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; } } /// /// Gets the first revoked certificate result if any. /// public CertificateStatusResult? FirstRevoked => CertificateResults.FirstOrDefault(r => r.Status == RevocationStatus.Revoked); }