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:
master
2025-11-09 21:59:57 +02:00
parent 75c2bcafce
commit cef4cb2c5a
486 changed files with 32952 additions and 801 deletions

View File

@@ -66,5 +66,5 @@ internal static class CryptoProCertificateResolver
}
private static string Normalize(string value)
=> value.Replace(" ", string.Empty, StringComparison.Ordinal).ToUpperInvariant(CultureInfo.InvariantCulture);
=> value.Replace(" ", string.Empty, StringComparison.Ordinal).ToUpperInvariant();
}

View File

@@ -17,9 +17,13 @@ public static class CryptoProCryptoServiceCollectionExtensions
services.Configure(configure);
}
if (!OperatingSystem.IsWindows())
{
return services;
}
services.TryAddEnumerable(
ServiceDescriptor.Singleton<StellaOps.Cryptography.ICryptoProvider, CryptoProGostCryptoProvider>());
return services;
}
}

View File

@@ -2,10 +2,12 @@ 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<CryptoProGostCryptoProvider>? logger;
@@ -26,7 +28,9 @@ public sealed class CryptoProGostCryptoProvider : ICryptoProvider, ICryptoProvid
key.Algorithm,
certificate,
key.ProviderName,
key.ContainerName);
key.ContainerName,
key.UseMachineKeyStore,
key.SignatureFormat);
map[key.KeyId] = entry;
}

View File

@@ -9,13 +9,17 @@ internal sealed class CryptoProGostKeyEntry
string algorithmId,
X509Certificate2 certificate,
string providerName,
string? containerName)
string? containerName,
bool useMachineKeyStore,
GostSignatureFormat signatureFormat)
{
KeyId = keyId;
AlgorithmId = algorithmId;
Certificate = certificate;
ProviderName = providerName;
ContainerName = containerName;
UseMachineKeyStore = useMachineKeyStore;
SignatureFormat = signatureFormat;
}
public string KeyId { get; }
@@ -28,5 +32,11 @@ internal sealed class CryptoProGostKeyEntry
public string? ContainerName { get; }
public bool UseMachineKeyStore { get; }
public GostSignatureFormat SignatureFormat { get; }
public bool Use256 => string.Equals(AlgorithmId, SignatureAlgorithms.GostR3410_2012_256, StringComparison.OrdinalIgnoreCase);
public int CoordinateSize => Use256 ? 32 : 64;
}

View File

@@ -11,6 +11,11 @@ public sealed class CryptoProGostKeyOptions
public string Algorithm { get; set; } = SignatureAlgorithms.GostR3410_2012_256;
/// <summary>
/// Wire format emitted by the signer. Defaults to DER (ASN.1 sequence). Set to Raw for (s || r).
/// </summary>
public GostSignatureFormat SignatureFormat { get; set; } = GostSignatureFormat.Der;
/// <summary>
/// Optional CryptoPro provider name (defaults to standard CSP).
/// </summary>
@@ -21,6 +26,11 @@ public sealed class CryptoProGostKeyOptions
/// </summary>
public string? ContainerName { get; set; }
/// <summary>
/// Set to true when the container lives in the machine key store.
/// </summary>
public bool UseMachineKeyStore { get; set; }
/// <summary>
/// Thumbprint of the certificate that owns the CryptoPro private key.
/// </summary>
@@ -45,6 +55,8 @@ public sealed class CryptoProGostKeyOptions
CertificateThumbprint = CertificateThumbprint,
SubjectName = SubjectName,
CertificateStoreLocation = CertificateStoreLocation,
CertificateStoreName = CertificateStoreName
CertificateStoreName = CertificateStoreName,
UseMachineKeyStore = UseMachineKeyStore,
SignatureFormat = SignatureFormat
};
}

View File

@@ -1,11 +1,19 @@
using System;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using GostCryptography.Gost_3410;
using GostCryptography.Base;
using GostCryptography.Config;
using GostCryptography.Gost_R3410;
using GostCryptography.Reflection;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.CryptoPro;
[SupportedOSPlatform("windows")]
internal sealed class CryptoProGostSigner : ICryptoSigner
{
private readonly CryptoProGostKeyEntry entry;
@@ -27,9 +35,11 @@ internal sealed class CryptoProGostSigner : ICryptoSigner
? GostDigestUtilities.ComputeDigest(data.Span, use256: true)
: GostDigestUtilities.ComputeDigest(data.Span, use256: false);
using var provider = CreateProvider();
var signature = provider.SignHash(digest);
return ValueTask.FromResult(signature);
using var algorithm = CreateAlgorithm(forVerification: false);
var formatter = new GostSignatureFormatter(algorithm);
var signature = formatter.CreateSignature(digest);
var normalized = NormalizeSignatureForOutput(signature);
return ValueTask.FromResult(normalized);
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
@@ -40,8 +50,10 @@ internal sealed class CryptoProGostSigner : ICryptoSigner
? GostDigestUtilities.ComputeDigest(data.Span, use256: true)
: GostDigestUtilities.ComputeDigest(data.Span, use256: false);
using var provider = CreateProvider();
var valid = provider.VerifyHash(digest, signature.ToArray());
using var algorithm = CreateAlgorithm(forVerification: true);
var deformatter = new GostSignatureDeformatter(algorithm);
var derSignature = EnsureDerSignature(signature.Span);
var valid = deformatter.VerifySignature(digest, derSignature);
return ValueTask.FromResult(valid);
}
@@ -63,13 +75,89 @@ internal sealed class CryptoProGostSigner : ICryptoSigner
return jwk;
}
private Gost3410CryptoServiceProvider CreateProvider()
private GostAsymmetricAlgorithm CreateAlgorithm(bool forVerification)
{
if (!string.IsNullOrWhiteSpace(entry.ContainerName))
if (!forVerification && !string.IsNullOrWhiteSpace(entry.ContainerName))
{
return new Gost3410CryptoServiceProvider(entry.ProviderName, entry.ContainerName);
return entry.Use256
? new Gost_R3410_2012_256_AsymmetricAlgorithm(CreateCspParameters())
: new Gost_R3410_2012_512_AsymmetricAlgorithm(CreateCspParameters());
}
return new Gost3410CryptoServiceProvider(entry.Certificate);
var algorithm = forVerification
? entry.Certificate.GetPublicKeyAlgorithm()
: entry.Certificate.GetPrivateKeyAlgorithm();
if (algorithm is GostAsymmetricAlgorithm gost)
{
return gost;
}
throw new InvalidOperationException("Certificate does not expose a GOST private key.");
}
private CspParameters CreateCspParameters()
{
var providerType = entry.Use256 ? ProviderType.CryptoPro_2012_512 : ProviderType.CryptoPro_2012_1024;
var flags = CspProviderFlags.UseExistingKey;
if (entry.UseMachineKeyStore)
{
flags |= CspProviderFlags.UseMachineKeyStore;
}
return new CspParameters(providerType.ToInt(), entry.ProviderName, entry.ContainerName)
{
Flags = flags,
KeyNumber = (int)KeyNumber.Signature
};
}
private byte[] NormalizeSignatureForOutput(byte[] signature)
{
var coordinateLength = entry.CoordinateSize;
if (entry.SignatureFormat == GostSignatureFormat.Raw)
{
if (GostSignatureEncoding.IsDer(signature))
{
return GostSignatureEncoding.ToRaw(signature, coordinateLength);
}
if (signature.Length == coordinateLength * 2)
{
return signature;
}
throw new CryptographicException("Unexpected signature format returned by CryptoPro.");
}
if (GostSignatureEncoding.IsDer(signature))
{
return signature;
}
if (signature.Length == coordinateLength * 2)
{
return GostSignatureEncoding.ToDer(signature, coordinateLength);
}
throw new CryptographicException("Unexpected signature format returned by CryptoPro.");
}
private byte[] EnsureDerSignature(ReadOnlySpan<byte> signature)
{
var coordinateLength = entry.CoordinateSize;
if (GostSignatureEncoding.IsDer(signature))
{
return signature.ToArray();
}
if (signature.Length == coordinateLength * 2)
{
return GostSignatureEncoding.ToDer(signature, coordinateLength);
}
throw new CryptographicException("Signature payload is neither DER nor raw GOST format.");
}
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Cryptography.Tests")]

View File

@@ -9,7 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GostCryptography" Version="2.0.11" />
<PackageReference Include="IT.GostCryptography" Version="6.0.0.1" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />