docs re-org, audit fixes, build fixes

This commit is contained in:
StellaOps Bot
2026-01-05 09:35:33 +02:00
parent eca4e964d3
commit dfab8a29c3
173 changed files with 1276 additions and 560 deletions

View File

@@ -11,6 +11,7 @@ namespace StellaOps.Cryptography.Kms;
public sealed class AwsKmsClient : IKmsClient, IDisposable
{
private readonly IAwsKmsFacade _facade;
private readonly TimeProvider _timeProvider;
private readonly TimeSpan _metadataCacheDuration;
private readonly TimeSpan _publicKeyCacheDuration;
@@ -18,11 +19,12 @@ public sealed class AwsKmsClient : IKmsClient, IDisposable
private readonly ConcurrentDictionary<string, CachedPublicKey> _publicKeyCache = new(StringComparer.Ordinal);
private bool _disposed;
public AwsKmsClient(IAwsKmsFacade facade, AwsKmsOptions options)
public AwsKmsClient(IAwsKmsFacade facade, AwsKmsOptions options, TimeProvider? timeProvider = null)
{
_facade = facade ?? throw new ArgumentNullException(nameof(facade));
ArgumentNullException.ThrowIfNull(options);
_timeProvider = timeProvider ?? TimeProvider.System;
_metadataCacheDuration = options.MetadataCacheDuration;
_publicKeyCacheDuration = options.PublicKeyCacheDuration;
}
@@ -156,7 +158,7 @@ public sealed class AwsKmsClient : IKmsClient, IDisposable
private async Task<AwsKeyMetadata> GetCachedMetadataAsync(string keyId, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_metadataCache.TryGetValue(keyId, out var cached) && cached.ExpiresAt > now)
{
return cached.Metadata;
@@ -170,7 +172,7 @@ public sealed class AwsKmsClient : IKmsClient, IDisposable
private async Task<AwsPublicKeyMaterial> GetCachedPublicKeyAsync(string resource, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_publicKeyCache.TryGetValue(resource, out var cached) && cached.ExpiresAt > now)
{
return cached.Material;

View File

@@ -37,10 +37,12 @@ internal sealed class AwsKmsFacade : IAwsKmsFacade
{
private readonly IAmazonKeyManagementService _client;
private readonly bool _ownsClient;
private readonly TimeProvider _timeProvider;
public AwsKmsFacade(AwsKmsOptions options)
public AwsKmsFacade(AwsKmsOptions options, TimeProvider? timeProvider = null)
{
ArgumentNullException.ThrowIfNull(options);
_timeProvider = timeProvider ?? TimeProvider.System;
var config = new AmazonKeyManagementServiceConfig();
if (!string.IsNullOrWhiteSpace(options.Region))
@@ -59,9 +61,10 @@ internal sealed class AwsKmsFacade : IAwsKmsFacade
_ownsClient = true;
}
public AwsKmsFacade(IAmazonKeyManagementService client)
public AwsKmsFacade(IAmazonKeyManagementService client, TimeProvider? timeProvider = null)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_timeProvider = timeProvider ?? TimeProvider.System;
_ownsClient = false;
}
@@ -116,7 +119,7 @@ internal sealed class AwsKmsFacade : IAwsKmsFacade
}, cancellationToken).ConfigureAwait(false);
var metadata = response.KeyMetadata ?? throw new InvalidOperationException($"Key '{keyId}' was not found.");
var createdAt = metadata.CreationDate?.ToUniversalTime() ?? DateTimeOffset.UtcNow;
var createdAt = metadata.CreationDate?.ToUniversalTime() ?? _timeProvider.GetUtcNow();
return new AwsKeyMetadata(
metadata.KeyId ?? keyId,

View File

@@ -15,15 +15,17 @@ public sealed class Fido2KmsClient : IKmsClient
private readonly byte[] _subjectPublicKeyInfo;
private readonly TimeSpan _metadataCacheDuration;
private readonly string _curveName;
private readonly TimeProvider _timeProvider;
private KmsKeyMetadata? _cachedMetadata;
private DateTimeOffset _metadataExpiresAt;
private bool _disposed;
public Fido2KmsClient(IFido2Authenticator authenticator, Fido2Options options)
public Fido2KmsClient(IFido2Authenticator authenticator, Fido2Options options, TimeProvider? timeProvider = null)
{
_authenticator = authenticator ?? throw new ArgumentNullException(nameof(authenticator));
_options = options ?? throw new ArgumentNullException(nameof(options));
_timeProvider = timeProvider ?? TimeProvider.System;
if (string.IsNullOrWhiteSpace(_options.CredentialId))
{
@@ -99,16 +101,17 @@ public sealed class Fido2KmsClient : IKmsClient
{
ThrowIfDisposed();
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_cachedMetadata is not null && _metadataExpiresAt > now)
{
return Task.FromResult(_cachedMetadata);
}
var createdAt = _options.CreatedAt ?? _timeProvider.GetUtcNow();
var version = new KmsKeyVersionMetadata(
_options.CredentialId,
KmsKeyState.Active,
_options.CreatedAt,
createdAt,
null,
Convert.ToBase64String(_subjectPublicKeyInfo),
_curveName);
@@ -117,7 +120,7 @@ public sealed class Fido2KmsClient : IKmsClient
_options.CredentialId,
KmsAlgorithms.Es256,
KmsKeyState.Active,
_options.CreatedAt,
createdAt,
ImmutableArray.Create(version));
_metadataExpiresAt = now.Add(_metadataCacheDuration);
@@ -138,7 +141,7 @@ public sealed class Fido2KmsClient : IKmsClient
Array.Empty<byte>(),
_publicParameters.Q.X ?? throw new InvalidOperationException("FIDO2 public key missing X coordinate."),
_publicParameters.Q.Y ?? throw new InvalidOperationException("FIDO2 public key missing Y coordinate."),
_options.CreatedAt);
_options.CreatedAt ?? _timeProvider.GetUtcNow());
}
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)

View File

@@ -24,8 +24,9 @@ public sealed class Fido2Options
/// <summary>
/// Gets or sets the timestamp when the credential was provisioned.
/// When not set, the Fido2KmsClient will use the current time via TimeProvider.
/// </summary>
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? CreatedAt { get; set; }
/// <summary>
/// Gets or sets the cache duration for metadata lookups.

View File

@@ -21,9 +21,10 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
private const int MinKeyDerivationIterations = 600_000;
private readonly FileKmsOptions _options;
private readonly TimeProvider _timeProvider;
private readonly SemaphoreSlim _mutex = new(1, 1);
public FileKmsClient(FileKmsOptions options)
public FileKmsClient(FileKmsOptions options, TimeProvider? timeProvider = null)
{
ArgumentNullException.ThrowIfNull(options);
if (string.IsNullOrWhiteSpace(options.RootPath))
@@ -37,6 +38,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
}
_options = options;
_timeProvider = timeProvider ?? TimeProvider.System;
if (_options.KeyDerivationIterations < MinKeyDerivationIterations)
{
throw new ArgumentOutOfRangeException(
@@ -202,7 +204,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
}
var versionId = string.IsNullOrWhiteSpace(material.VersionId)
? $"{DateTimeOffset.UtcNow:yyyyMMddTHHmmssfffZ}"
? $"{_timeProvider.GetUtcNow():yyyyMMddTHHmmssfffZ}"
: material.VersionId;
if (record.Versions.Any(v => string.Equals(v.VersionId, versionId, StringComparison.Ordinal)))
@@ -234,7 +236,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
existing.State = KmsKeyState.PendingRotation;
}
var createdAt = material.CreatedAt == default ? DateTimeOffset.UtcNow : material.CreatedAt;
var createdAt = material.CreatedAt == default ? _timeProvider.GetUtcNow() : material.CreatedAt;
var publicKey = CombinePublicCoordinates(material.Qx, material.Qy);
record.Versions.Add(new KeyVersionRecord
@@ -280,7 +282,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
throw new InvalidOperationException($"Key '{keyId}' has been revoked and cannot be rotated.");
}
var timestamp = DateTimeOffset.UtcNow;
var timestamp = _timeProvider.GetUtcNow();
var versionId = $"{timestamp:yyyyMMddTHHmmssfffZ}";
var keyData = CreateKeyMaterial(record.Algorithm);
@@ -334,7 +336,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false)
?? throw new InvalidOperationException($"Key '{keyId}' does not exist.");
var timestamp = DateTimeOffset.UtcNow;
var timestamp = _timeProvider.GetUtcNow();
record.State = KmsKeyState.Revoked;
foreach (var version in record.Versions)
{
@@ -381,7 +383,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
KeyId = keyId,
Algorithm = _options.Algorithm,
State = KmsKeyState.Active,
CreatedAt = DateTimeOffset.UtcNow,
CreatedAt = _timeProvider.GetUtcNow(),
};
await SaveMetadataAsync(record, cancellationToken).ConfigureAwait(false);
@@ -645,7 +647,7 @@ public sealed class FileKmsClient : IKmsClient, IDisposable
v.CurveName))
.ToImmutableArray();
var createdAt = record.CreatedAt ?? (versions.Length > 0 ? versions.Min(v => v.CreatedAt) : DateTimeOffset.UtcNow);
var createdAt = record.CreatedAt ?? (versions.Length > 0 ? versions.Min(v => v.CreatedAt) : TimeProvider.System.GetUtcNow());
return new KmsKeyMetadata(record.KeyId, record.Algorithm, record.State, createdAt, versions);
}

View File

@@ -13,6 +13,7 @@ namespace StellaOps.Cryptography.Kms;
public sealed class GcpKmsClient : IKmsClient, IDisposable
{
private readonly IGcpKmsFacade _facade;
private readonly TimeProvider _timeProvider;
private readonly TimeSpan _metadataCacheDuration;
private readonly TimeSpan _publicKeyCacheDuration;
@@ -20,11 +21,12 @@ public sealed class GcpKmsClient : IKmsClient, IDisposable
private readonly ConcurrentDictionary<string, CachedPublicKey> _publicKeyCache = new(StringComparer.Ordinal);
private bool _disposed;
public GcpKmsClient(IGcpKmsFacade facade, GcpKmsOptions options)
public GcpKmsClient(IGcpKmsFacade facade, GcpKmsOptions options, TimeProvider? timeProvider = null)
{
_facade = facade ?? throw new ArgumentNullException(nameof(facade));
ArgumentNullException.ThrowIfNull(options);
_timeProvider = timeProvider ?? TimeProvider.System;
_metadataCacheDuration = options.MetadataCacheDuration;
_publicKeyCacheDuration = options.PublicKeyCacheDuration;
}
@@ -170,7 +172,7 @@ public sealed class GcpKmsClient : IKmsClient, IDisposable
private async Task<CryptoKeySnapshot> GetCachedMetadataAsync(string keyId, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_metadataCache.TryGetValue(keyId, out var cached) && cached.ExpiresAt > now)
{
return cached.Snapshot;
@@ -186,7 +188,7 @@ public sealed class GcpKmsClient : IKmsClient, IDisposable
private async Task<GcpPublicMaterial> GetCachedPublicKeyAsync(string versionName, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_publicKeyCache.TryGetValue(versionName, out var cached) && cached.ExpiresAt > now)
{
return cached.Material;

View File

@@ -44,10 +44,12 @@ internal sealed class GcpKmsFacade : IGcpKmsFacade
{
private readonly KeyManagementServiceClient _client;
private readonly bool _ownsClient;
private readonly TimeProvider _timeProvider;
public GcpKmsFacade(GcpKmsOptions options)
public GcpKmsFacade(GcpKmsOptions options, TimeProvider? timeProvider = null)
{
ArgumentNullException.ThrowIfNull(options);
_timeProvider = timeProvider ?? TimeProvider.System;
var builder = new KeyManagementServiceClientBuilder
{
Endpoint = string.IsNullOrWhiteSpace(options.Endpoint)
@@ -59,9 +61,10 @@ internal sealed class GcpKmsFacade : IGcpKmsFacade
_ownsClient = true;
}
public GcpKmsFacade(KeyManagementServiceClient client)
public GcpKmsFacade(KeyManagementServiceClient client, TimeProvider? timeProvider = null)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_timeProvider = timeProvider ?? TimeProvider.System;
_ownsClient = false;
}
@@ -155,16 +158,16 @@ internal sealed class GcpKmsFacade : IGcpKmsFacade
}
}
private static DateTimeOffset ToDateTimeOffsetOrUtcNow(Timestamp? timestamp)
private DateTimeOffset ToDateTimeOffsetOrUtcNow(Timestamp? timestamp)
{
if (timestamp is null)
{
return DateTimeOffset.UtcNow;
return _timeProvider.GetUtcNow();
}
if (timestamp.Seconds == 0 && timestamp.Nanos == 0)
{
return DateTimeOffset.UtcNow;
return _timeProvider.GetUtcNow();
}
return timestamp.ToDateTimeOffset();

View File

@@ -35,10 +35,12 @@ internal sealed class Pkcs11InteropFacade : IPkcs11Facade
private readonly IPkcs11Library _library;
private readonly ISlot _slot;
private readonly ConcurrentDictionary<string, IObjectAttribute[]> _attributeCache = new(StringComparer.Ordinal);
private readonly TimeProvider _timeProvider;
public Pkcs11InteropFacade(Pkcs11Options options)
public Pkcs11InteropFacade(Pkcs11Options options, TimeProvider? timeProvider = null)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_timeProvider = timeProvider ?? TimeProvider.System;
if (string.IsNullOrWhiteSpace(_options.LibraryPath))
{
throw new ArgumentException("PKCS#11 library path must be provided.", nameof(options));
@@ -66,7 +68,7 @@ internal sealed class Pkcs11InteropFacade : IPkcs11Facade
return new Pkcs11KeyDescriptor(
KeyId: label ?? privateHandle.ObjectId.ToString(),
Label: label,
CreatedAt: DateTimeOffset.UtcNow);
CreatedAt: _timeProvider.GetUtcNow());
}
public async Task<Pkcs11PublicKeyMaterial> GetPublicKeyAsync(CancellationToken cancellationToken)

View File

@@ -13,15 +13,17 @@ public sealed class Pkcs11KmsClient : IKmsClient
private readonly IPkcs11Facade _facade;
private readonly TimeSpan _metadataCacheDuration;
private readonly TimeSpan _publicKeyCacheDuration;
private readonly TimeProvider _timeProvider;
private readonly ConcurrentDictionary<string, CachedMetadata> _metadataCache = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, CachedPublicKey> _publicKeyCache = new(StringComparer.Ordinal);
private bool _disposed;
public Pkcs11KmsClient(IPkcs11Facade facade, Pkcs11Options options)
public Pkcs11KmsClient(IPkcs11Facade facade, Pkcs11Options options, TimeProvider? timeProvider = null)
{
_facade = facade ?? throw new ArgumentNullException(nameof(facade));
ArgumentNullException.ThrowIfNull(options);
_timeProvider = timeProvider ?? TimeProvider.System;
_metadataCacheDuration = options.MetadataCacheDuration;
_publicKeyCacheDuration = options.PublicKeyCacheDuration;
@@ -169,7 +171,7 @@ public sealed class Pkcs11KmsClient : IKmsClient
private async Task<CachedMetadata> GetCachedMetadataAsync(string keyId, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_metadataCache.TryGetValue(keyId, out var cached) && cached.ExpiresAt > now)
{
return cached;
@@ -183,7 +185,7 @@ public sealed class Pkcs11KmsClient : IKmsClient
private async Task<CachedPublicKey> GetCachedPublicKeyAsync(string keyId, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
if (_publicKeyCache.TryGetValue(keyId, out var cached) && cached.ExpiresAt > now)
{
return cached;

View File

@@ -62,6 +62,7 @@ public sealed class PolicySimulationSmokeRunner
new NullPolicySnapshotRepository(),
new NullPolicyAuditRepository(),
timeProvider,
null,
_loggerFactory.CreateLogger<PolicySnapshotStore>());
var previewService = new PolicyPreviewService(snapshotStore, _loggerFactory.CreateLogger<PolicyPreviewService>());

View File

@@ -45,6 +45,7 @@ public sealed class MinimalProofExporterTests
_mockChunkRepo.Object,
signer: null,
_timeProvider,
guidProvider: null,
NullLogger<MinimalProofExporter>.Instance);
// Create test data
@@ -435,6 +436,7 @@ public sealed class MinimalProofExporterTests
_mockChunkRepo.Object,
mockSigner.Object,
_timeProvider,
guidProvider: null,
NullLogger<MinimalProofExporter>.Instance);
var options = new MinimalProofExportOptions