docs re-org, audit fixes, build fixes
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user