using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace StellaOps.Cryptography; /// /// Default implementation of with deterministic provider ordering. /// public sealed class CryptoProviderRegistry : ICryptoProviderRegistry { private readonly ReadOnlyCollection providers; private readonly IReadOnlyDictionary providersByName; private readonly IReadOnlyList preferredOrder; private readonly HashSet preferredOrderSet; private readonly CryptoRegistryProfiles profiles; public CryptoProviderRegistry( IEnumerable providers, IEnumerable? preferredProviderOrder = null, CryptoRegistryProfiles? registryProfiles = null) { if (providers is null) { throw new ArgumentNullException(nameof(providers)); } var providerList = providers.ToList(); if (providerList.Count == 0) { throw new ArgumentException("At least one crypto provider must be registered.", nameof(providers)); } providersByName = providerList.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); this.providers = new ReadOnlyCollection(providerList); var baseOrder = preferredProviderOrder? .Where(name => providersByName.ContainsKey(name)) .Select(name => providersByName[name].Name) .ToArray() ?? Array.Empty(); profiles = registryProfiles ?? new CryptoRegistryProfiles(baseOrder, "default", new Dictionary>(StringComparer.OrdinalIgnoreCase) { ["default"] = baseOrder }); preferredOrder = profiles.ResolvePreferredOrder(); preferredOrderSet = new HashSet(preferredOrder, StringComparer.OrdinalIgnoreCase); } public IReadOnlyCollection Providers => providers; public bool TryResolve(string preferredProvider, out ICryptoProvider provider) { if (string.IsNullOrWhiteSpace(preferredProvider)) { provider = default!; return false; } return providersByName.TryGetValue(preferredProvider, out provider!); } public ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId) { if (string.IsNullOrWhiteSpace(algorithmId)) { throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId)); } foreach (var provider in EnumerateCandidates()) { if (provider.Supports(capability, algorithmId)) { CryptoProviderMetrics.RecordProviderResolution(provider.Name, capability, algorithmId); return provider; } } CryptoProviderMetrics.RecordProviderResolutionFailure(capability, algorithmId); throw new InvalidOperationException( $"No crypto provider is registered for capability '{capability}' and algorithm '{algorithmId}'."); } public CryptoSignerResolution ResolveSigner( CryptoCapability capability, string algorithmId, CryptoKeyReference keyReference, string? preferredProvider = null) { if (!string.IsNullOrWhiteSpace(preferredProvider) && providersByName.TryGetValue(preferredProvider!, out var hinted)) { if (!hinted.Supports(capability, algorithmId)) { throw new InvalidOperationException( $"Provider '{preferredProvider}' does not support capability '{capability}' and algorithm '{algorithmId}'."); } var signer = hinted.GetSigner(algorithmId, keyReference); CryptoProviderMetrics.RecordProviderResolution(hinted.Name, capability, algorithmId); return new CryptoSignerResolution(signer, hinted.Name); } var provider = ResolveOrThrow(capability, algorithmId); var resolved = provider.GetSigner(algorithmId, keyReference); CryptoProviderMetrics.RecordProviderResolution(provider.Name, capability, algorithmId); return new CryptoSignerResolution(resolved, provider.Name); } public CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null) { if (string.IsNullOrWhiteSpace(algorithmId)) { throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId)); } if (!string.IsNullOrWhiteSpace(preferredProvider) && providersByName.TryGetValue(preferredProvider!, out var hinted)) { if (!hinted.Supports(CryptoCapability.ContentHashing, algorithmId)) { throw new InvalidOperationException( $"Provider '{preferredProvider}' does not support content hashing with algorithm '{algorithmId}'."); } var hasher = hinted.GetHasher(algorithmId); CryptoProviderMetrics.RecordProviderResolution(hinted.Name, CryptoCapability.ContentHashing, algorithmId); return new CryptoHasherResolution(hasher, hinted.Name); } var provider = ResolveOrThrow(CryptoCapability.ContentHashing, algorithmId); var resolved = provider.GetHasher(algorithmId); CryptoProviderMetrics.RecordProviderResolution(provider.Name, CryptoCapability.ContentHashing, algorithmId); return new CryptoHasherResolution(resolved, provider.Name); } private IEnumerable EnumerateCandidates() { foreach (var name in preferredOrder) { yield return providersByName[name]; } foreach (var provider in providers) { if (!preferredOrderSet.Contains(provider.Name)) { yield return provider; } } } }