using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Runtime.Versioning; using StellaOps.Cryptography; namespace StellaOps.Cryptography.Plugin.CryptoPro; [SupportedOSPlatform("windows")] public sealed class CryptoProGostCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics { private readonly ILogger? logger; private readonly IReadOnlyDictionary entries; public CryptoProGostCryptoProvider( IOptions? optionsAccessor = null, ILogger? logger = null) { this.logger = logger; var options = optionsAccessor?.Value ?? new CryptoProGostProviderOptions(); var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var key in options.Keys) { var certificate = CryptoProCertificateResolver.Resolve(key); var entry = new CryptoProGostKeyEntry( key.KeyId, key.Algorithm, certificate, key.ProviderName, key.ContainerName, key.UseMachineKeyStore, key.SignatureFormat); map[key.KeyId] = entry; } entries = map; } public string Name => "ru.cryptopro.csp"; public bool Supports(CryptoCapability capability, string algorithmId) => capability is CryptoCapability.Signing or CryptoCapability.Verification && (string.Equals(algorithmId, SignatureAlgorithms.GostR3410_2012_256, StringComparison.OrdinalIgnoreCase) || string.Equals(algorithmId, SignatureAlgorithms.GostR3410_2012_512, StringComparison.OrdinalIgnoreCase)); public IPasswordHasher GetPasswordHasher(string algorithmId) => throw new NotSupportedException("CryptoPro provider does not expose password hashing."); public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) { ArgumentNullException.ThrowIfNull(keyReference); var entry = ResolveKey(keyReference.KeyId); if (!string.Equals(entry.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( $"Signing key '{keyReference.KeyId}' is registered for algorithm '{entry.AlgorithmId}', not '{algorithmId}'."); } logger?.LogDebug("Using CryptoPro key {Key} ({Algorithm})", entry.KeyId, entry.AlgorithmId); return new CryptoProGostSigner(entry); } public void UpsertSigningKey(CryptoSigningKey signingKey) => throw new NotSupportedException("CryptoPro keys are managed externally."); public bool RemoveSigningKey(string keyId) => false; public IReadOnlyCollection GetSigningKeys() => Array.Empty(); public IEnumerable DescribeKeys() { foreach (var entry in entries.Values) { yield return new CryptoProviderKeyDescriptor( Name, entry.KeyId, entry.AlgorithmId, new Dictionary(StringComparer.OrdinalIgnoreCase) { ["provider"] = entry.ProviderName, ["container"] = entry.ContainerName, ["thumbprint"] = entry.Certificate.Thumbprint, ["subject"] = entry.Certificate.Subject }); } } private CryptoProGostKeyEntry ResolveKey(string? keyId) { if (string.IsNullOrWhiteSpace(keyId)) { throw new ArgumentException("Crypto key reference must include KeyId.", nameof(keyId)); } if (entries.TryGetValue(keyId, out var entry)) { return entry; } throw new KeyNotFoundException($"CryptoPro key '{keyId}' is not registered."); } }