Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography.Plugin.OpenSslGost/OpenSslGostProvider.cs
StellaOps Bot 3b96b2e3ea
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
up
2025-11-27 23:45:09 +02:00

140 lines
5.5 KiB
C#

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<string, OpenSslGostKeyEntry> entries;
private readonly ILogger<OpenSslGostProvider>? logger;
public OpenSslGostProvider(
IOptions<OpenSslGostProviderOptions>? optionsAccessor = null,
ILogger<OpenSslGostProvider>? 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<CryptoSigningKey> GetSigningKeys()
=> Array.Empty<CryptoSigningKey>();
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
{
foreach (var entry in entries.Values)
{
yield return new CryptoProviderKeyDescriptor(
Name,
entry.KeyId,
entry.AlgorithmId,
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["certificate"] = entry.Certificate?.Subject,
["curve"] = entry.PrivateKey.Parameters.Curve.FieldSize.ToString()
});
}
}
private static IReadOnlyDictionary<string, OpenSslGostKeyEntry> LoadEntries(OpenSslGostProviderOptions options)
{
var map = new Dictionary<string, OpenSslGostKeyEntry>(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);
}