115 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			115 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Collections.ObjectModel;
 | |
| using System.Linq;
 | |
| 
 | |
| namespace StellaOps.Cryptography;
 | |
| 
 | |
| /// <summary>
 | |
| /// Default implementation of <see cref="ICryptoProviderRegistry"/> with deterministic provider ordering.
 | |
| /// </summary>
 | |
| public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
 | |
| {
 | |
|     private readonly ReadOnlyCollection<ICryptoProvider> providers;
 | |
|     private readonly IReadOnlyDictionary<string, ICryptoProvider> providersByName;
 | |
|     private readonly IReadOnlyList<string> preferredOrder;
 | |
|     private readonly HashSet<string> preferredOrderSet;
 | |
| 
 | |
|     public CryptoProviderRegistry(
 | |
|         IEnumerable<ICryptoProvider> providers,
 | |
|         IEnumerable<string>? 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<ICryptoProvider>(providerList);
 | |
| 
 | |
|         preferredOrder = preferredProviderOrder?
 | |
|             .Where(name => providersByName.ContainsKey(name))
 | |
|             .Select(name => providersByName[name].Name)
 | |
|             .ToArray() ?? Array.Empty<string>();
 | |
|         preferredOrderSet = new HashSet<string>(preferredOrder, StringComparer.OrdinalIgnoreCase);
 | |
|     }
 | |
| 
 | |
|     public IReadOnlyCollection<ICryptoProvider> 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<ICryptoProvider> EnumerateCandidates()
 | |
|     {
 | |
|         foreach (var name in preferredOrder)
 | |
|         {
 | |
|             yield return providersByName[name];
 | |
|         }
 | |
| 
 | |
|         foreach (var provider in providers)
 | |
|         {
 | |
|             if (!preferredOrderSet.Contains(provider.Name))
 | |
|             {
 | |
|                 yield return provider;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |