CryptQueryObject fails with CRYPT_E_NO_MATCH error in managed code but works fine in unmanaged one

0

I've been struggling with this bug for over a day and I appreciate if anyone could help me shed some light on it. It all started from this question. My goal was to retrieve digital signature information on a signed .js file. (The file was originally signed by Microsoft's signtool.)

Since my managed code seemed to have failed, I decided to try an unmanaged approach, in C++, which surprisingly worked just fine. So I decide to write a similar thing in C# using PInvoke. But no matter what I did in the managed code, that didn't work.

So I did some digging and here's the part that seems to fail.

If I do this from either 32-bit or 64-bit unmanaged C++ code, it works fine:

HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL; 
DWORD dwEncoding = 0, dwContentType = 0, dwFormatType = 0;

// Get message handle and store handle from the signed file.
if(CryptQueryObject(CERT_QUERY_OBJECT_FILE,
                            L"D:\\Test\\DataStore\\Downloads\\en-US\\test02_1.js",
                            CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
                            CERT_QUERY_FORMAT_FLAG_BINARY,
                            0,
                            &dwEncoding,
                            &dwContentType,
                            &dwFormatType,
                            &hStore,
                            &hMsg,
                            NULL))
{
    //All good
    TRACE("Got it!\n");
}
else
{
    //Failed
    TRACE("Error: 0x%x\n", ::GetLastError());
}

But if I do the same from an ASP.NET web application written in C# (and running as a 64-bit process on a 64-bit Windows 8.1 OS) using PInvoke, it gives me the 0x80092009 error code, or CRYPT_E_NO_MATCH:

IntPtr hStore = IntPtr.Zero;
IntPtr hMsg = IntPtr.Zero;

IntPtr dwEncoding = IntPtr.Zero, dwContentType = IntPtr.Zero, dwFormatType = IntPtr.Zero;

IntPtr DummyNull = IntPtr.Zero;

// Get message handle and store handle from the signed file.
if (!CryptQueryObject(CERT_QUERY_OBJECT_FILE,
    "D:\\Test\\DataStore\\Downloads\\en-US\\test02_1.js",
    CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
    CERT_QUERY_FORMAT_FLAG_BINARY,
    0,
    dwEncoding,
    dwContentType,
    dwFormatType,
    hStore,
    hMsg,
    ref DummyNull))
{
    //Failed
    int nOSError = Marshal.GetLastWin32Error();

    throw new Exception("Failed with error " + nOSError);
}

and these are PInvoke declarations for it:

        [DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern Boolean CryptQueryObject(
            Int32 dwObjectType,
            [MarshalAs(UnmanagedType.LPWStr)]string pvObject,
            Int32 dwExpectedContentTypeFlags,
            Int32 dwExpectedFormatTypeFlags,
            Int32 dwFlags,
            IntPtr pdwMsgAndCertEncodingType,
            IntPtr pdwContentType,
            IntPtr pdwFormatType,
            IntPtr phCertStore,
            IntPtr phMsg,
            ref IntPtr ppvContext
        );

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

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_ALGORITHM_IDENTIFIER
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public String pszObjId;
            public CRYPT_OBJID_BLOB Parameters;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_BIT_BLOB
        {
            public uint cbData;
            public IntPtr pbData;
            public uint cUnusedBits;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CERT_PUBLIC_KEY_INFO
        {
            public CRYPT_ALGORITHM_IDENTIFIER Algorithm;
            public CRYPT_BIT_BLOB PublicKey;
        }

    #pragma warning disable 0618
        [StructLayout(LayoutKind.Sequential)]
        private struct CERT_INFO
        {
            public uint dwVersion;
            public CRYPT_OBJID_BLOB SerialNumber;
            public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
            public CRYPT_OBJID_BLOB Issuer;
            public FILETIME NotBefore;
            public FILETIME NotAfter;
            public CRYPT_OBJID_BLOB Subject;
            public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
            public CRYPT_OBJID_BLOB IssuerUniqueId;
            public CRYPT_OBJID_BLOB SubjectUniqueId;
            public uint cExtension;
            public IntPtr rgExtension;
        }
    #pragma warning restore 0618

        private const int CERT_QUERY_OBJECT_FILE = 0x00000001;
        private const int CERT_QUERY_OBJECT_BLOB = 0x00000002;

        private const int CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED = 10;
        private const int CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = (1 << CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED);

        private const int CERT_QUERY_FORMAT_BINARY = 1;
        private const int CERT_QUERY_FORMAT_FLAG_BINARY = (1 << CERT_QUERY_FORMAT_BINARY);

Any idea why?

PS. Note that if instead of a digitally signed .js file, I replace it with a digitally signed .exe file, both managed and unmanaged code seem to work fine. And that really puzzles me!

c#
asp.net
.net
winapi
digital-signature
asked on Stack Overflow Dec 15, 2015 by c00000fd • edited May 23, 2017 by Community

1 Answer

0

Since I haven't gotten an answer to this, let me share my current findings. First off, due to lack of replies and even a downvote from some "guru", it seems to me that people aren't really aware that one can not only digitally sign .exe and .dll files using the SignTool, but also .js, .vbs and .ps1 or PowerShell scripts, to name a few. It also appears to me that CryptQueryObject API relies on the extension of the digitally signed file to properly gauge the format of the signature. And for some reason the managed code seems to either rename the file it works with, or somehow make it inaccessible for the CryptQueryObject API.

I shared more of my thoughts on this in this thread.

answered on Stack Overflow Dec 23, 2015 by c00000fd • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0