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!