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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("StellaOps.Cryptography.Tests")]
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user