(Sorry for my bad English.) I have some problems in my code.so I will try to explain my problem. I have a simple exe file with signed(pkcs7). I want to verify this file (without an internet connection). I don't want to use WinVerifyTrust() because I want to know how windows verify PE file. I wrote some code that first calls CryptQueryObject() with BLOB_DATA(mapped pe file), and it works fine. Then I can get certificate information: signer, serial, etc. (with CryptGetMsgParam()) and it also works fine; but, when I go to verify it with CertGetCertificateChain() I get and error. So, can you help me to solve this problem, which I think is that I don't use CertGetCertificateChain() correctly?
BOOL bRet = FALSE;
DWORD dwMsgAndCertEncodingType = 0;
DWORD dwContentType = 0;
DWORD dwFormatType = 0;
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCCERT_CONTEXT pCertContext = NULL;
CERT_BLOB blob_data = { 0 };
// CERT_INFO CertInfo = { 0 };
DWORD dwSignerInfo = 0;
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
CERT_CHAIN_PARA ChainPara = { 0 };
blob_data.pbData = pBuff;
blob_data.cbData = dwSize;
ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
bRet = CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
&blob_data,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
&dwContentType,
NULL,
&hStore,
&hMsg,
&pCertContext);
if (bRet != TRUE)
{
printf("Error CryptQueryObject\n");
return FALSE;
}
if (dwContentType != CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED)
{
printf("Error type\n");
return FALSE;
}
CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
pSignerInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSignerInfo);
CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, pSignerInfo, &dwSignerInfo);
bRet = CertGetCertificateChain(NULL, (PCCERT_CONTEXT)pCertContext, NULL, NULL, NULL, CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY,
NULL, &pChainContext);
if (bRet != TRUE)
{
printf("Error there\n");
}
One problem (though maybe not the only one) is the fact that your fifth parameter to CertGetCertificateChain() is NULL? This should point to a CERT_CHAIN_PARA structure. (You have one declared but don't appear to either initialize or use it.)
I don't know exactly what data you should put in this, but here's an example using code taken from a real (working) program (one of my own making) that checks digital certificates:
//...
CERT_CHAIN_PARA ChainPara; memset(&ChainPara, 0, sizeof(CERT_CHAIN_PARA));
ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
ChainPara.RequestedUsage.Usage.cUsageIdentifier = 0;
//...
bRet = CertGetCertificateChain(NULL, (PCCERT_CONTEXT)pCertContext, NULL, NULL, &ChainPara,
CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY, NULL, &pChainContext);
//...
You can look at the documentation for CERT_CHAIN_PARA to get some hints on what actual data/values you should set.
Hope this helps! (Feel free to ask for further clarification and or suggestions.)
Related
I'm new to HTTP and MFC. I suppose to upload a binary file to a given URL, by sending PUT request. I'm sure the requesting URL and the header are well formed and the file works well, because I tried them by Postman then get a SUCCESS returned.
But when I try to do this by the following MFC code, it always returns a 403 error. Please help me.
bool UploadFile(LPCTSTR strUrl, LPCTSTR filenameFullpath, CString header)
{
BOOL bResult = FALSE;
DWORD dwType = 0;
CString strServer;
CString strObject;
INTERNET_PORT wPort = 0;
DWORD dwFileLength = 0;
BYTE * pFileBuff = NULL;
CHttpConnection * pHC = NULL;
CHttpFile * pHF = NULL;
CInternetSession cis;
bResult = AfxParseURL(strUrl, dwType, strServer, strObject, wPort);
if (!bResult)
return FALSE;
CFile file;
try
{
// Read the file
if (!file.Open(filenameFullpath, CFile::shareDenyNone | CFile::modeRead))
return FALSE;
dwFileLength = file.GetLength();
if (dwFileLength <= 0)
return FALSE;
pFileBuff = new BYTE[dwFileLength];
memset(pFileBuff, 0, sizeof(BYTE) * dwFileLength);
file.Read(pFileBuff, dwFileLength);
// Set up internet connection
const int nTimeOut = 5000;
cis.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, nTimeOut);
cis.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 2); // Retry once if failed
pHC = cis.GetHttpConnection(strServer, dwType == AFX_INET_SERVICE_HTTP ? NORMAL_CONNECT : SECURE_CONNECT, wPort); // Get a HTTP connection
pHF = pHC->OpenRequest(CHttpConnection::HTTP_VERB_PUT, strObject);//strObject
pHF->AddRequestHeaders(header , HTTP_ADDREQ_FLAG_ADD_IF_NEW);
// I set the header separately instead of passing it as the first parameter of the next call.
// If I don't do so, another error occurs
if (!pHF->SendRequest(NULL, 0, pFileBuff, dwFileLength))
{
delete[]pFileBuff;
pFileBuff = NULL;
pHF->Close();
pHC->Close();
cis.Close();
return FALSE;
}
DWORD dwStateCode = 0;
pHF->QueryInfoStatusCode(dwStateCode);
if (dwStateCode == HTTP_STATUS_OK)
bResult = TRUE;
}
catch (CInternetException * pEx)
{
char sz[256] = "";
pEx->GetErrorMessage(sz, 25);
CString str;
str.Format("InternetException occur!\r\n%s", sz);
AfxMessageBox(str);
}
delete[]pFileBuff;
pFileBuff = NULL;
file.Close();
pHF->Close();
pHC->Close();
cis.Close();
return bResult;
}
And the calling of this function is just something like this:
CHttpClient hc;
hc.UploadFile(csUrl, "E:\\blah blah blah\\blah.tma", csUploadHeader);
while csUrl and csUploadHeader are well formed CString;
I got it! I should give pHF the header items (the key-value pairs) one by one, by calling the AddRequestHeaders() method several times, instead of wrapping and passing them together to pHF.
Then the following codes works very well:
CString sHeader1, sHeader2, sHeader3;
sHeader1.Format(_T("%s : %s"), sKey1, sValue1); // sKey_, sValue_ are strings
sHeader2.Format(_T("%s : %s"), sKey2, sValue2);
sHeader3.Format(_T("%s : %s"), sKey3, sValue3);
pHF->AddRequestHeaders(sHeader1, HTTP_ADDREQ_FLAG_ADD_IF_NEW); // Keep the second paeramter as these
pHF->AddRequestHeaders(sHeader2, HTTP_ADDREQ_FLAG_COALESCE);
pHF->AddRequestHeaders(sHeader3, HTTP_ADDREQ_FLAG_COALESCE);
And a very important notification: the string we pass to AddRequestHeaders() should not have the quotation("") around either the key or the value. In other words, string like "MyKey":"MyValue" have to be modified as MyKey:MyValue before giving to AddRequestHeaders();
I'm currently writing a C program which have to do WMI query.
I can issue a query without trouble, but it begin to be really hard when it come to retrieve property's value from the VARIANT variable.
I have a huge switch case, and for each case I just want to wprintf the value (the final purpose is to retrieve the value in a wchar_t string).
So far, for the integer type, it wasn't so difficult, but for the datetime type, I can't have something at all.
Here's the example i'm working on (there are no function check for the sake of lisibility, but I do it :
typedef struct cpwmi {
IWbemLocator *locator;
IWbemServices *services;
IEnumWbemClassObject *results;
IWbemClassObject *result;
} cpwmi_s;
void CpWmi_Constructor(cpwmi_s *self)
{
self->locator = NULL;
self->services = NULL;
self->results = NULL;
self->result = NULL;
}
void CpWmi_Destructor(cpwmi_s *self)
{
if (self->result) {
self->result->lpVtbl->Release(self->result);
}
if (self->results) {
self->results->lpVtbl->Release(self->results);
}
if (self->services) {
self->services->lpVtbl->Release(self->services);
}
if (self->locator) {
self->locator->lpVtbl->Release(self->locator);
}
}
void CpWmi_MinimalExample(cpwmi_s *self)
{
// Connection
CoCreateInstance(&CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, &self->locator);
BSTR ressource = SysAllocString(L"ROOT\\CIMV2");
self->locator->lpVtbl->ConnectServer(self->locator, ressource, NULL, NULL, NULL, 0, NULL, NULL, &self->services);
SysFreeString(ressource);
// Issue WMI query
BSTR query = SysAllocString(L"SELECT ReleaseDate FROM Win32_BIOS");
BSTR language = SysAllocString(L"WQL");
self->services->lpVtbl->ExecQuery(self->services, language, query, WBEM_FLAG_BIDIRECTIONAL, NULL, &self->results);
SysFreeString(query);
SysFreeString(language);
// Go to first result
ULONG count = 0;
self->results->lpVtbl->Next(self->results, WBEM_INFINITE, 1, &self->result, &count);
// Get propperty's value
BSTR propertyName = SysAllocString(L"ReleaseDate");
VARIANT propertyValue;
CIMTYPE propertyType;
self->result->lpVtbl->Get(self->result, propertyName, 0, &propertyValue, &propertyType, 0);
wprintf(L"%s: ", propertyName);
if (propertyValue.vt != VT_NULL && propertyValue.vt != VT_EMPTY) {
switch (propertyType) {
/* Many case here */
case CIM_DATETIME:
wprintf(L"%f", propertyValue.date);
break;
}
}
VariantClear(&propertyValue);
wprintf(L"\n");
}
int main(void)
{
cpwmi_s wmi;
CpWmi_Constructor(&wmi);
// initialize COM
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
CpWmi_MinimalExample(&wmi);
CpWmi_Destructor(&wmi);
// unwind everything else we've allocated
CoUninitialize();
printf("Stop\n");
getchar();
return;
}
I expect to have something like
ReleaseDate : 20140401000000.000000+000
but I end up with
ReleaseDate : -92559592126069970772275711289628980735378270670621907468943360.000000
I must misread msdn documentation or don't know how to search/read, because up to this point, I have no idea how to do that in C !
I'm also worried about CIM_REFERENCE_TYPE, CIM_OBJECT_TYPE and the alike.
Is there anyone who have a hint or know to do that ?
propertyValue.date is double, it needs "%g" print format, but it's probably not valid. Try bstrVal value instead:
case CIM_DATETIME:
//wprintf(L"%g\n", propertyValue.date);
wprintf(L"%s\n", propertyValue.bstrVal);
break;
Note, in debug mode you can move the cursor over propertyValue and it should show the values contained in propertyValue
Is there any way of conditionally checking (strictly in C) whether a Windows user has administrator privileges or not?
I wish to do this programmatically (not simply telling the user to "Run as Administrator").
There are a couple of fundamentally different ways. The most common
is, unfortunately, rather tedious. It involves finding the current
user's SID, then finding the groups to which he belongs, and then
finding whether one of them is the Administrators group:
#include <windows.h>
#include <vector>
bool is_administrator() {
HANDLE access_token;
DWORD buffer_size = 0;
PSID admin_SID;
TOKEN_GROUPS *group_token = NULL;
SID_IDENTIFIER_AUTHORITY NT_authority = SECURITY_NT_AUTHORITY;
if (!OpenProcessToken(GetCurrentProcess(),TOKEN_READ,&access_token))
return false;
GetTokenInformation(
access_token,
TokenGroups,
group_token,
0,
&buffer_size
);
std::vector<char> buffer(buffer_size);
group_token =
reinterpret_cast<TOKEN_GROUPS*>(&buffer[0]);
bool succeeded = GetTokenInformation(
access_token,
TokenGroups,
group_token,
buffer_size,
&buffer_size
);
CloseHandle(access_token);
if (!succeeded)
return false;
if (!AllocateAndInitializeSid(
&NT_authority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0,0,0,0,0,0,
&admin_SID
))
{
return false;
}
bool found=false;
for(int i=0; !found && i < group_token->GroupCount; i++)
found = EqualSid(admin_SID,group_token->Groups[i].Sid);
FreeSid(admin_SID);
return found;
}
There's another way that's a bit simpler though:
bool is_administrator()
{
bool result;
DWORD rc;
wchar_t user_name[256];
USER_INFO_1 *info;
DWORD size = sizeof( user_name );
GetUserNameW( user_name, &size);
rc = NetUserGetInfo( NULL, user_name, 1, (byte **) &info );
if ( rc != NERR_Success )
return false;
result = info->usri1_priv == USER_PRIV_ADMIN;
NetApiBufferFree( info );
return result;
}
In either case, if you have a domain, things can be a little hairy,
because a particular user might be an administrator on the local
machine, but not on the domain, or vice versa. Finding the info
doesn't necessarily change much, but you may have to think a bit to
figure out what you really want.
Edit: As #Benj pointed out, the first method could really use a bit of updating. While I already fixed the obvious leak it had, it's still a huge, monolithic function with no exception safety, and generally rather outdated coding style. Perhaps a small update is in order:
#include <windows.h>
#include <vector>
#include <algorithm>
class sid {
PSID s;
public:
sid(SID_IDENTIFIER_AUTHORITY auth, std::vector<DWORD> sub_auths) {
DWORD count = sub_auths.size();
sub_auths.resize(7, DWORD());
if (!AllocateAndInitializeSid(
&auth,
count,
sub_auths[0], sub_auths[1], sub_auths[2], sub_auths[3],
sub_auths[4], sub_auths[5], sub_auths[6], sub_auths[7],
&s
))
{
throw std::runtime_error("Unable to allocate Admin SID");
}
}
sid(PSID const &p=NULL) : s(p) {}
bool operator==(sid const &r) const { return EqualSid(s, r.s); }
};
class access_token {
HANDLE token;
public:
access_token(HANDLE PID=GetCurrentProcess(), DWORD access=TOKEN_READ) {
if (!OpenProcessToken(PID, access, &token))
throw std::runtime_error("Unable to open process token");
}
operator HANDLE() { return token; }
~access_token() { CloseHandle(token); }
};
std::vector<sid> get_group_sids() {
DWORD buffer_size = 0;
TOKEN_GROUPS *group_token = NULL;
std::vector<sid> groups;
access_token token;
GetTokenInformation(token, TokenGroups, group_token, 0, &buffer_size);
std::vector<char> buffer(buffer_size);
group_token = reinterpret_cast<TOKEN_GROUPS*>(&buffer[0]);
if (GetTokenInformation(token, TokenGroups, group_token, buffer_size, &buffer_size))
for (int i=0; i<group_token->GroupCount; i++)
groups.push_back(group_token->Groups[i].Sid);
return groups;
}
bool is_administrator() {
std::vector<sid> groups = get_group_sids();
SID_IDENTIFIER_AUTHORITY NT_authority = SECURITY_NT_AUTHORITY;
std::vector<DWORD> sub_auths;
sub_auths.push_back(SECURITY_BUILTIN_DOMAIN_RID);
sub_auths.push_back(DOMAIN_ALIAS_RID_ADMINS);
sid admin_SID(NT_authority, sub_auths);
return std::find(groups.begin(), groups.end(), admin_SID) != groups.end();
}
#ifdef TEST
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::boolalpha << is_administrator() << "\n";
}
#endif
A slightly different (and shorter? easier?) method adapted from some hints on MSDN:
PSID administrators_group = NULL;
SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
BOOL result = AllocateAndInitializeSid( &nt_authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administrators_group);
BOOL is_user_admin = FALSE;
if (result)
{
CheckTokenMembership(NULL, administrators_group, &is_user_admin);
FreeSid(administrators_group);
}
if (is_user_admin)
{
// do something here for admin users...
}
Sure you need to call:
OpenThreadToken()
To get the user's token.
GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwNeeded)
To get the size of the token group information (and allocate enough space)
GetTokenInformation(hToken, TokenGroups, pTokenGroups, dwSize, &dwNeeded)
To get the local groups
AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators);
To get the SID of the Administrator
EqualSid()
To compare that SID with the SIDs in your local groups.
You should use IsUserAnAdmin from SHELL32
I'm trying to debug some code that uses COM, which I am a beginner at.
The two calls to IUnknown::Release at the end have got me worried.
The interfaces were created with DllGetClassObject and IClassFactory::CreateInstance.
I have seen other similar code that does not call IUnknown::Release on these - which is correct?
int OpenMixer_Win_DirectSound(px_mixer *Px, int index)
{
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA desc;
HMODULE hDsound = INVALID_HANDLE_VALUE;
GCO DllGetClassObject;
IClassFactory *pcf = NULL;
IKsPropertySet *pps = NULL;
HRESULT hr;
ULONG bytes;
LPGUID guidIn;
LPGUID guidOut;
UINT deviceIn = -1;
UINT deviceOut = -1;
int ret = FALSE;
guidIn = PaWinDS_GetStreamInputGUID(Px->pa_stream);
guidOut = PaWinDS_GetStreamOutputGUID(Px->pa_stream);
do {
hDsound = LoadLibraryA("dsound.dll");
if (hDsound == NULL) {
break;
}
DllGetClassObject = (GCO) GetProcAddress(hDsound, "DllGetClassObject");
if (DllGetClassObject == NULL) {
break;
}
hr = DllGetClassObject(&CLSID_DirectSoundPrivate,
&IID_IClassFactory,
(void **)(&pcf));
if (hr || pcf == NULL) {
break;
}
hr = IClassFactory_CreateInstance(pcf,
NULL,
&IID_IKsPropertySet,
(void **)(&pps));
if (hr || pps == NULL) {
break;
}
/* Do stuff with the interfaces */
} while( FALSE );
if (pps) {
IUnknown_Release(pps);
}
if (pcf) {
IUnknown_Release(pcf);
}
// Free the library. Note that portaudio also opens dsound.dll
// so this probably doesn't do anything until Pa_Terminate is called.
if (hDsound != INVALID_HANDLE_VALUE) {
FreeLibrary(hDsound);
}
}
Absolutely. Both functions create a new interface pointer, they will have a reference count of 1, the AddRef() function was already called. When you're done with it then you have to call Release(). You'll leak memory if you don't. Every interface in COM works this way.
Yes. As seen in DllGetClassObject sample, the return ppvObj will have a refcount.
Yes, DllGetClassObject() will create an object and pass ownership of that object to your code. Your code will now own the object and be responsible for releasing it by calling IUnknown::Release().
I'm implementing a process elevation helper for Windows. It's a program that will run in elevated mode and launch other programs with administrator privileges without displaying additional UAC prompts. For security reasons, I want to make sure only binaries that are digitally signed with my company's Authenticode key can be executed.
The WinVerifyTrust function gets me halfway there, but it only ensures that a binary is signed by some key that is part of Microsoft's chain of trust. Is there a relatively simple way to perform the Authenticode verification AND ensure that it is signed by our private key?
I believe what you're looking for is CryptQueryObject.
With it you should be able to pull the involved certificate out of a PE, and do any additional checks you want.
By way of example, this will get you to a HCRYPTMSG. From there you can use CryptMsgGetParam to pull out whatever you want. I'd hoped to make something more 'robust', but these APIs are pretty hairy insomuch as they require a lot of branching to handle all their return cases.
So, here's a p/invoke-rific c# example (I started in C, but that was basically unreadable):
static class Crypt32
{
//Omitting flag constants; you can look these up in WinCrypt.h
[DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool 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);
}
class Program
{
static void Main(string[] args)
{
//Path to executable here
// I tested with MS-Office .exe's
string path = "";
int contentType;
int formatType;
int ignored;
IntPtr context = IntPtr.Zero;
IntPtr pIgnored = IntPtr.Zero;
IntPtr cryptMsg = IntPtr.Zero;
if (!Crypt32.CryptQueryObject(
Crypt32.CERT_QUERY_OBJECT_FILE,
Marshal.StringToHGlobalUni(path),
Crypt32.CERT_QUERY_CONTENT_FLAG_ALL,
Crypt32.CERT_QUERY_FORMAT_FLAG_ALL,
0,
out ignored,
out contentType,
out formatType,
ref pIgnored,
ref cryptMsg,
ref context))
{
int error = Marshal.GetLastWin32Error();
Console.WriteLine((new Win32Exception(error)).Message);
return;
}
//expecting '10'; CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED
Console.WriteLine("Context Type: " + contentType);
//Which implies this is set
Console.WriteLine("Crypt Msg: " + cryptMsg.ToInt32());
return;
}
To get the certificate information from signed code use this:
using System.Security.Cryptography.X509Certificates;
X509Certificate basicSigner = X509Certificate.CreateFromSignedFile(filename);
X509Certificate2 cert = new X509Certificate2(basicSigner);
Then you can get the cert details like this:
Console.WriteLine(cert.IssuerName.Name);
Console.WriteLine(cert.SubjectName.Name);
// etc
these are some of the nastiest APIs I've ever worked with
A word of warning: it's worse than you already thought.
At least since introducing SHA-256 signing (has this always been the case?), it's possible for Authenticode to have multiple signatures. They're not encoded as multiple signatures in the PKCS-7 signature message; instead, they're unauthenticated message attributes of type OID_NESTED_SIGNATURE, each containing another complete PKCS-7 signature message.
WinVerifyTrust will tell you the file is valid if any of the signatures are valid and come from a trusted certificate chain. However it won't tell you which of the signatures was valid. If you then use CryptQueryObject to read the full PKCS-7 message, and only look at the certificate for the primary signature (as in the code samples here and on MSDN), you're not necessarily looking at a verified certificate. The associated signature might not match the executable, and/or the certificate might not have a trusted CA chain.
If you're using the details of the primary signature to validate that the certificate is one your software trusts, you're vulnerable to a situation where WinVerifyTrust is trusting a secondary signature, but your code is checking the primary signature's certificate is what you expected, and you haven't noticed that the signature from the primary certificate is nonsense. An attacker could use your public certificate without owning its private key, combined with some other code-signing certificate issued to someone else, to bypass a publisher check this way.
From Win8 onwards, WinVerifyTrust can optionally validate specific signatures, so you should be able to iterate the signatures to find one that is valid and one that satisfies your requirements.
If you have to be Win7-compatible, though, as far as I know the best you can manage is MsiGetFileSignatureInformation. From experimentation (as for everything else here, the actual documentation is frustratingly woolly), it seems to return the trusted certificate when WinVerifyTrust trusts one. But if there isn't a trusted signature, it returns the primary signature's certificate anyway, so you still have to use WinVerifyTrust to check that first.
Of course there also plenty of possible time-of-check/time-of-use problems here.
found the solution here:
http://www.ucosoft.com/how-to-program-to-retrieve-the-authenticode-information.html
here it is with indentation:
#define _UNICODE 1
#define UNICODE 1
#include <windows.h>
#include <tchar.h>
#include <wincrypt.h>
#include <Softpub.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment (lib, "Crypt32")
// the Authenticode Signature is encode in PKCS7
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
// Information structure of authenticode sign
typedef struct
{
LPWSTR lpszProgramName;
LPWSTR lpszPublisherLink;
LPWSTR lpszMoreInfoLink;
DWORD cbSerialSize;
LPBYTE lpSerialNumber;
LPTSTR lpszIssuerName;
LPTSTR lpszSubjectName;
}
SPROG_SIGNATUREINFO, *PSPROG_SIGNATUREINFO;
VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo);
VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo);
BOOL GetAuthenticodeInformation(LPCTSTR lpszFileName, PSPROG_SIGNATUREINFO pInfo)
{
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
DWORD dwSignerInfo;
BOOL bRet = FALSE;
__try
{
// as CryptQueryObject() only accept WCHAR file name, convert first
WCHAR wszFileName[MAX_PATH];
#ifdef UNICODE
if ( !lstrcpynW( wszFileName, lpszFileName, MAX_PATH))
__leave;
#else
if ( mbstowcs( wszFileName, lpszFileName, MAX_PATH) == -1)
__leave;
#endif
//Retrieve the Message Handle and Store Handle
DWORD dwEncoding, dwContentType, dwFormatType;
if ( !CryptQueryObject( CERT_QUERY_OBJECT_FILE, wszFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY, 0, &dwEncoding,
&dwContentType, &dwFormatType, &hStore,
&hMsg, NULL))
__leave;
//Get the length of SignerInfo
if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo))
__leave;
// allocate the memory for SignerInfo
if ( !(pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc( LPTR, dwSignerInfo)))
__leave;
// get the SignerInfo
if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo))
__leave;
//get the Publisher from SignerInfo
GetProgAndPublisherInfo( pSignerInfo, pInfo);
//get the Certificate from SignerInfo
GetCertificateInfo( hStore, pSignerInfo, pInfo);
bRet = TRUE;
}
__finally
{
// release the memory
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
if (hStore != NULL) CertCloseStore(hStore, 0);
if (hMsg != NULL) CryptMsgClose(hMsg);
}
return bRet;
}
LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
LPWSTR outputString = NULL;
// allocate the memory
outputString = (LPWSTR)VirtualAlloc(NULL, (wcslen(inputString) + 1) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);
// copy
if (outputString != NULL)
{
lstrcpyW(outputString, inputString);
}
return outputString;
}
VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo)
{
PSPC_SP_OPUS_INFO OpusInfo = NULL;
DWORD dwData;
__try
{
// query SPC_SP_OPUS_INFO_OBJID OID in Authenticated Attributes
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID, pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// get the length of SPC_SP_OPUS_INFO
if ( !CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwData))
__leave;
// allocate the memory for SPC_SP_OPUS_INFO
if ( !(OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData)))
__leave;
// get SPC_SP_OPUS_INFO structure
if ( !CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
OpusInfo,
&dwData))
__leave;
// copy the Program Name of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pwszProgramName)
{
pInfo->lpszProgramName = AllocateAndCopyWideString(OpusInfo->pwszProgramName);
}
else
pInfo->lpszProgramName = NULL;
// copy the Publisher Info of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pPublisherInfo)
{
switch (OpusInfo->pPublisherInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
break;
default:
pInfo->lpszPublisherLink = NULL;
break;
}
}
else
{
pInfo->lpszPublisherLink = NULL;
}
// copy the More Info of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pMoreInfo)
{
switch (OpusInfo->pMoreInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
break;
default:
pInfo->lpszMoreInfoLink = NULL;
break;
}
}
else
{
pInfo->lpszMoreInfoLink = NULL;
}
break; // we have got the information, break
}
}
}
__finally
{
if (OpusInfo != NULL) LocalFree(OpusInfo);
}
}
VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
__try
{
CERT_INFO CertInfo;
DWORD dwData;
// query Signer Certificate in Certificate Store
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;
if ( !(pCertContext = CertFindCertificateInStore( hStore,
ENCODING, 0, CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo, NULL)))
__leave;
dwData = pCertContext->pCertInfo->SerialNumber.cbData;
// SPROG_SIGNATUREINFO.cbSerialSize
pInfo->cbSerialSize = dwData;
// SPROG_SIGNATUREINFO.lpSerialNumber
pInfo->lpSerialNumber = (LPBYTE)VirtualAlloc(NULL, dwData, MEM_COMMIT, PAGE_READWRITE);
memcpy( pInfo->lpSerialNumber, pCertContext->pCertInfo->SerialNumber.pbData, dwData);
// SPROG_SIGNATUREINFO.lpszIssuerName
__try
{
// get the length of Issuer Name
if (!(dwData = CertGetNameString( pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG, NULL, NULL, 0)))
__leave;
// allocate the memory
if ( !(pInfo->lpszIssuerName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE)))
__leave;
// get Issuer Name
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG, NULL, pInfo->
lpszIssuerName, dwData)))
__leave;
}
__finally
{
}
// SPROG_SIGNATUREINFO.lpszSubjectName
__try
{
//get the length of Subject Name
if (!(dwData = CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0)))
__leave;
// allocate the memory
if ( !(pInfo->lpszSubjectName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE)))
__leave;
// get Subject Name
if (!(CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, pInfo->lpszSubjectName, dwData)))
__leave;
}
__finally
{
}
}
__finally
{
if (pCertContext != NULL)
CertFreeCertificateContext(pCertContext);
}
}
int _tmain(int argc, TCHAR *argv[])
{
if (argc != 2)
{
_tprintf(_T("Usage: SignedFileInfo \n"));
return 0;
}
else
{
SPROG_SIGNATUREINFO SignInfo;
ZeroMemory(&SignInfo, sizeof(SignInfo));
GetAuthenticodeInformation( argv[1], &SignInfo);
wprintf(L"Program Name: %s\n", SignInfo.lpszProgramName);
wprintf(L"Publisher Link: %s\n", SignInfo.lpszPublisherLink);
wprintf(L"More Info Link: %s\n", SignInfo.lpszMoreInfoLink);
{
_tprintf(_T("Serial Number: "));
DWORD dwData = SignInfo.cbSerialSize;
for (DWORD n = 0; n < dwData; n++)
{
_tprintf(_T("%02x "),
SignInfo.lpSerialNumber[dwData - (n + 1)]);
}
_tprintf(_T("\n"));
}
_tprintf(_T("Issuer Name: %s\n"), SignInfo.lpszIssuerName);
_tprintf(_T("Subject Name: %s\n"), SignInfo.lpszSubjectName);
if ( SignInfo.lpszProgramName) VirtualFree(SignInfo.lpszProgramName, 0, MEM_RELEASE);
if ( SignInfo.lpszPublisherLink) VirtualFree(SignInfo.lpszPublisherLink, 0, MEM_RELEASE);
if ( SignInfo.lpszMoreInfoLink) VirtualFree(SignInfo.lpszMoreInfoLink, 0, MEM_RELEASE);
if ( SignInfo.lpSerialNumber) VirtualFree(SignInfo.lpSerialNumber, 0, MEM_RELEASE);
if ( SignInfo.lpszIssuerName) VirtualFree(SignInfo.lpszIssuerName, 0, MEM_RELEASE);
if ( SignInfo.lpszSubjectName) VirtualFree(SignInfo.lpszSubjectName, 0, MEM_RELEASE);
return 0;
}
}