using System.Collections.Concurrent; using StellaOps.VexLens.Models; namespace StellaOps.VexLens.Verification; /// /// In-memory implementation of . /// Suitable for testing and single-instance deployments. /// public sealed class InMemoryIssuerDirectory : IIssuerDirectory { private readonly ConcurrentDictionary _issuers = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _fingerprintToIssuer = new(StringComparer.OrdinalIgnoreCase); private readonly TimeProvider _timeProvider; public InMemoryIssuerDirectory(TimeProvider? timeProvider = null) { _timeProvider = timeProvider ?? TimeProvider.System; } public Task GetIssuerAsync( string issuerId, CancellationToken cancellationToken = default) { _issuers.TryGetValue(issuerId, out var issuer); return Task.FromResult(issuer); } public Task GetIssuerByKeyFingerprintAsync( string fingerprint, CancellationToken cancellationToken = default) { if (_fingerprintToIssuer.TryGetValue(fingerprint, out var issuerId)) { _issuers.TryGetValue(issuerId, out var issuer); return Task.FromResult(issuer); } return Task.FromResult(null); } public Task> ListIssuersAsync( IssuerListOptions? options = null, CancellationToken cancellationToken = default) { var query = _issuers.Values.AsEnumerable(); if (options != null) { if (options.Category.HasValue) { query = query.Where(i => i.Category == options.Category.Value); } if (options.MinimumTrustTier.HasValue) { query = query.Where(i => i.TrustTier >= options.MinimumTrustTier.Value); } if (options.Status.HasValue) { query = query.Where(i => i.Status == options.Status.Value); } if (!string.IsNullOrWhiteSpace(options.SearchTerm)) { var term = options.SearchTerm; query = query.Where(i => i.Name.Contains(term, StringComparison.OrdinalIgnoreCase) || i.IssuerId.Contains(term, StringComparison.OrdinalIgnoreCase)); } if (options.Offset.HasValue) { query = query.Skip(options.Offset.Value); } if (options.Limit.HasValue) { query = query.Take(options.Limit.Value); } } var result = query .OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .ToList(); return Task.FromResult>(result); } public Task RegisterIssuerAsync( IssuerRegistration registration, CancellationToken cancellationToken = default) { var now = _timeProvider.GetUtcNow(); var keyRecords = new List(); if (registration.InitialKeys != null) { foreach (var key in registration.InitialKeys) { keyRecords.Add(new KeyFingerprintRecord( Fingerprint: key.Fingerprint, KeyType: key.KeyType, Algorithm: key.Algorithm, Status: KeyFingerprintStatus.Active, RegisteredAt: now, ExpiresAt: key.ExpiresAt, RevokedAt: null, RevocationReason: null)); _fingerprintToIssuer[key.Fingerprint] = registration.IssuerId; } } var record = new IssuerRecord( IssuerId: registration.IssuerId, Name: registration.Name, Category: registration.Category, TrustTier: registration.TrustTier, Status: IssuerStatus.Active, KeyFingerprints: keyRecords, Metadata: registration.Metadata, RegisteredAt: now, LastUpdatedAt: null, RevokedAt: null, RevocationReason: null); _issuers[registration.IssuerId] = record; return Task.FromResult(record); } public Task RevokeIssuerAsync( string issuerId, string reason, CancellationToken cancellationToken = default) { if (!_issuers.TryGetValue(issuerId, out var current)) { return Task.FromResult(false); } var now = _timeProvider.GetUtcNow(); var updated = current with { Status = IssuerStatus.Revoked, RevokedAt = now, RevocationReason = reason, LastUpdatedAt = now }; _issuers[issuerId] = updated; // Also revoke all keys foreach (var key in current.KeyFingerprints) { _fingerprintToIssuer.TryRemove(key.Fingerprint, out _); } return Task.FromResult(true); } public Task AddKeyFingerprintAsync( string issuerId, KeyFingerprintRegistration keyRegistration, CancellationToken cancellationToken = default) { if (!_issuers.TryGetValue(issuerId, out var current)) { throw new InvalidOperationException($"Issuer '{issuerId}' not found"); } var now = _timeProvider.GetUtcNow(); var newKey = new KeyFingerprintRecord( Fingerprint: keyRegistration.Fingerprint, KeyType: keyRegistration.KeyType, Algorithm: keyRegistration.Algorithm, Status: KeyFingerprintStatus.Active, RegisteredAt: now, ExpiresAt: keyRegistration.ExpiresAt, RevokedAt: null, RevocationReason: null); var updatedKeys = current.KeyFingerprints.Append(newKey).ToList(); var updated = current with { KeyFingerprints = updatedKeys, LastUpdatedAt = now }; _issuers[issuerId] = updated; _fingerprintToIssuer[keyRegistration.Fingerprint] = issuerId; return Task.FromResult(updated); } public Task RevokeKeyFingerprintAsync( string issuerId, string fingerprint, string reason, CancellationToken cancellationToken = default) { if (!_issuers.TryGetValue(issuerId, out var current)) { return Task.FromResult(false); } var keyIndex = current.KeyFingerprints .Select((k, i) => (k, i)) .FirstOrDefault(x => x.k.Fingerprint == fingerprint); if (keyIndex.k == null) { return Task.FromResult(false); } var now = _timeProvider.GetUtcNow(); var revokedKey = keyIndex.k with { Status = KeyFingerprintStatus.Revoked, RevokedAt = now, RevocationReason = reason }; var updatedKeys = current.KeyFingerprints.ToList(); updatedKeys[keyIndex.i] = revokedKey; var updated = current with { KeyFingerprints = updatedKeys, LastUpdatedAt = now }; _issuers[issuerId] = updated; _fingerprintToIssuer.TryRemove(fingerprint, out _); return Task.FromResult(true); } public Task ValidateTrustAsync( string issuerId, string? keyFingerprint, CancellationToken cancellationToken = default) { var warnings = new List(); if (!_issuers.TryGetValue(issuerId, out var issuer)) { return Task.FromResult(new IssuerTrustValidation( IsTrusted: false, EffectiveTrustTier: TrustTier.Unknown, IssuerStatus: IssuerTrustStatus.NotRegistered, KeyStatus: null, Warnings: ["Issuer is not registered in the directory"])); } var issuerStatus = issuer.Status switch { IssuerStatus.Active => IssuerTrustStatus.Trusted, IssuerStatus.Suspended => IssuerTrustStatus.Suspended, IssuerStatus.Revoked => IssuerTrustStatus.Revoked, _ => IssuerTrustStatus.NotRegistered }; if (issuerStatus != IssuerTrustStatus.Trusted) { return Task.FromResult(new IssuerTrustValidation( IsTrusted: false, EffectiveTrustTier: TrustTier.Untrusted, IssuerStatus: issuerStatus, KeyStatus: null, Warnings: [$"Issuer status is {issuer.Status}"])); } KeyTrustStatus? keyStatus = null; if (!string.IsNullOrWhiteSpace(keyFingerprint)) { var key = issuer.KeyFingerprints .FirstOrDefault(k => k.Fingerprint.Equals(keyFingerprint, StringComparison.OrdinalIgnoreCase)); if (key == null) { keyStatus = KeyTrustStatus.NotRegistered; warnings.Add("Key fingerprint is not registered for this issuer"); } else if (key.Status == KeyFingerprintStatus.Revoked) { keyStatus = KeyTrustStatus.Revoked; warnings.Add($"Key was revoked: {key.RevocationReason}"); } else if (key.ExpiresAt.HasValue && key.ExpiresAt.Value < _timeProvider.GetUtcNow()) { keyStatus = KeyTrustStatus.Expired; warnings.Add($"Key expired on {key.ExpiresAt.Value:O}"); } else { keyStatus = KeyTrustStatus.Valid; } } var isTrusted = issuerStatus == IssuerTrustStatus.Trusted && (keyStatus == null || keyStatus == KeyTrustStatus.Valid); var effectiveTier = isTrusted ? issuer.TrustTier : TrustTier.Untrusted; return Task.FromResult(new IssuerTrustValidation( IsTrusted: isTrusted, EffectiveTrustTier: effectiveTier, IssuerStatus: issuerStatus, KeyStatus: keyStatus, Warnings: warnings)); } }