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; public CryptoProviderRegistry( IEnumerable providers, IEnumerable? preferredProviderOrder = 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); preferredOrder = preferredProviderOrder? .Where(name => providersByName.ContainsKey(name)) .Select(name => providersByName[name].Name) .ToArray() ?? Array.Empty(); 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)) { return provider; } } 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); return new CryptoSignerResolution(signer, hinted.Name); } var provider = ResolveOrThrow(capability, algorithmId); var resolved = provider.GetSigner(algorithmId, keyReference); return new CryptoSignerResolution(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; } } } }