Add support for ГОСТ Р 34.10 digital signatures
- Implemented the GostKeyValue class for handling public key parameters in ГОСТ Р 34.10 digital signatures. - Created the GostSignedXml class to manage XML signatures using ГОСТ 34.10, including methods for computing and checking signatures. - Developed the GostSignedXmlImpl class to encapsulate the signature computation logic and public key retrieval. - Added specific key value classes for ГОСТ Р 34.10-2001, ГОСТ Р 34.10-2012/256, and ГОСТ Р 34.10-2012/512 to support different signature algorithms. - Ensured compatibility with existing XML signature standards while integrating ГОСТ cryptography.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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 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);
|
||||
}
|
||||
Reference in New Issue
Block a user