// -----------------------------------------------------------------------------
// 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);
}