using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; namespace StellaOps.Cryptography; /// /// Default in-process crypto provider exposing password hashing capabilities. /// public sealed class DefaultCryptoProvider : ICryptoProvider { private readonly ConcurrentDictionary passwordHashers; private readonly ConcurrentDictionary signingKeys; private static readonly HashSet SupportedSigningAlgorithms = new(StringComparer.OrdinalIgnoreCase) { SignatureAlgorithms.Es256 }; public DefaultCryptoProvider() { passwordHashers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); signingKeys = new ConcurrentDictionary(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 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."); } } }