sprints work.

This commit is contained in:
master
2026-01-20 00:45:38 +02:00
parent b34bde89fa
commit 4903395618
275 changed files with 52785 additions and 79 deletions

View File

@@ -0,0 +1,106 @@
// -----------------------------------------------------------------------------
// CertificateStatusOptions.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Configuration options for certificate status checking.
// -----------------------------------------------------------------------------
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Options for certificate revocation status checking.
/// </summary>
public sealed record CertificateStatusOptions
{
/// <summary>
/// Gets whether to prefer OCSP over CRL.
/// </summary>
public bool PreferOcsp { get; init; } = true;
/// <summary>
/// Gets whether revocation checking is required.
/// </summary>
public bool RequireRevocationCheck { get; init; } = true;
/// <summary>
/// Gets whether to only accept stapled/cached responses (offline mode).
/// </summary>
public bool OfflineOnly { get; init; } = false;
/// <summary>
/// Gets whether to allow unknown status to pass.
/// </summary>
public bool AllowUnknownStatus { get; init; } = false;
/// <summary>
/// Gets the maximum age for OCSP responses.
/// </summary>
public TimeSpan MaxOcspAge { get; init; } = TimeSpan.FromDays(7);
/// <summary>
/// Gets the maximum age for CRL responses.
/// </summary>
public TimeSpan MaxCrlAge { get; init; } = TimeSpan.FromDays(30);
/// <summary>
/// Gets the timeout for online requests.
/// </summary>
public TimeSpan RequestTimeout { get; init; } = TimeSpan.FromSeconds(10);
/// <summary>
/// Gets whether to include OCSP nonce for replay protection.
/// </summary>
public bool IncludeOcspNonce { get; init; } = true;
/// <summary>
/// Gets whether to cache responses.
/// </summary>
public bool EnableCaching { get; init; } = true;
/// <summary>
/// Gets whether to check the entire certificate chain.
/// </summary>
public bool CheckFullChain { get; init; } = true;
/// <summary>
/// Gets whether to skip root certificate revocation check.
/// </summary>
public bool SkipRootCheck { get; init; } = true;
/// <summary>
/// Gets the default options.
/// </summary>
public static CertificateStatusOptions Default { get; } = new();
/// <summary>
/// Gets strict options (all checks required, short validity).
/// </summary>
public static CertificateStatusOptions Strict { get; } = new()
{
PreferOcsp = true,
RequireRevocationCheck = true,
AllowUnknownStatus = false,
MaxOcspAge = TimeSpan.FromDays(1),
MaxCrlAge = TimeSpan.FromDays(7),
IncludeOcspNonce = true
};
/// <summary>
/// Gets offline-only options (stapled/cached responses only).
/// </summary>
public static CertificateStatusOptions OfflineOnlyMode { get; } = new()
{
OfflineOnly = true,
RequireRevocationCheck = true,
EnableCaching = true
};
/// <summary>
/// Gets permissive options (revocation check optional).
/// </summary>
public static CertificateStatusOptions Permissive { get; } = new()
{
RequireRevocationCheck = false,
AllowUnknownStatus = true
};
}

View File

@@ -0,0 +1,68 @@
// -----------------------------------------------------------------------------
// CertificateStatusRequest.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Request model for certificate status checking.
// -----------------------------------------------------------------------------
using System.Security.Cryptography.X509Certificates;
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Request for checking certificate revocation status.
/// </summary>
public sealed record CertificateStatusRequest
{
/// <summary>
/// Gets the certificate to check.
/// </summary>
public required X509Certificate2 Certificate { get; init; }
/// <summary>
/// Gets the issuer certificate (required for OCSP).
/// </summary>
public X509Certificate2? Issuer { get; init; }
/// <summary>
/// Gets the status check options.
/// </summary>
public CertificateStatusOptions Options { get; init; } = CertificateStatusOptions.Default;
/// <summary>
/// Gets pre-fetched stapled revocation data (for offline verification).
/// </summary>
public StapledRevocationData? StapledData { get; init; }
/// <summary>
/// Gets the validation time (for historical validation).
/// </summary>
public DateTimeOffset? ValidationTime { get; init; }
/// <summary>
/// Creates a request for a certificate with its issuer.
/// </summary>
public static CertificateStatusRequest Create(
X509Certificate2 certificate,
X509Certificate2? issuer = null,
CertificateStatusOptions? options = null) => new()
{
Certificate = certificate,
Issuer = issuer,
Options = options ?? CertificateStatusOptions.Default
};
/// <summary>
/// Creates a request for offline verification using stapled data.
/// </summary>
public static CertificateStatusRequest CreateOffline(
X509Certificate2 certificate,
StapledRevocationData stapledData,
DateTimeOffset? validationTime = null) => new()
{
Certificate = certificate,
StapledData = stapledData,
ValidationTime = validationTime,
Options = CertificateStatusOptions.OfflineOnlyMode
};
}

View File

@@ -0,0 +1,175 @@
// -----------------------------------------------------------------------------
// 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);
}

View File

@@ -0,0 +1,50 @@
// -----------------------------------------------------------------------------
// ICertificateStatusProvider.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Main interface for certificate revocation checking.
// -----------------------------------------------------------------------------
using System.Security.Cryptography.X509Certificates;
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Provides certificate revocation status checking via OCSP, CRL, or stapled responses.
/// </summary>
public interface ICertificateStatusProvider
{
/// <summary>
/// Checks the revocation status of a certificate.
/// </summary>
/// <param name="request">The status check request.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The certificate status result.</returns>
Task<CertificateStatusResult> CheckStatusAsync(
CertificateStatusRequest request,
CancellationToken cancellationToken = default);
/// <summary>
/// Checks the revocation status of a certificate chain.
/// </summary>
/// <param name="chain">The certificate chain to check.</param>
/// <param name="options">Status check options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Status results for each certificate in the chain.</returns>
Task<ChainStatusResult> CheckChainStatusAsync(
X509Chain chain,
CertificateStatusOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Fetches revocation data for stapling (OCSP response and/or CRL).
/// </summary>
/// <param name="certificate">The certificate to get revocation data for.</param>
/// <param name="issuer">The issuer certificate.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Stapled revocation data for bundling.</returns>
Task<StapledRevocationData?> FetchRevocationDataAsync(
X509Certificate2 certificate,
X509Certificate2 issuer,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,126 @@
// -----------------------------------------------------------------------------
// RevocationEnums.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Enumerations for revocation status and sources.
// -----------------------------------------------------------------------------
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Certificate revocation status.
/// </summary>
public enum RevocationStatus
{
/// <summary>
/// The certificate is not revoked.
/// </summary>
Good,
/// <summary>
/// The certificate has been revoked.
/// </summary>
Revoked,
/// <summary>
/// The revocation status could not be determined.
/// </summary>
Unknown,
/// <summary>
/// Revocation checking is not available.
/// </summary>
Unavailable
}
/// <summary>
/// Source of revocation information.
/// </summary>
public enum RevocationSource
{
/// <summary>
/// No revocation source was used.
/// </summary>
None,
/// <summary>
/// Online OCSP responder.
/// </summary>
Ocsp,
/// <summary>
/// Certificate Revocation List.
/// </summary>
Crl,
/// <summary>
/// Pre-fetched (stapled) OCSP response.
/// </summary>
OcspStapled,
/// <summary>
/// Cached CRL.
/// </summary>
CrlCached,
/// <summary>
/// Delta CRL applied to base CRL.
/// </summary>
DeltaCrl
}
/// <summary>
/// Certificate revocation reason codes (RFC 5280).
/// </summary>
public enum RevocationReason
{
/// <summary>
/// No reason specified.
/// </summary>
Unspecified = 0,
/// <summary>
/// The private key was compromised.
/// </summary>
KeyCompromise = 1,
/// <summary>
/// The CA's private key was compromised.
/// </summary>
CaCompromise = 2,
/// <summary>
/// The certificate holder's affiliation changed.
/// </summary>
AffiliationChanged = 3,
/// <summary>
/// The certificate has been superseded.
/// </summary>
Superseded = 4,
/// <summary>
/// The certificate is no longer needed.
/// </summary>
CessationOfOperation = 5,
/// <summary>
/// The certificate is on hold (may be unrevoked).
/// </summary>
CertificateHold = 6,
/// <summary>
/// Removed from CRL (used in delta CRLs).
/// </summary>
RemoveFromCrl = 8,
/// <summary>
/// Privilege withdrawn.
/// </summary>
PrivilegeWithdrawn = 9,
/// <summary>
/// The AA's key was compromised.
/// </summary>
AaCompromise = 10
}

View File

@@ -0,0 +1,132 @@
// -----------------------------------------------------------------------------
// StapledRevocationData.cs
// Sprint: SPRINT_20260119_008 Certificate Status Provider
// Task: CSP-001 - Core Abstractions
// Description: Model for pre-fetched revocation data for offline verification.
// -----------------------------------------------------------------------------
namespace StellaOps.Cryptography.CertificateStatus.Abstractions;
/// <summary>
/// Pre-fetched revocation data for bundling with signatures or evidence.
/// </summary>
public sealed record StapledRevocationData
{
/// <summary>
/// Gets the certificate thumbprint this data is for.
/// </summary>
public required string CertificateThumbprint { get; init; }
/// <summary>
/// Gets the OCSP response if available.
/// </summary>
public ReadOnlyMemory<byte>? OcspResponse { get; init; }
/// <summary>
/// Gets the CRL if available.
/// </summary>
public ReadOnlyMemory<byte>? Crl { get; init; }
/// <summary>
/// Gets when this data was fetched.
/// </summary>
public required DateTimeOffset FetchedAt { get; init; }
/// <summary>
/// Gets when this data expires.
/// </summary>
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>
/// Gets the OCSP responder URL used.
/// </summary>
public string? OcspResponderUrl { get; init; }
/// <summary>
/// Gets the CRL distribution point URL used.
/// </summary>
public string? CrlDistributionPoint { get; init; }
/// <summary>
/// Gets whether this data includes OCSP.
/// </summary>
public bool HasOcsp => OcspResponse is { Length: > 0 };
/// <summary>
/// Gets whether this data includes CRL.
/// </summary>
public bool HasCrl => Crl is { Length: > 0 };
/// <summary>
/// Gets whether this data is still fresh.
/// </summary>
public bool IsFresh => !ExpiresAt.HasValue || ExpiresAt.Value > DateTimeOffset.UtcNow;
/// <summary>
/// Creates stapled data from an OCSP response.
/// </summary>
public static StapledRevocationData FromOcsp(
string thumbprint,
ReadOnlyMemory<byte> ocspResponse,
string? responderUrl = null,
DateTimeOffset? expiresAt = null) => new()
{
CertificateThumbprint = thumbprint,
OcspResponse = ocspResponse,
FetchedAt = DateTimeOffset.UtcNow,
ExpiresAt = expiresAt,
OcspResponderUrl = responderUrl
};
/// <summary>
/// Creates stapled data from a CRL.
/// </summary>
public static StapledRevocationData FromCrl(
string thumbprint,
ReadOnlyMemory<byte> crl,
string? distributionPoint = null,
DateTimeOffset? expiresAt = null) => new()
{
CertificateThumbprint = thumbprint,
Crl = crl,
FetchedAt = DateTimeOffset.UtcNow,
ExpiresAt = expiresAt,
CrlDistributionPoint = distributionPoint
};
}
/// <summary>
/// Collection of stapled revocation data for an entire certificate chain.
/// </summary>
public sealed record StapledRevocationBundle
{
/// <summary>
/// Gets the revocation data for each certificate in the chain.
/// </summary>
public required IReadOnlyList<StapledRevocationData> Certificates { get; init; }
/// <summary>
/// Gets when this bundle was created.
/// </summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// Gets the earliest expiration of any certificate's revocation data.
/// </summary>
public DateTimeOffset? EarliestExpiration =>
Certificates.Where(c => c.ExpiresAt.HasValue)
.OrderBy(c => c.ExpiresAt)
.FirstOrDefault()?.ExpiresAt;
/// <summary>
/// Gets whether all certificates have fresh revocation data.
/// </summary>
public bool IsComplete => Certificates.All(c => c.IsFresh && (c.HasOcsp || c.HasCrl));
/// <summary>
/// Gets the stapled data for a specific certificate thumbprint.
/// </summary>
public StapledRevocationData? GetForCertificate(string thumbprint) =>
Certificates.FirstOrDefault(c =>
c.CertificateThumbprint.Equals(thumbprint, StringComparison.OrdinalIgnoreCase));
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>StellaOps.Cryptography.CertificateStatus.Abstractions</RootNamespace>
</PropertyGroup>
</Project>