134 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Security.Cryptography;
 | |
| 
 | |
| namespace StellaOps.Cryptography;
 | |
| 
 | |
| /// <summary>
 | |
| /// Default in-process crypto provider exposing password hashing capabilities.
 | |
| /// </summary>
 | |
| public sealed class DefaultCryptoProvider : ICryptoProvider
 | |
| {
 | |
|     private readonly ConcurrentDictionary<string, IPasswordHasher> passwordHashers;
 | |
|     private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys;
 | |
|     private static readonly HashSet<string> SupportedSigningAlgorithms = new(StringComparer.OrdinalIgnoreCase)
 | |
|     {
 | |
|         SignatureAlgorithms.Es256
 | |
|     };
 | |
| 
 | |
|     public DefaultCryptoProvider()
 | |
|     {
 | |
|         passwordHashers = new ConcurrentDictionary<string, IPasswordHasher>(StringComparer.OrdinalIgnoreCase);
 | |
|         signingKeys = new ConcurrentDictionary<string, CryptoSigningKey>(StringComparer.Ordinal);
 | |
| 
 | |
|         var argon = new Argon2idPasswordHasher();
 | |
|         var pbkdf2 = new Pbkdf2PasswordHasher();
 | |
| 
 | |
|         passwordHashers.TryAdd(PasswordHashAlgorithm.Argon2id.ToString(), argon);
 | |
|         passwordHashers.TryAdd(PasswordHashAlgorithms.Argon2id, argon);
 | |
|         passwordHashers.TryAdd(PasswordHashAlgorithm.Pbkdf2.ToString(), pbkdf2);
 | |
|         passwordHashers.TryAdd(PasswordHashAlgorithms.Pbkdf2Sha256, pbkdf2);
 | |
|     }
 | |
| 
 | |
|     public string Name => "default";
 | |
| 
 | |
|     public bool Supports(CryptoCapability capability, string algorithmId)
 | |
|     {
 | |
|         if (string.IsNullOrWhiteSpace(algorithmId))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return capability switch
 | |
|         {
 | |
|             CryptoCapability.PasswordHashing => passwordHashers.ContainsKey(algorithmId),
 | |
|             CryptoCapability.Signing or CryptoCapability.Verification => SupportedSigningAlgorithms.Contains(algorithmId),
 | |
|             _ => false
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     public IPasswordHasher GetPasswordHasher(string algorithmId)
 | |
|     {
 | |
|         if (!Supports(CryptoCapability.PasswordHashing, algorithmId))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Password hashing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
 | |
|         }
 | |
| 
 | |
|         return passwordHashers[algorithmId];
 | |
|     }
 | |
| 
 | |
|     public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
 | |
|     {
 | |
|         ArgumentNullException.ThrowIfNull(keyReference);
 | |
| 
 | |
|         if (!Supports(CryptoCapability.Signing, algorithmId))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
 | |
|         }
 | |
| 
 | |
|         if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
 | |
|         {
 | |
|             throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
 | |
|         }
 | |
| 
 | |
|         if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
 | |
|         {
 | |
|             throw new InvalidOperationException(
 | |
|                 $"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
 | |
|         }
 | |
| 
 | |
|         return EcdsaSigner.Create(signingKey);
 | |
|     }
 | |
| 
 | |
|     public void UpsertSigningKey(CryptoSigningKey signingKey)
 | |
|     {
 | |
|         ArgumentNullException.ThrowIfNull(signingKey);
 | |
|         EnsureSigningSupported(signingKey.AlgorithmId);
 | |
|         if (signingKey.Kind != CryptoSigningKeyKind.Ec)
 | |
|         {
 | |
|             throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
 | |
|         }
 | |
|         ValidateSigningKey(signingKey);
 | |
| 
 | |
|         signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
 | |
|     }
 | |
| 
 | |
|     public bool RemoveSigningKey(string keyId)
 | |
|     {
 | |
|         if (string.IsNullOrWhiteSpace(keyId))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return signingKeys.TryRemove(keyId, out _);
 | |
|     }
 | |
| 
 | |
|     public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
 | |
|         => signingKeys.Values.ToArray();
 | |
| 
 | |
|     private static void EnsureSigningSupported(string algorithmId)
 | |
|     {
 | |
|         if (!SupportedSigningAlgorithms.Contains(algorithmId))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'default'.");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static void ValidateSigningKey(CryptoSigningKey signingKey)
 | |
|     {
 | |
|         if (!string.Equals(signingKey.AlgorithmId, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Only ES256 signing keys are currently supported by provider 'default'.");
 | |
|         }
 | |
| 
 | |
|         var expected = ECCurve.NamedCurves.nistP256;
 | |
|         var curve = signingKey.PrivateParameters.Curve;
 | |
|         if (!curve.IsNamed || !string.Equals(curve.Oid.Value, expected.Oid.Value, StringComparison.Ordinal))
 | |
|         {
 | |
|             throw new InvalidOperationException("ES256 signing keys must use the NIST P-256 curve.");
 | |
|         }
 | |
|     }
 | |
| }
 |