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;
}
}
}
}