在.NET中从Authenticode签名的文件获取时间戳

22
我们需要验证二进制文件是否使用数字签名(Authenticode)正确签名。这可以通过signtool.exe轻松实现。然而,我们需要一种自动的方式来验证签名者名称和时间戳。在本地C ++中,可以使用CryptQueryObject()API来完成此操作,如此精彩的示例所示:如何从Authenticode签名的可执行文件中获取信息 但是我们生活在一个托管的世界:)因此寻找C#解决方案解决同样的问题。直接的方法是pInvoke Crypt32.dll,一切都完成了。但是,在System.Security.Cryptography.X509Certificates命名空间中有类似的托管API。X509Certificate2类似乎提供了一些信息,但没有时间戳。现在我们来到了最初的问题,如何在C#中获取数字签名的时间戳?

4
受控的 Authenticode 类有很多限制!直接使用 p/Invoke 可能更容易。 - Stephen Cleary
1
第一个原因是托管代码看起来更好。其次,在多篇MSDN文章中都有类似这样的可怕提示:注意 WinVerifyTrust 函数可在“要求”部分中列出的操作系统中使用。它可能会在随后的版本中被修改或取消。其中Windows Vista是最新支持的系统。当然,WinVerifyTrust和CryptQueryObject可以在Windows 7上运行,但由于所有这些警告,使用其他API似乎是合乎逻辑的。看来证书命名空间确实不太完善。有趣的是,现在在线的MSDN中没有出现这个警告... - SlavaGu
1
@StephenCleary 一个.NET Core的解决方案也将是一个不错的奖励。 - Aaron Hudon
8个回答

16

回到最初的问题,我找不到托管的方法,所以最终使用了以下 pInvoke:

public static bool IsTimestamped(string filename)
{
    try
    {
        int encodingType;
        int contentType;
        int formatType;
        IntPtr certStore = IntPtr.Zero;
        IntPtr cryptMsg = IntPtr.Zero;
        IntPtr context = IntPtr.Zero;

        if (!WinCrypt.CryptQueryObject(
            WinCrypt.CERT_QUERY_OBJECT_FILE,
            Marshal.StringToHGlobalUni(filename),
            WinCrypt.CERT_QUERY_CONTENT_FLAG_ALL,
            WinCrypt.CERT_QUERY_FORMAT_FLAG_ALL,
            0,
            out encodingType,
            out contentType,
            out formatType,
            ref certStore,
            ref cryptMsg,
            ref context))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        //expecting contentType=10; CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED 
        //Logger.LogInfo(string.Format("Querying file '{0}':", filename));
        //Logger.LogInfo(string.Format("  Encoding Type: {0}", encodingType));
        //Logger.LogInfo(string.Format("  Content Type: {0}", contentType));
        //Logger.LogInfo(string.Format("  Format Type: {0}", formatType));
        //Logger.LogInfo(string.Format("  Cert Store: {0}", certStore.ToInt32()));
        //Logger.LogInfo(string.Format("  Crypt Msg: {0}", cryptMsg.ToInt32()));
        //Logger.LogInfo(string.Format("  Context: {0}", context.ToInt32()));


        // Get size of the encoded message.
        int cbData = 0;
        if (!WinCrypt.CryptMsgGetParam(
            cryptMsg,
            WinCrypt.CMSG_ENCODED_MESSAGE,//Crypt32.CMSG_SIGNER_INFO_PARAM,
            0,
            IntPtr.Zero,
            ref cbData))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var vData = new byte[cbData];

        // Get the encoded message.
        if (!WinCrypt.CryptMsgGetParam(
            cryptMsg,
            WinCrypt.CMSG_ENCODED_MESSAGE,//Crypt32.CMSG_SIGNER_INFO_PARAM,
            0,
            vData,
            ref cbData))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var signedCms = new SignedCms();
        signedCms.Decode(vData);

        foreach (var signerInfo in signedCms.SignerInfos)
        {
            foreach (var unsignedAttribute in signerInfo.UnsignedAttributes)
            {
                if (unsignedAttribute.Oid.Value == WinCrypt.szOID_RSA_counterSign)
                {
                    //Note at this point we assume this counter signature is the timestamp
                    //refer to http://support.microsoft.com/kb/323809 for the origins

                    //TODO: extract timestamp value, if required
                    return true;
                }

            }
        }
    }
    catch (Exception)
    {
        // no logging
    }

    return false;
}

WinCrypt.cs文件包含以下内容:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MyNamespace.Win32
{
    static class WinCrypt
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct BLOB
        {
            public int cbData;
            public IntPtr pbData;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_ALGORITHM_IDENTIFIER
        {
            public String pszObjId;
            BLOB Parameters;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CERT_ID
        {
            public int dwIdChoice;
            public BLOB IssuerSerialNumberOrKeyIdOrHashId;
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        public struct SIGNER_SUBJECT_INFO
        {
            /// DWORD->unsigned int
            public uint cbSize;

            /// DWORD*
            public System.IntPtr pdwIndex;

            /// DWORD->unsigned int
            public uint dwSubjectChoice;

            /// SubjectChoiceUnion
            public SubjectChoiceUnion Union1;
        }

        [StructLayoutAttribute(LayoutKind.Explicit)]
        public struct SubjectChoiceUnion
        {

            /// SIGNER_FILE_INFO*
            [FieldOffsetAttribute(0)]
            public System.IntPtr pSignerFileInfo;

            /// SIGNER_BLOB_INFO*
            [FieldOffsetAttribute(0)]
            public System.IntPtr pSignerBlobInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CERT_NAME_BLOB
        {
            public uint cbData;
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
            public byte[] pbData;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_INTEGER_BLOB
        {
            public UInt32 cbData;
            public IntPtr pbData;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_ATTR_BLOB
        {
            public uint cbData;
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
            public byte[] pbData;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_ATTRIBUTE
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pszObjId;
            public uint cValue;
            [MarshalAs(UnmanagedType.LPStruct)]
            public CRYPT_ATTR_BLOB rgValue;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CMSG_SIGNER_INFO
        {
            public int dwVersion;
            private CERT_NAME_BLOB Issuer;
            CRYPT_INTEGER_BLOB SerialNumber;
            CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
            CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm;
            BLOB EncryptedHash;
            CRYPT_ATTRIBUTE[] AuthAttrs;
            CRYPT_ATTRIBUTE[] UnauthAttrs;
        }

        [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptQueryObject(
            int dwObjectType,
            IntPtr pvObject,
            int dwExpectedContentTypeFlags,
            int dwExpectedFormatTypeFlags,
            int dwFlags,
            out int pdwMsgAndCertEncodingType,
            out int pdwContentType,
            out int pdwFormatType,
            ref IntPtr phCertStore,
            ref IntPtr phMsg,
            ref IntPtr ppvContext);


        [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptMsgGetParam(
            IntPtr hCryptMsg,
            int dwParamType,
            int dwIndex,
            IntPtr pvData,
            ref int pcbData
        );

        [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptMsgGetParam(
            IntPtr hCryptMsg,
            int dwParamType,
            int dwIndex,
            [In, Out] byte[] vData,
            ref int pcbData
        );

        [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CryptDecodeObject(
          uint CertEncodingType,
          UIntPtr lpszStructType,
          byte[] pbEncoded,
          uint cbEncoded,
          uint flags,
          [In, Out] byte[] pvStructInfo,
          ref uint cbStructInfo);


        public const int CRYPT_ASN_ENCODING = 0x00000001;
        public const int CRYPT_NDR_ENCODING = 0x00000002;
        public const int X509_ASN_ENCODING = 0x00000001;
        public const int X509_NDR_ENCODING = 0x00000002;
        public const int PKCS_7_ASN_ENCODING = 0x00010000;
        public const int PKCS_7_NDR_ENCODING = 0x00020000;

        public static UIntPtr PKCS7_SIGNER_INFO = new UIntPtr(500);
        public static UIntPtr CMS_SIGNER_INFO = new UIntPtr(501);

        public static string szOID_RSA_signingTime = "1.2.840.113549.1.9.5";
        public static string szOID_RSA_counterSign = "1.2.840.113549.1.9.6";

        //+-------------------------------------------------------------------------
        //  Get parameter types and their corresponding data structure definitions.
        //--------------------------------------------------------------------------
        public const int CMSG_TYPE_PARAM = 1;
        public const int CMSG_CONTENT_PARAM = 2;
        public const int CMSG_BARE_CONTENT_PARAM = 3;
        public const int CMSG_INNER_CONTENT_TYPE_PARAM = 4;
        public const int CMSG_SIGNER_COUNT_PARAM = 5;
        public const int CMSG_SIGNER_INFO_PARAM = 6;
        public const int CMSG_SIGNER_CERT_INFO_PARAM = 7;
        public const int CMSG_SIGNER_HASH_ALGORITHM_PARAM = 8;
        public const int CMSG_SIGNER_AUTH_ATTR_PARAM = 9;
        public const int CMSG_SIGNER_UNAUTH_ATTR_PARAM = 10;
        public const int CMSG_CERT_COUNT_PARAM = 11;
        public const int CMSG_CERT_PARAM = 12;
        public const int CMSG_CRL_COUNT_PARAM = 13;
        public const int CMSG_CRL_PARAM = 14;
        public const int CMSG_ENVELOPE_ALGORITHM_PARAM = 15;
        public const int CMSG_RECIPIENT_COUNT_PARAM = 17;
        public const int CMSG_RECIPIENT_INDEX_PARAM = 18;
        public const int CMSG_RECIPIENT_INFO_PARAM = 19;
        public const int CMSG_HASH_ALGORITHM_PARAM = 20;
        public const int CMSG_HASH_DATA_PARAM = 21;
        public const int CMSG_COMPUTED_HASH_PARAM = 22;
        public const int CMSG_ENCRYPT_PARAM = 26;
        public const int CMSG_ENCRYPTED_DIGEST = 27;
        public const int CMSG_ENCODED_SIGNER = 28;
        public const int CMSG_ENCODED_MESSAGE = 29;
        public const int CMSG_VERSION_PARAM = 30;
        public const int CMSG_ATTR_CERT_COUNT_PARAM = 31;
        public const int CMSG_ATTR_CERT_PARAM = 32;
        public const int CMSG_CMS_RECIPIENT_COUNT_PARAM = 33;
        public const int CMSG_CMS_RECIPIENT_INDEX_PARAM = 34;
        public const int CMSG_CMS_RECIPIENT_ENCRYPTED_KEY_INDEX_PARAM = 35;
        public const int CMSG_CMS_RECIPIENT_INFO_PARAM = 36;
        public const int CMSG_UNPROTECTED_ATTR_PARAM = 37;
        public const int CMSG_SIGNER_CERT_ID_PARAM = 38;
        public const int CMSG_CMS_SIGNER_INFO_PARAM = 39;


        //-------------------------------------------------------------------------
        //dwObjectType for CryptQueryObject
        //-------------------------------------------------------------------------
        public const int CERT_QUERY_OBJECT_FILE = 0x00000001;
        public const int CERT_QUERY_OBJECT_BLOB = 0x00000002;

        //-------------------------------------------------------------------------
        //dwContentType for CryptQueryObject
        //-------------------------------------------------------------------------
        //encoded single certificate
        public const int CERT_QUERY_CONTENT_CERT = 1;
        //encoded single CTL
        public const int CERT_QUERY_CONTENT_CTL = 2;
        //encoded single CRL
        public const int CERT_QUERY_CONTENT_CRL = 3;
        //serialized store
        public const int CERT_QUERY_CONTENT_SERIALIZED_STORE = 4;
        //serialized single certificate
        public const int CERT_QUERY_CONTENT_SERIALIZED_CERT = 5;
        //serialized single CTL
        public const int CERT_QUERY_CONTENT_SERIALIZED_CTL = 6;
        //serialized single CRL
        public const int CERT_QUERY_CONTENT_SERIALIZED_CRL = 7;
        //a PKCS#7 signed message
        public const int CERT_QUERY_CONTENT_PKCS7_SIGNED = 8;
        //a PKCS#7 message, such as enveloped message.  But it is not a signed message,
        public const int CERT_QUERY_CONTENT_PKCS7_UNSIGNED = 9;
        //a PKCS7 signed message embedded in a file
        public const int CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED = 10;
        //an encoded PKCS#10
        public const int CERT_QUERY_CONTENT_PKCS10 = 11;
        //an encoded PKX BLOB
        public const int CERT_QUERY_CONTENT_PFX = 12;
        //an encoded CertificatePair (contains forward and/or reverse cross certs)
        public const int CERT_QUERY_CONTENT_CERT_PAIR = 13;

        //-------------------------------------------------------------------------
        //dwExpectedConentTypeFlags for CryptQueryObject
        //-------------------------------------------------------------------------
        //encoded single certificate
        public const int CERT_QUERY_CONTENT_FLAG_CERT = (1 << CERT_QUERY_CONTENT_CERT);

        //encoded single CTL
        public const int CERT_QUERY_CONTENT_FLAG_CTL = (1 << CERT_QUERY_CONTENT_CTL);

        //encoded single CRL
        public const int CERT_QUERY_CONTENT_FLAG_CRL = (1 << CERT_QUERY_CONTENT_CRL);

        //serialized store
        public const int CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE = (1 << CERT_QUERY_CONTENT_SERIALIZED_STORE);

        //serialized single certificate
        public const int CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT = (1 << CERT_QUERY_CONTENT_SERIALIZED_CERT);

        //serialized single CTL
        public const int CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL = (1 << CERT_QUERY_CONTENT_SERIALIZED_CTL);

        //serialized single CRL
        public const int CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL = (1 << CERT_QUERY_CONTENT_SERIALIZED_CRL);

        //an encoded PKCS#7 signed message
        public const int CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED = (1 << CERT_QUERY_CONTENT_PKCS7_SIGNED);

        //an encoded PKCS#7 message.  But it is not a signed message
        public const int CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED = (1 << CERT_QUERY_CONTENT_PKCS7_UNSIGNED);

        //the content includes an embedded PKCS7 signed message
        public const int CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = (1 << CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED);

        //an encoded PKCS#10
        public const int CERT_QUERY_CONTENT_FLAG_PKCS10 = (1 << CERT_QUERY_CONTENT_PKCS10);

        //an encoded PFX BLOB
        public const int CERT_QUERY_CONTENT_FLAG_PFX = (1 << CERT_QUERY_CONTENT_PFX);

        //an encoded CertificatePair (contains forward and/or reverse cross certs)
        public const int CERT_QUERY_CONTENT_FLAG_CERT_PAIR = (1 << CERT_QUERY_CONTENT_CERT_PAIR);

        //content can be any type
        public const int CERT_QUERY_CONTENT_FLAG_ALL =
            CERT_QUERY_CONTENT_FLAG_CERT |
            CERT_QUERY_CONTENT_FLAG_CTL |
            CERT_QUERY_CONTENT_FLAG_CRL |
            CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE |
            CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT |
            CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL |
            CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL |
            CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED |
            CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED |
            CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED |
            CERT_QUERY_CONTENT_FLAG_PKCS10 |
            CERT_QUERY_CONTENT_FLAG_PFX |
            CERT_QUERY_CONTENT_FLAG_CERT_PAIR;

        //-------------------------------------------------------------------------
        //dwFormatType for CryptQueryObject
        //-------------------------------------------------------------------------
        //the content is in binary format
        public const int CERT_QUERY_FORMAT_BINARY = 1;

        //the content is base64 encoded
        public const int CERT_QUERY_FORMAT_BASE64_ENCODED = 2;

        //the content is ascii hex encoded with "{ASN}" prefix
        public const int CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3;

        //-------------------------------------------------------------------------
        //dwExpectedFormatTypeFlags for CryptQueryObject
        //-------------------------------------------------------------------------
        //the content is in binary format
        public const int CERT_QUERY_FORMAT_FLAG_BINARY = (1 << CERT_QUERY_FORMAT_BINARY);

        //the content is base64 encoded
        public const int CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = (1 << CERT_QUERY_FORMAT_BASE64_ENCODED);

        //the content is ascii hex encoded with "{ASN}" prefix
        public const int CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED = (1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED);

        //the content can be of any format
        public const int CERT_QUERY_FORMAT_FLAG_ALL =
            CERT_QUERY_FORMAT_FLAG_BINARY |
            CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED |
            CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED;

    }
}

很遗憾,这只能获取“RSA”签名的时间戳(szOID_RSA_counterSign),但会错过“RFC3161”(szOID_RFC3161_counterSign)签名的时间戳。 - Klaus Gütter

8

谢谢大家,

你们帮了我很多 :)

顺便说一句:我发现获取时间戳的方法更简单了。

就是这样:

foreach (var signerInfo in signedCms.SignerInfos)
{
  foreach (var unsignedAttribute in signerInfo.UnsignedAttributes)
  {
    if (unsignedAttribute.Oid.Value == WinCrypt.szOID_RSA_counterSign)
    {
      foreach (var counterSignInfo in signerInfo.CounterSignerInfos)
      {
        foreach (var signedAttribute in counterSignInfo.SignedAttributes)
        {
          if (signedAttribute.Oid.Value == WinCrypt.szOID_RSA_signingTime)
          {
            Pkcs9SigningTime signingTime = (Pkcs9SigningTime)signedAttribute.Values[0];
            Console.Out.WriteLine("Signing Time UTC: " + signingTime.SigningTime);
          }
        }
      }
      return true;
    }
  }
}

很遗憾,这只能获取“RSA”签名的时间戳(szOID_RSA_counterSign),但会错过“RFC3161”(szOID_RFC3161_counterSign)签名的时间戳。 - Klaus Gütter

4
感谢OP的工作。我添加了实现来获取证书的实际时间戳。
foreach (var signerInfo in signedCms.SignerInfos)
            {
                foreach (var unsignedAttribute in signerInfo.UnsignedAttributes)
                {
                    if (unsignedAttribute.Oid.Value == WinCrypt.szOID_RSA_counterSign)
                    {
                        foreach (var counterSignInfo in signerInfo.CounterSignerInfos)
                        {
                            foreach (var signedAttribute in counterSignInfo.SignedAttributes)
                            {
                                if (signedAttribute.Oid.Value == WinCrypt.szOID_RSA_signingTime)
                                {                                        
                                    System.Runtime.InteropServices.ComTypes.FILETIME fileTime = new System.Runtime.InteropServices.ComTypes.FILETIME();
                                    int fileTimeSize = Marshal.SizeOf(fileTime);
                                    IntPtr fileTimePtr = Marshal.AllocCoTaskMem(fileTimeSize);
                                    Marshal.StructureToPtr(fileTime, fileTimePtr, true);

                                    byte[] buffdata = new byte[fileTimeSize];
                                    Marshal.Copy(fileTimePtr, buffdata, 0, fileTimeSize);

                                    uint buffSize = (uint)buffdata.Length;

                                    uint encoding = WinCrypt.X509_ASN_ENCODING | WinCrypt.PKCS_7_ASN_ENCODING;

                                    UIntPtr rsaSigningTime = (UIntPtr)(uint)Marshal.StringToHGlobalAnsi(WinCrypt.szOID_RSA_signingTime);

                                    byte[] pbData = signedAttribute.Values[0].RawData;                                         
                                    uint ucbData = (uint)pbData.Length;

                                    bool workie = WinCrypt.CryptDecodeObject(encoding, rsaSigningTime.ToUInt32(), pbData, ucbData, 0, buffdata, ref buffSize);

                                    if (workie)
                                    {
                                        IntPtr fileTimePtr2 = Marshal.AllocCoTaskMem(buffdata.Length);
                                        Marshal.Copy(buffdata, 0, fileTimePtr2, buffdata.Length);
                                        System.Runtime.InteropServices.ComTypes.FILETIME fileTime2 = (System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(fileTimePtr2, typeof(System.Runtime.InteropServices.ComTypes.FILETIME));

                                        long hFT2 = (((long)fileTime2.dwHighDateTime) << 32) + ((uint)fileTime2.dwLowDateTime);

                                        DateTime dte = DateTime.FromFileTime(hFT2);
                                        Console.WriteLine(dte.ToString());
                                    }
                                    else
                                    {
                                        throw new Win32Exception(Marshal.GetLastWin32Error());                                            
                                    }

                                }    
                            }

                        }                            

                        return true;
                    }

                }
            }

3

我想获取数字证书的主题,它是类似于OU类型字符串的:

  • CN = Microsoft Corporation,OU = MOPR,O = Microsoft Corporation,L = Redmond,S = Washington,C = US

我发现X509Certificate速度非常缓慢,需要将整个文件加载到内存中。我尝试读取一个800MB的修补程序文件时,随着读取,我的内存增加了800MB,而且需要超过30秒!!

我站在上面的海报的肩膀上,并设法调整上面的代码,以比使用X509对象快数百倍地获取X509Certificate2对象。

有关性能差异的详细信息,请阅读我的博客文章并查看性能差异的图像:X509Certificate object c# performance and memory issues alternative – fixed

请尝试此方法:

public static X509Certificate2 GetDigitalCertificate(string filename)
    {
        X509Certificate2 cert = null;

        int encodingType;
        int contentType;
        int formatType;
        IntPtr certStore = IntPtr.Zero;
        IntPtr cryptMsg = IntPtr.Zero;
        IntPtr context = IntPtr.Zero;

        if (!WinCrypt.CryptQueryObject(
            WinCrypt.CERT_QUERY_OBJECT_FILE,
            Marshal.StringToHGlobalUni(filename),
            (WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED
            | WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED
            | WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED), // <-- These are the attributes that makes it fast!!
            WinCrypt.CERT_QUERY_FORMAT_FLAG_ALL,
            0,
            out encodingType,
            out contentType,
            out formatType,
            ref certStore,
            ref cryptMsg,
            ref context))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Get size of the encoded message.
        int cbData = 0;
        if (!WinCrypt.CryptMsgGetParam(
            cryptMsg,
            WinCrypt.CMSG_ENCODED_MESSAGE,
            0,
            IntPtr.Zero,
            ref cbData))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var vData = new byte[cbData];

        // Get the encoded message.
        if (!WinCrypt.CryptMsgGetParam(
            cryptMsg,
            WinCrypt.CMSG_ENCODED_MESSAGE,
            0,
            vData,
            ref cbData))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var signedCms = new SignedCms();
        signedCms.Decode(vData);

        if (signedCms.SignerInfos.Count > 0)
        {
            var signerInfo = signedCms.SignerInfos[0];

            if (signerInfo.Certificate != null)
            {
                cert = signerInfo.Certificate;
            }
        }

        return cert;
    }

看起来如果你在CryptQueryObject调用上使用"WinCrypt.CERT_QUERY_CONTENT_FLAG_ALL"会遇到与X509Certificate对象相同的内存性能问题,但如果将它缩减为仅限于PKCS7内容类型,它就像是一个梦想一样执行,并且似乎给了我需要的信息。


1

鉴于你并没有收到回复,让我来给你提供一个。

如果你不介意使用第三方组件,可以看看我们SecureBlackbox产品中的TElAuthenticodeVerifier组件。使用该组件,你可以验证签名并检查时间戳。


这个类看起来很不错,谢谢。VerifySignature会在线检查证书有效性吗?顺便说一下,我已经通过调用CryptQueryObject实现了时间戳检查,所以我只是出于好奇问一下。 - SlavaGu
嗨,SlavaGu,你能和我分享一下你的解决方案吗?或者给我一些想法怎么做也可以。我也有类似的问题。谢谢。 - Ashif Nataliya

1
提供的答案对于我的SHA256签名方法没有用。我在那些嵌套的foreach循环中从未到达底部。但是,Nuget AuthenticodeExaminer似乎工作得很好。这里有一个关于单个证书和单个时间戳的示例:
        var extractor = new FileInspector(@"D:\Temp\file.exe");
        var signTime = extractor.GetSignatures().FirstOrDefault()?.TimestampSignatures.FirstOrDefault()?.TimestampDateTime?.UtcDateTime;

0

我喜欢使用SignedCms类来避免一些令人讨厌的p/invoke代码,但请注意,在某些网络情况下,SignedCms构造函数可能会阻塞很长时间---我目前正在运行的测试中看到大约15秒的阻塞。Alejandro Campos Magencio在他的MSDN博客上有一些关于此的信息,题为调用EnvelopedCms构造函数时出现大延迟


0

这里是从https://habr.com/ru/articles/426645/查找到的现代代码:

//using System.Security.Cryptography;
//using System.Security.Cryptography.Pkcs;
internal const string SigningTime = "1.2.840.113549.1.9.5";

private static DateTime? GetSignDate(SignerInfo signerInfo)
{
    DateTime? signDate = (signerInfo.SignedAttributes
        .Cast<CryptographicAttributeObject>()
        .FirstOrDefault(x => x.Oid.Value == SigningTime)
        ?.Values[0] as Pkcs9SigningTime)
        ?.SigningTime;
    if (signDate.HasValue)
        return signDate;
    signDate = (signerInfo.UnsignedAttributes
        .Cast<CryptographicAttributeObject>()
        .FirstOrDefault(x => x.Oid.Value == SigningTime)
        ?.Values[0] as Pkcs9SigningTime)
        ?.SigningTime;
    return signDate;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接