Last active
July 16, 2022 13:22
-
-
Save Zubastic/986dc8af9a0221671e495929dd8162b0 to your computer and use it in GitHub Desktop.
Crypto pro signing with GOST encoding and certificate chain
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ApInteropEDSManager : CAdESManager | |
{ | |
#region Interop | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CryptoAPIBlob | |
{ | |
public int cbData; | |
public IntPtr pbData; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CryptAlgorithmIdentifier | |
{ | |
[MarshalAs(UnmanagedType.LPStr)] public string pszObjId; | |
public CryptoAPIBlob Parameters; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CryptSignMessagePara | |
{ | |
public int cbSize; | |
public int dwMsgEncodingType; | |
public IntPtr pSigningCert; | |
public CryptAlgorithmIdentifier HashAlgorithm; | |
public IntPtr pvHashAuxInfo; | |
public int cMsgCert; | |
public IntPtr rgpMsgCert; | |
public int cMsgCrl; | |
public IntPtr rgpMsgCrl; | |
public int cAuthAttr; | |
public IntPtr rgAuthAttr; | |
public int cUnauthAttr; | |
public IntPtr rgUnauthAttr; | |
public int dwFlags; | |
public int dwInnerContentType; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CryptVerifyMessagePara | |
{ | |
public int size; | |
public int encoding; | |
public IntPtr cryptoProvider; | |
public IntPtr getSignerCertificateCallback; | |
public IntPtr callbackCookie; | |
} | |
[Flags] | |
public enum CertChainFlags | |
{ | |
None = 0x00000000, | |
CertChainRevocationCheckEndCert = 0x10000000, | |
CertChainRevocationCheckChain = 0x20000000, | |
CertChainRevocationCheckChainExcludeRoot = 0x40000000, | |
CertChainRevocationCheckCacheOnly = unchecked((int)0x80000000) | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertEnhkeyUsage | |
{ | |
public int cUsageIdentifier; | |
public IntPtr rgpszUsageIdentifier; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertUsageMatch | |
{ | |
public int dwType; | |
public CertEnhkeyUsage Usage; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertChainPara | |
{ | |
public int cbSize; | |
public CertUsageMatch RequestedUsage; | |
public CertUsageMatch RequestedIssuancePolicy; | |
public int dwUrlRetrievalTimeout; | |
public int fCheckRevocationFreshnessTime; | |
public int dwRevocationFreshnessTime; | |
public IntPtr pftCacheResync; | |
public IntPtr pStrongSignPara; | |
public int dwStrongSignFlags; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertTrustStatus | |
{ | |
public int dwErrorStatus; | |
public int dwInfoStatus; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertChainContext | |
{ | |
public int cbSize; | |
public CertTrustStatus TrustStatus; | |
public int cChain; | |
public IntPtr rgpChain; | |
public int cLowerQualityChainContext; | |
public IntPtr rgpLowerQualityChainContext; | |
public bool fHasRevocationFreshnessTime; | |
public int dwRevocationFreshnessTime; | |
public int dwCreateFlags; | |
public Guid ChainId; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertSimpleChain | |
{ | |
public int cbSize; | |
public CertTrustStatus TrustStatus; | |
public int cElement; | |
public IntPtr rgpElement; | |
public IntPtr pTrustListInfo; | |
public bool fHasRevocationFreshnessTime; | |
public int dwRevocationFreshnessTime; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct CertChainElement | |
{ | |
public int cbSize; | |
public IntPtr pCertContext; | |
public CertTrustStatus TrustStatus; | |
public IntPtr pRevocationInfo; | |
public IntPtr pIssuanceUsage; | |
public IntPtr pApplicationUsage; | |
public IntPtr pwszExtendedErrorInfo; | |
} | |
public const int CertTrustNoError = 0x00000000; | |
public const int CertTrustIsNotTimeValid = 0x00000001; | |
public const int CertTrustIsRevoked = 0x00000004; | |
public const int CertTrustIsNotSignatureValid = 0x00000008; | |
public const int CertTrustIsNotValidForUsage = 0x00000010; | |
public const int CertTrustIsUntrustedRoot = 0x00000020; | |
public const int CertTrustRevocationStatusUnknown = 0x00000040; | |
public const int CertTrustIsCyclic = 0x00000080; | |
public const int CertTrustInvalidExtension = 0x00000100; | |
public const int CertTrustInvalidPolicyConstraints = 0x00000200; | |
public const int CertTrustInvalidBasicConstraints = 0x00000400; | |
public const int CertTrustInvalidNameConstraints = 0x00000800; | |
public const int CertTrustHasNotSupportedNameConstraint = 0x00001000; | |
public const int CertTrustHasNotDefinedNameConstraint = 0x00002000; | |
public const int CertTrustHasNotPermittedNameConstraint = 0x00004000; | |
public const int CertTrustHasExcludedNameConstraint = 0x00008000; | |
public const int CertTrustIsOfflineRevocation = 0x01000000; | |
public const int CertTrustNoIssuanceChainPolicy = 0x02000000; | |
public const int CertTrustIsExplicitDistrust = 0x04000000; | |
public const int CertTrustHasNotSupportedCriticalExt = 0x08000000; | |
public const int CertTrustIsPartialChain = 0x00010000; | |
public const int CertTrustCtlIsNotTimeValid = 0x00020000; | |
public const int CertTrustCtlIsNotSignatureValid = 0x00040000; | |
public const int CertTrustCtlIsNotValidForUsage = 0x00080000; | |
public const int CertTrustHasExactMatchIssuer = 0x00000001; | |
public const int CertTrustHasKeyMatchIssuer = 0x00000002; | |
public const int CertTrustHasNameMatchIssuer = 0x00000004; | |
public const int CertTrustIsSelfSigned = 0x00000008; | |
public const int CertTrustHasPreferredIssuer = 0x00000100; | |
public const int CertTrustHasIssuanceChainPolicy = 0x00000200; | |
public const int CertTrustHasValidNameConstraints = 0x00000400; | |
public const int CertTrustIsPeerTrusted = 0x00000800; | |
public const int CertTrustHasCrlValidityExtended = 0x00001000; | |
public const int CertTrustIsFromExclusiveTrustStore = 0x00002000; | |
public const int CertTrustIsComplexChain = 0x00010000; | |
private const int Pkcs7AsnEncoding = 0x00010000; | |
private const int X509AsnEncoding = 0x00000001; | |
private const int CryptEncoding = Pkcs7AsnEncoding | X509AsnEncoding; | |
private const string SzOidOiwsecGost = "1.2.643.2.2.9"; | |
private const int CertStoreProvSystemW = 10; | |
private const int CertStoreProvSystem = CertStoreProvSystemW; | |
private const int CertSystemStoreCurrentUserID = 1; | |
private const int CertSystemStoreLocationShift = 16; | |
private const int CertSystemStoreCurrentUser = CertSystemStoreCurrentUserID << CertSystemStoreLocationShift; | |
private const string CertPersonalStoreName = "My"; | |
private const int CertCompareShift = 16; | |
private const int CertCompareSHA1Hash = 1; | |
private const int CertFindSHA1Hash = CertCompareSHA1Hash << CertCompareShift; | |
private const int CertFindHash = CertFindSHA1Hash; | |
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern IntPtr CertOpenStore( | |
int lpszStoreProvider, | |
int dwMsgAndCertEncodingType, | |
IntPtr hCryptProv, | |
int dwFlags, | |
string pvPara); | |
[DllImport("Crypt32.dll", SetLastError = true)] | |
private static extern bool CryptSignMessage( | |
ref CryptSignMessagePara pSignPara, | |
bool fDetachedSignature, | |
int cToBeSigned, | |
IntPtr[] rgpbToBeSigned, | |
int[] rgcbToBeSigned, | |
byte[] pbSignedBlob, | |
ref int pcbSignedBlob); | |
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern IntPtr CertFindCertificateInStore( | |
IntPtr hCertStore, | |
int dwCertEncodingType, | |
int dwFindFlags, | |
int dwFindType, | |
IntPtr pvFindPara, | |
IntPtr pPrevCertContext); | |
[DllImport("Crypt32.dll", SetLastError = true)] | |
private static extern bool CryptVerifyMessageSignature( | |
ref CryptVerifyMessagePara pVerifyPara, | |
int dwSignerIndex, | |
byte[] pbSignedBlob, | |
int cbSignedBlob, | |
byte[] pbDecoded, | |
ref int pcbDecoded, | |
IntPtr ppSignerCert); | |
[DllImport("Crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool CertGetCertificateChain( | |
IntPtr hChainEngine, | |
IntPtr pCertContext, | |
IntPtr pTime, | |
IntPtr hStore, | |
[In] ref CertChainPara pChainPara, | |
CertChainFlags dwFlags, | |
IntPtr pvReserved, | |
out IntPtr ppChainContext); | |
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern bool CertFreeCertificateContext(IntPtr pCertContext); | |
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern void CertFreeCertificateChain(IntPtr pChainContext); | |
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern bool CertCloseStore(IntPtr hCertStore, int dwFlags); | |
#endregion | |
#region Base overrides | |
/// <inheritdoc /> | |
public override async Task<SignatureData> SignDocumentAsync( | |
byte[] certificate, | |
ISignatureFile file, | |
CancellationToken cancellationToken = default) | |
{ | |
var signingTime = DateTime.UtcNow; | |
var settingsCard = (await this.CardCache.Cards.GetAsync(SignatureHelper.SignatureSettingsType, cancellationToken).ConfigureAwait(false)).GetValue(); | |
var (digestOid, encryptOid, _) = SignatureHelper.GetDigestAndEcryptOid(settingsCard, certificate); | |
var toBeSigned = await this.GetToBeSignedDocumentAsync(certificate, file, signingTime, digestOid, encryptOid, cancellationToken).ConfigureAwait(false); | |
var signatureValue = await this.GenerateSignatureAsync(certificate, new MemorySignatureFile(toBeSigned), digestOid, cancellationToken).ConfigureAwait(false); | |
return new SignatureData(signatureValue); | |
} | |
/// <inheritdoc /> | |
public override async ValueTask<byte[]> GenerateSignatureAsync( | |
byte[] certificate, | |
ISignatureFile file, | |
string hashAlgoOid, | |
CancellationToken cancellationToken = default) | |
{ | |
var message = new IntPtr[1]; | |
var certs = new List<IntPtr>(); | |
var ppChainContext = IntPtr.Zero; | |
var hStoreHandle = IntPtr.Zero; | |
var pSignerCert = IntPtr.Zero; | |
var pbData = IntPtr.Zero; | |
var hashBlobPtr = IntPtr.Zero; | |
try | |
{ | |
var cert = new X509Certificate2(certificate); | |
var hash = cert.GetCertHash(); | |
pbData = Marshal.AllocHGlobal(hash.Length); | |
Marshal.Copy(hash, 0, pbData, hash.Length); | |
var hashBlob = new CryptoAPIBlob | |
{ | |
pbData = pbData, | |
cbData = hash.Length | |
}; | |
hashBlobPtr = Marshal.AllocHGlobal(Marshal.SizeOf(hashBlob)); | |
Marshal.StructureToPtr(hashBlob, hashBlobPtr, false); | |
hStoreHandle = CertOpenStore( | |
CertStoreProvSystem, | |
0, | |
IntPtr.Zero, | |
CertSystemStoreCurrentUser, | |
CertPersonalStoreName); | |
if (hStoreHandle == IntPtr.Zero) | |
{ | |
throw new CryptographicException("Ошибка получения сертификата.", new Win32Exception(Marshal.GetLastWin32Error())); | |
} | |
pSignerCert = CertFindCertificateInStore( | |
hStoreHandle, | |
CryptEncoding, | |
0, | |
CertFindHash, | |
hashBlobPtr, | |
IntPtr.Zero); | |
if (pSignerCert == IntPtr.Zero) | |
{ | |
throw new CryptographicException("Ошибка получения сертификата.", new Win32Exception(Marshal.GetLastWin32Error())); | |
} | |
var chainPara = new CertChainPara { cbSize = Marshal.SizeOf<CertChainPara>() }; | |
if (!CertGetCertificateChain( | |
IntPtr.Zero, // use the default chain engine | |
pSignerCert, // pointer to the end certificate | |
IntPtr.Zero, // use the default time | |
IntPtr.Zero, // search no additional stores | |
ref chainPara, | |
CertChainFlags.None, // no revocation check | |
IntPtr.Zero, // currently reserved | |
out ppChainContext)) // return a pointer to the chain created | |
{ | |
throw new CryptographicException("Ошибка получения цепочки.", new Win32Exception(Marshal.GetLastWin32Error())); | |
} | |
var pChainContext = (CertChainContext)Marshal.PtrToStructure(ppChainContext, typeof(CertChainContext)); | |
var contextErrorMessage = GetErrorInfo(pChainContext.TrustStatus.dwErrorStatus); | |
if (!string.IsNullOrWhiteSpace(contextErrorMessage)) | |
{ | |
throw new CryptographicException(contextErrorMessage + " (" + GetInfo(pChainContext.TrustStatus.dwInfoStatus) + ")"); | |
} | |
var rgpChain = (CertSimpleChain)Marshal.PtrToStructure(Marshal.ReadIntPtr(pChainContext.rgpChain), typeof(CertSimpleChain)); | |
var chainErrorMessage = GetErrorInfo(rgpChain.TrustStatus.dwErrorStatus); | |
if (!string.IsNullOrWhiteSpace(chainErrorMessage)) | |
{ | |
throw new CryptographicException(chainErrorMessage + " (" + GetInfo(rgpChain.TrustStatus.dwInfoStatus) + ")"); | |
} | |
for (var c = 0; c < rgpChain.cElement && c < 8; c++) | |
{ | |
var elementIntPtr = Marshal.ReadIntPtr(rgpChain.rgpElement + c * Marshal.SizeOf(typeof(IntPtr))); | |
var rgpElement = (CertChainElement)Marshal.PtrToStructure(elementIntPtr, typeof(CertChainElement)); | |
certs.Add(rgpElement.pCertContext); | |
} | |
var content = await file.GetBytesAsync(cancellationToken).ConfigureAwait(false); | |
message[0] = Marshal.AllocHGlobal(content.Length); | |
Marshal.Copy(content, 0, message[0], content.Length); | |
var messageSize = new int[1]; | |
messageSize[0] = content.Length; | |
var sigParams = new CryptSignMessagePara(); | |
sigParams.cbSize = Marshal.SizeOf(sigParams); | |
sigParams.dwMsgEncodingType = CryptEncoding; | |
sigParams.pSigningCert = pSignerCert; | |
sigParams.HashAlgorithm.pszObjId = cert.SignatureAlgorithm.Value ?? SzOidOiwsecGost; | |
sigParams.HashAlgorithm.Parameters.pbData = IntPtr.Zero; | |
sigParams.HashAlgorithm.Parameters.cbData = 0; | |
sigParams.pvHashAuxInfo = IntPtr.Zero; | |
sigParams.cMsgCert = certs.Count; | |
var arr = certs.ToArray(); | |
var gc = GCHandle.Alloc(arr, GCHandleType.Pinned); | |
sigParams.rgpMsgCert = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0); | |
gc.Free(); | |
sigParams.cMsgCrl = 0; | |
sigParams.rgpMsgCrl = IntPtr.Zero; | |
sigParams.cAuthAttr = 0; | |
sigParams.rgAuthAttr = IntPtr.Zero; | |
sigParams.cUnauthAttr = 0; | |
sigParams.rgUnauthAttr = IntPtr.Zero; | |
sigParams.dwFlags = 0; | |
sigParams.dwInnerContentType = 0; | |
var cbSignedMessageBlob = 0; | |
if (!CryptSignMessage( | |
ref sigParams, // Signature parameters | |
false, // Detached | |
1, // Number of messages | |
message, // Messages to be signed | |
messageSize, // Size of messages | |
null, // Buffer for signed message | |
ref cbSignedMessageBlob)) // Size of buffer | |
{ | |
throw new CryptographicException("Ошибка подписания.", new Win32Exception(Marshal.GetLastWin32Error())); | |
} | |
var pbSignedMessageBlob = new byte[cbSignedMessageBlob]; | |
if (!CryptSignMessage( | |
ref sigParams, // Signature parameters | |
false, // Detached | |
1, // Number of messages | |
message, // Messages to be signed | |
messageSize, // Size of messages | |
pbSignedMessageBlob, // Buffer for signed message | |
ref cbSignedMessageBlob)) // Size of buffer | |
{ | |
throw new CryptographicException("Ошибка подписания.", new Win32Exception(Marshal.GetLastWin32Error())); | |
} | |
return pbSignedMessageBlob; | |
} | |
finally | |
{ | |
if (message[0] != IntPtr.Zero) | |
{ | |
Marshal.FreeHGlobal(message[0]); | |
} | |
foreach (var cert in certs.Where(cert => cert != IntPtr.Zero)) | |
{ | |
CertFreeCertificateContext(cert); | |
} | |
if (pSignerCert != IntPtr.Zero) | |
{ | |
CertFreeCertificateContext(pSignerCert); | |
} | |
if (ppChainContext != IntPtr.Zero) | |
{ | |
CertFreeCertificateChain(ppChainContext); | |
} | |
if (hStoreHandle != IntPtr.Zero) | |
{ | |
CertCloseStore(hStoreHandle, 0); | |
} | |
if (pbData != IntPtr.Zero) | |
{ | |
Marshal.FreeHGlobal(pbData); | |
} | |
if (hashBlobPtr != IntPtr.Zero) | |
{ | |
Marshal.FreeHGlobal(hashBlobPtr); | |
} | |
} | |
} | |
/// <inheritdoc /> | |
public override async ValueTask<(bool success, string errorText)> VerifySignatureAsync( | |
byte[] encodedSignature, | |
ISignatureFile file, | |
CancellationToken cancellationToken = default) | |
{ | |
var errorText = string.Empty; | |
var verifyParams = new CryptVerifyMessagePara(); | |
verifyParams.size = Marshal.SizeOf(verifyParams); | |
verifyParams.encoding = CryptEncoding; | |
verifyParams.cryptoProvider = IntPtr.Zero; | |
verifyParams.getSignerCertificateCallback = IntPtr.Zero; | |
verifyParams.callbackCookie = IntPtr.Zero; | |
var cbDecodedMessageBlob = 0; | |
var result = CryptVerifyMessageSignature( | |
ref verifyParams, // Verify parameters. | |
0, // Signer index. | |
encodedSignature, // Pointer to signed BLOB. | |
encodedSignature.Length, // Size of signed BLOB. | |
null, // Buffer for decoded message. | |
ref cbDecodedMessageBlob, // Size of buffer. | |
IntPtr.Zero); // Pointer to signer certificate. | |
if (!result) | |
{ | |
errorText = "Неправильная подпись. " + new Win32Exception(Marshal.GetLastWin32Error()).Message; | |
return (false, errorText); | |
} | |
var pbDecodedMessageBlob = new byte[cbDecodedMessageBlob]; | |
result = CryptVerifyMessageSignature( | |
ref verifyParams, // Verify parameters. | |
0, // Signer index. | |
encodedSignature, // Pointer to signed BLOB. | |
encodedSignature.Length, // Size of signed BLOB. | |
pbDecodedMessageBlob, // Buffer for decoded message. | |
ref cbDecodedMessageBlob, // Size of buffer. | |
IntPtr.Zero); // Pointer to signer certificate. | |
if (!result) | |
{ | |
errorText = "Неправильная подпись. " + new Win32Exception(Marshal.GetLastWin32Error()).Message; | |
return (false, errorText); | |
} | |
return (true, errorText); | |
} | |
#endregion | |
#region Private Methods | |
private static string GetErrorInfo(int code) | |
{ | |
switch (code) | |
{ | |
case CertTrustNoError: | |
return null; | |
case CertTrustIsNotTimeValid: | |
return null; //"This certificate or one of the certificates in the certificate chain is not time-valid"; | |
case CertTrustIsRevoked: | |
return "Trust for this certificate or one of the certificates in the certificate chain has been revoked"; | |
case CertTrustIsNotSignatureValid: | |
return "The certificate or one of the certificates in the certificate chain does not have a valid signature"; | |
case CertTrustIsNotValidForUsage: | |
return "The certificate or certificate chain is not valid in its proposed usage"; | |
case CertTrustIsUntrustedRoot: | |
return "The certificate or certificate chain is based on an untrusted root"; | |
case CertTrustRevocationStatusUnknown: | |
return "The revocation status of the certificate or one of the certificates in the certificate chain is unknown"; | |
case CertTrustIsCyclic: | |
return "One of the certificates in the chain was issued by a certification authority that the original certificate had certified"; | |
case CertTrustIsPartialChain: | |
return "The certificate chain is not complete"; | |
case CertTrustCtlIsNotTimeValid: | |
return "A CTL used to create this chain was not time-valid"; | |
case CertTrustCtlIsNotSignatureValid: | |
return "A CTL used to create this chain did not have a valid signature"; | |
case CertTrustCtlIsNotValidForUsage: | |
return "A CTL used to create this chain is not valid for this usage"; | |
default: | |
return $"TrustStatus.dwErrorStatus = {code}"; | |
} | |
} | |
private static string GetInfo(int code) | |
{ | |
switch (code) | |
{ | |
case 0: | |
return null; | |
case CertTrustHasExactMatchIssuer: | |
return "An exact match issuer certificate has been found for this certificate"; | |
case CertTrustHasKeyMatchIssuer: | |
return "A key match issuer certificate has been found for this certificate"; | |
case CertTrustHasNameMatchIssuer: | |
return "A name match issuer certificate has been found for this certificate"; | |
case CertTrustIsSelfSigned: | |
return "This certificate is self-signed"; | |
case CertTrustIsComplexChain: | |
return "The certificate chain created is a complex chain"; | |
case CertTrustHasPreferredIssuer: | |
return "The certificate chain has a preferred issuer"; | |
case CertTrustHasIssuanceChainPolicy: | |
return "The certificate chain has issuance chain policy"; | |
case CertTrustHasValidNameConstraints: | |
return "The certificate chain valid name contraints"; | |
case CertTrustIsPeerTrusted: | |
return "The certificate chain is peer trusted"; | |
case CertTrustHasCrlValidityExtended: | |
return "The certificate chain has CRL validity extended"; | |
case CertTrustIsFromExclusiveTrustStore: | |
return "The certificate chain was found in a store specified by hExclusiveRoot or hExclusiveTrustedPeople"; | |
default: | |
return $"TrustStatus.dwInfoStatus = {code}"; | |
} | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment