CertCheck.cpp (12098B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <windows.h> 8 #include <softpub.h> 9 #include <wintrust.h> 10 11 #pragma comment(lib, "wintrust.lib") 12 #pragma comment(lib, "crypt32.lib") 13 14 #ifndef UNICODE 15 #error "This file only supports building in Unicode mode" 16 #endif 17 18 static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; 19 20 // The definitions for NSPIM, the callback typedef, and 21 // extra_parameters all come from the NSIS plugin API source. 22 enum NSPIM 23 { 24 NSPIM_UNLOAD, 25 NSPIM_GUIUNLOAD, 26 }; 27 28 typedef UINT_PTR(*NSISPLUGINCALLBACK)(enum NSPIM); 29 30 struct extra_parameters 31 { 32 // The real type of exec_flags is exec_flags_t*, which is a large struct 33 // whose definition is omitted here because this plugin doesn't need it. 34 void* exec_flags; 35 int (__stdcall *ExecuteCodeSegment)(int, HWND); 36 void (__stdcall *validate_filename)(TCHAR*); 37 int (__stdcall *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); 38 }; 39 40 typedef struct _stack_t { 41 struct _stack_t *next; 42 TCHAR text[MAX_PATH]; 43 } stack_t; 44 45 int popstring(stack_t **stacktop, LPTSTR str, int len); 46 void pushstring(stack_t **stacktop, LPCTSTR str, int len); 47 48 struct CertificateCheckInfo 49 { 50 wchar_t filePath[MAX_PATH]; 51 wchar_t name[MAX_PATH]; 52 wchar_t issuer[MAX_PATH]; 53 }; 54 55 static HINSTANCE gHInst; 56 static HANDLE gCheckThread; 57 static HANDLE gCheckEvent; 58 static bool gCheckTrustPassed; 59 static bool gCheckAttributesPassed; 60 61 // We need a plugin callback not only to clean up our thread, but also 62 // because registering a callback prevents NSIS from unloading the DLL 63 // after each call from the script. 64 UINT_PTR __cdecl 65 NSISPluginCallback(NSPIM event) 66 { 67 if (event == NSPIM_UNLOAD){ 68 if (gCheckThread != NULL && 69 WaitForSingleObject(gCheckThread, 0) != WAIT_OBJECT_0) { 70 TerminateThread(gCheckThread, ERROR_OPERATION_ABORTED); 71 } 72 CloseHandle(gCheckThread); 73 gCheckThread = NULL; 74 CloseHandle(gCheckEvent); 75 gCheckEvent = NULL; 76 } 77 return NULL; 78 } 79 80 /** 81 * Checks to see if a file stored at filePath matches the specified info. This 82 * only supports the name and issuer attributes currently. 83 * 84 * @param certContext The certificate context of the file 85 * @param infoToMatch The acceptable information to match 86 * @return FALSE if the info does not match or if any error occurs in the check 87 */ 88 BOOL 89 DoCertificateAttributesMatch(PCCERT_CONTEXT certContext, 90 CertificateCheckInfo* infoToMatch) 91 { 92 DWORD dwData; 93 LPTSTR szName = NULL; 94 95 // Pass in NULL to get the needed size of the issuer buffer. 96 dwData = CertGetNameString(certContext, 97 CERT_NAME_SIMPLE_DISPLAY_TYPE, 98 CERT_NAME_ISSUER_FLAG, NULL, 99 NULL, 0); 100 101 if (!dwData) { 102 return FALSE; 103 } 104 105 // Allocate memory for Issuer name buffer. 106 szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); 107 if (!szName) { 108 return FALSE; 109 } 110 111 // Get Issuer name. 112 if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 113 CERT_NAME_ISSUER_FLAG, NULL, szName, dwData)) { 114 LocalFree(szName); 115 return FALSE; 116 } 117 118 // If the issuer does not match, return a failure. 119 if (!infoToMatch->issuer || 120 wcscmp(szName, infoToMatch->issuer)) { 121 LocalFree(szName); 122 return FALSE; 123 } 124 125 LocalFree(szName); 126 szName = NULL; 127 128 // Pass in NULL to get the needed size of the name buffer. 129 dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 130 0, NULL, NULL, 0); 131 if (!dwData) { 132 return FALSE; 133 } 134 135 // Allocate memory for the name buffer. 136 szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); 137 if (!szName) { 138 return FALSE; 139 } 140 141 // Obtain the name. 142 if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, 143 NULL, szName, dwData))) { 144 LocalFree(szName); 145 return FALSE; 146 } 147 148 // If the issuer does not match, return a failure. 149 if (!infoToMatch->name || 150 wcscmp(szName, infoToMatch->name)) { 151 LocalFree(szName); 152 return FALSE; 153 } 154 155 // We have a match! 156 LocalFree(szName); 157 158 // If there were any errors we would have aborted by now. 159 return TRUE; 160 } 161 162 /** 163 * Checks to see if a file's signing cert matches the specified info. This 164 * only supports the name and issuer attributes currently. 165 * 166 * @param info The acceptable information to match 167 * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info 168 * does not match, or the last error otherwise. 169 */ 170 DWORD 171 CheckCertificateInfoForPEFile(CertificateCheckInfo* info) 172 { 173 HCERTSTORE certStore = NULL; 174 HCRYPTMSG cryptMsg = NULL; 175 PCCERT_CONTEXT certContext = NULL; 176 PCMSG_SIGNER_INFO signerInfo = NULL; 177 DWORD lastError = ERROR_SUCCESS; 178 179 // Get the HCERTSTORE and HCRYPTMSG from the signed file. 180 DWORD encoding, contentType, formatType; 181 BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, 182 info->filePath, 183 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, 184 CERT_QUERY_CONTENT_FLAG_ALL, 185 0, &encoding, &contentType, 186 &formatType, &certStore, &cryptMsg, NULL); 187 if (!result) { 188 lastError = GetLastError(); 189 goto cleanup; 190 } 191 192 // Pass in NULL to get the needed signer information size. 193 DWORD signerInfoSize; 194 result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, 195 NULL, &signerInfoSize); 196 if (!result) { 197 lastError = GetLastError(); 198 goto cleanup; 199 } 200 201 // Allocate the needed size for the signer information. 202 signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); 203 if (!signerInfo) { 204 lastError = GetLastError(); 205 goto cleanup; 206 } 207 208 // Get the signer information (PCMSG_SIGNER_INFO). 209 // In particular we want the issuer and serial number. 210 result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, 211 (PVOID)signerInfo, &signerInfoSize); 212 if (!result) { 213 lastError = GetLastError(); 214 goto cleanup; 215 } 216 217 // Search for the signer certificate in the certificate store. 218 CERT_INFO certInfo; 219 certInfo.Issuer = signerInfo->Issuer; 220 certInfo.SerialNumber = signerInfo->SerialNumber; 221 certContext = CertFindCertificateInStore(certStore, ENCODING, 0, 222 CERT_FIND_SUBJECT_CERT, 223 (PVOID)&certInfo, NULL); 224 if (!certContext) { 225 lastError = GetLastError(); 226 goto cleanup; 227 } 228 229 if (!DoCertificateAttributesMatch(certContext, info)) { 230 lastError = ERROR_NOT_FOUND; 231 goto cleanup; 232 } 233 234 cleanup: 235 if (signerInfo) { 236 LocalFree(signerInfo); 237 } 238 if (certContext) { 239 CertFreeCertificateContext(certContext); 240 } 241 if (certStore) { 242 CertCloseStore(certStore, 0); 243 } 244 if (cryptMsg) { 245 CryptMsgClose(cryptMsg); 246 } 247 return lastError; 248 } 249 250 /** 251 * Verifies the trust of a signed file's certificate. 252 * 253 * @param filePath The file path to check. 254 * @return ERROR_SUCCESS if successful, or the last error code otherwise. 255 */ 256 DWORD 257 VerifyCertificateTrustForFile(LPCWSTR filePath) 258 { 259 // Setup the file to check. 260 WINTRUST_FILE_INFO fileToCheck; 261 ZeroMemory(&fileToCheck, sizeof(fileToCheck)); 262 fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); 263 fileToCheck.pcwszFilePath = filePath; 264 265 // Setup what to check, we want to check it is signed and trusted. 266 WINTRUST_DATA trustData; 267 // ZeroMemory should be fine here, but the compiler converts that into a 268 // call to memset, and we're avoiding the C runtime to keep file size down. 269 SecureZeroMemory(&trustData, sizeof(trustData)); 270 trustData.cbStruct = sizeof(trustData); 271 trustData.dwUIChoice = WTD_UI_NONE; 272 trustData.dwUnionChoice = WTD_CHOICE_FILE; 273 trustData.pFile = &fileToCheck; 274 275 GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; 276 // Check if the file is signed by something that is trusted. 277 LONG ret = WinVerifyTrust(NULL, &policyGUID, &trustData); 278 return ret; 279 } 280 281 /** 282 * Synchronously verifies the trust and attributes of a signed PE file. 283 * Meant to be invoked as a thread entry point from CheckPETrustAndInfoAsync. 284 */ 285 DWORD WINAPI 286 VerifyCertThreadProc(void* info) 287 { 288 CertificateCheckInfo* certInfo = (CertificateCheckInfo*)info; 289 290 if (VerifyCertificateTrustForFile(certInfo->filePath) == ERROR_SUCCESS) { 291 gCheckTrustPassed = true; 292 } 293 294 if (CheckCertificateInfoForPEFile(certInfo) == ERROR_SUCCESS) { 295 gCheckAttributesPassed = true; 296 } 297 298 LocalFree(info); 299 SetEvent(gCheckEvent); 300 return 0; 301 } 302 303 /** 304 * Verifies the trust and the attributes of a signed PE file's certificate on 305 * a separate thread. Returns immediately upon starting that thread. 306 * Call GetStatus (repeatedly if necessary) to get the result of the checks. 307 * 308 * @param stacktop A pointer to the NSIS stack. 309 * From the top down, the stack should contain: 310 * 1) the path to the file that will have its trust verified 311 * 2) the expected certificate subject common name 312 * 3) the expected certificate issuer common name 313 */ 314 extern "C" void __declspec(dllexport) 315 CheckPETrustAndInfoAsync(HWND, int, TCHAR*, stack_t **stacktop, extra_parameters* pX) 316 { 317 pX->RegisterPluginCallback(gHInst, NSISPluginCallback); 318 319 gCheckTrustPassed = false; 320 gCheckAttributesPassed = false; 321 gCheckThread = nullptr; 322 323 CertificateCheckInfo* certInfo = 324 (CertificateCheckInfo*)LocalAlloc(0, sizeof(CertificateCheckInfo)); 325 if (certInfo) { 326 popstring(stacktop, certInfo->filePath, MAX_PATH); 327 popstring(stacktop, certInfo->name, MAX_PATH); 328 popstring(stacktop, certInfo->issuer, MAX_PATH); 329 330 gCheckThread = CreateThread(nullptr, 0, VerifyCertThreadProc, 331 (void*)certInfo, 0, nullptr); 332 } 333 if (!gCheckThread) { 334 LocalFree(certInfo); 335 SetEvent(gCheckEvent); 336 } 337 } 338 339 /** 340 * Returns the result of a certificate check on the NSIS stack. 341 * 342 * If the check is not yet finished, will push "0" to the stack. 343 * If the check is finished, the top of the stack will be "1", followed by: 344 * "1" if the certificate is trusted by the system, "0" if not. Then: 345 * "1" if the certificate attributes matched those provided, "0" if not. 346 */ 347 extern "C" void __declspec(dllexport) 348 GetStatus(HWND, int, TCHAR*, stack_t **stacktop, void*) 349 { 350 if (WaitForSingleObject(gCheckEvent, 0) == WAIT_OBJECT_0) { 351 pushstring(stacktop, gCheckAttributesPassed ? L"1" : L"0", 2); 352 pushstring(stacktop, gCheckTrustPassed ? L"1" : L"0", 2); 353 pushstring(stacktop, L"1", 2); 354 } else { 355 pushstring(stacktop, L"0", 2); 356 } 357 } 358 359 BOOL WINAPI 360 DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID) 361 { 362 if (fdwReason == DLL_PROCESS_ATTACH) { 363 gHInst = hInst; 364 gCheckEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 365 } 366 return TRUE; 367 } 368 369 /** 370 * Removes an element from the top of the NSIS stack 371 * 372 * @param stacktop A pointer to the top of the stack 373 * @param str The string to pop to 374 * @param len The max length 375 * @return 0 on success 376 */ 377 int popstring(stack_t **stacktop, LPTSTR str, int len) 378 { 379 // Removes the element from the top of the stack and puts it in the buffer 380 stack_t *th; 381 if (!stacktop || !*stacktop) { 382 return 1; 383 } 384 385 th = (*stacktop); 386 lstrcpyn(str,th->text, len); 387 *stacktop = th->next; 388 GlobalFree((HGLOBAL)th); 389 return 0; 390 } 391 392 /** 393 * Adds an element to the top of the NSIS stack 394 * 395 * @param stacktop A pointer to the top of the stack 396 * @param str The string to push on the stack 397 * @param len The length of the string to push on the stack 398 * @return 0 on success 399 */ 400 void pushstring(stack_t **stacktop, LPCTSTR str, int len) 401 { 402 stack_t *th; 403 if (!stacktop) { 404 return; 405 } 406 407 th = (stack_t*)GlobalAlloc(GPTR, sizeof(stack_t) + len); 408 lstrcpyn(th->text, str, len); 409 th->next = *stacktop; 410 *stacktop = th; 411 }