Get timestamp from Authenticode Signed files in .NET
Asked Answered
E

8

22

We need to verify that binary files are signed properly with digital signature (Authenticode). This can be achieved with signtool.exe pretty easily. However, we need an automatic way that also verifies signer name and timestamp. This is doable in native C++ with CryptQueryObject() API as shown in this wonderful sample: How To Get Information from Authenticode Signed Executables

However we live in a managed world :) hence looking for C# solution to the same problem. Straight approach would be to pInvoke Crypt32.dll and all is done. But there is similar managed API in System.Security.Cryptography.X509Certificates Namespace. X509Certificate2 Class seems to provide some information but no timestamp. Now we came to the original question how can we get that timestamp of a digital signature in C Sharp?

El answered 19/7, 2010 at 12:37 Comment(3)
The managed authenticode classes leave a lot out! It's probably easier to just p/Invoke.Swinge
First reason is that managed code looks nicer. Secondly, in several MSDN articles there are frightening notes like this one Note The WinVerifyTrust function is available for use in the operating systems listed in the Requirements section. It may be altered or unavailable in subsequent versions. Where Windows Vista is the latest supported system. Of course WinVerifyTrust and CryptQueryObject work on Windows 7 but with all these warnings it seemed logical to use some other API. It seems that Certificates namespace is half-baked indeed. Funny but this warning is not present in online MSDN now...El
@StephenCleary a .NET Core solution to this problem would be a nice bonus too.Solo
E
16

Back to the original question, I could not find managed way so ended up using pInvoke as follows:

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;
}

and the WinCrypt.cs contains the following:

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;

    }
}
El answered 7/2, 2011 at 23:22 Comment(1)
This will unfortunately only get "RSA" signed timestamps (szOID_RSA_counterSign) but miss "RFC3161" (szOID_RFC3161_counterSign) signed timestamps.Harquebusier
G
8

Thank you guys,

You help me a lot :)

BTW: I found simpler way how to obtain the time stamp.

here it is:

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;
    }
  }
}
Gadmon answered 9/9, 2011 at 10:22 Comment(1)
This will unfortunately only get "RSA" signed timestamps (szOID_RSA_counterSign) but miss "RFC3161" (szOID_RFC3161_counterSign) signed timestamps.Harquebusier
C
4

Thanks to the OP for your work. I added the implementation to get the actual TimeStamp of the cert.

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;
                    }

                }
            }
Charger answered 7/9, 2011 at 19:44 Comment(0)
M
3

I wanted to get the subject off the digital certificate, its an OU type string like

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

I found the X509Certificate to be really slow and load the entire file into memory. I tried to read an 800Mb patch file and my memory increased by 800Mb as it read it, and it took over 30 seconds!!

I have stood on the sholders of the posters above an managed to tweak the above code to get an X509Certificate2 object hundreds of time faster than using the X509 Object.

Please read my blog post for more details with images of the performance differences. X509Certificate object c# performance and memory issues alternative – fixed

Try this:

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;
    }

It seems that if you use the "WinCrypt.CERT_QUERY_CONTENT_FLAG_ALL" on the CryptQueryObject call it suffers from the same memory performance hit as the X509Certificate object, but if you trim it down to just the PKCS7 content types it performs like a dream and seems to give me the info I need.

Mockheroic answered 16/2, 2016 at 14:39 Comment(0)
K
1

As I see that you've got no replies anyway, let me offer one.

If you don't mind using third-party components, take a look at TElAuthenticodeVerifier component of our SecureBlackbox product. With this component you can verify the signature and check the timestamps.

Kukri answered 27/8, 2010 at 9:54 Comment(2)
The class looks nice, thanks. Does VerifySignature check certificate validity against CRL online? BTW, I've already implemented timestamp check by pinvoking CryptQueryObject so I am asking just for my curiosity.El
Hi SlavaGu, Could you please share your solution with me? or may be give some idea how can I do. I have also similar issue. Thanks.Scribe
N
1

The provided answers didn't worked in my case for SHA256 signing method. I newer reached the bottom within those nested foreach loops. But Nuget AuthenticodeExaminer seams to works just fine. Here is example for single certificate with single timestamp:

        var extractor = new FileInspector(@"D:\Temp\file.exe");
        var signTime = extractor.GetSignatures().FirstOrDefault()?.TimestampSignatures.FirstOrDefault()?.TimestampDateTime?.UtcDateTime;
Nickelic answered 18/12, 2019 at 11:3 Comment(0)
C
0

I like the idea of avoiding some nasty p/invoke code by using the SignedCms class, but be aware that under some network circumstances the SignedCms constructor can block for a long time --- I'm seeing about 15 seconds on a test I'm currently running. Alejandro Campos Magencio has some information on this on his MSDN blog in a post titled Big delay while calling EnvelopedCms constructor.

Carchemish answered 15/8, 2012 at 19:10 Comment(0)
M
0

here modern code looked up from 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;
}
Millstream answered 6/7, 2023 at 11:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.