using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Org.BouncyCastle.Crypto.Parameters; using StellaOps.Cryptography; namespace StellaOps.Cryptography.Plugin.OpenSslGost; public sealed class OpenSslGostProvider : ICryptoProvider, ICryptoProviderDiagnostics { private readonly IReadOnlyDictionary entries; private readonly ILogger? logger; public OpenSslGostProvider( IOptions? optionsAccessor = null, ILogger? logger = null) { this.logger = logger; var options = optionsAccessor?.Value ?? new OpenSslGostProviderOptions(); entries = LoadEntries(options); } public string Name => "ru.openssl.gost"; 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("OpenSSL GOST provider does not expose password hashing."); public ICryptoHasher GetHasher(string algorithmId) => throw new NotSupportedException("OpenSSL GOST provider does not expose content hashing."); public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) { ArgumentNullException.ThrowIfNull(keyReference); if (string.IsNullOrEmpty(keyReference.KeyId)) { throw new ArgumentException("Crypto key reference must include KeyId.", nameof(keyReference)); } if (!entries.TryGetValue(keyReference.KeyId, out var entry)) { throw new KeyNotFoundException($"OpenSSL GOST key '{keyReference.KeyId}' is not registered."); } 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 OpenSSL GOST key {Key} ({Algorithm})", entry.KeyId, entry.AlgorithmId); return new OpenSslGostSigner(entry); } public void UpsertSigningKey(CryptoSigningKey signingKey) => throw new NotSupportedException("OpenSSL GOST provider uses external key material."); 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) { ["certificate"] = entry.Certificate?.Subject, ["curve"] = entry.PrivateKey.Parameters.Curve.FieldSize.ToString() }); } } private static IReadOnlyDictionary LoadEntries(OpenSslGostProviderOptions options) { var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var key in options.Keys) { ValidateKeyOptions(key); var passphrase = ResolveSecret(key.PrivateKeyPassphraseEnvVar); var privateKey = OpenSslPemLoader.LoadPrivateKey(key.PrivateKeyPath, passphrase); var certPassword = ResolveSecret(key.CertificatePasswordEnvVar); X509Certificate2? certificate; try { certificate = OpenSslPemLoader.LoadCertificate(key.CertificatePath, certPassword); } catch (Exception ex) { throw new InvalidOperationException($"Failed to load certificate for key '{key.KeyId}'.", ex); } var publicKey = OpenSslPemLoader.LoadPublicKey(privateKey, certificate); var entry = new OpenSslGostKeyEntry( key.KeyId, key.Algorithm, privateKey, publicKey, certificate, key.SignatureFormat); map[key.KeyId] = entry; } return map; } private static void ValidateKeyOptions(OpenSslGostKeyOptions key) { if (!File.Exists(key.PrivateKeyPath)) { throw new InvalidOperationException($"Private key '{key.PrivateKeyPath}' does not exist."); } if (!string.Equals(key.Algorithm, SignatureAlgorithms.GostR3410_2012_256, StringComparison.OrdinalIgnoreCase) && !string.Equals(key.Algorithm, SignatureAlgorithms.GostR3410_2012_512, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( $"Unsupported GOST algorithm '{key.Algorithm}' for key '{key.KeyId}'."); } } private static string? ResolveSecret(string? envVar) => string.IsNullOrEmpty(envVar) ? null : Environment.GetEnvironmentVariable(envVar); }