Skip to content

Instantly share code, notes, and snippets.

@Zubastic
Last active July 16, 2022 13:22
Show Gist options
  • Save Zubastic/986dc8af9a0221671e495929dd8162b0 to your computer and use it in GitHub Desktop.
Save Zubastic/986dc8af9a0221671e495929dd8162b0 to your computer and use it in GitHub Desktop.
Crypto pro signing with GOST encoding and certificate chain
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