Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include <stdio.h> |
michael@0 | 6 | #include <stdlib.h> |
michael@0 | 7 | #include <windows.h> |
michael@0 | 8 | #include <softpub.h> |
michael@0 | 9 | #include <wintrust.h> |
michael@0 | 10 | |
michael@0 | 11 | #pragma comment(lib, "wintrust.lib") |
michael@0 | 12 | #pragma comment(lib, "crypt32.lib") |
michael@0 | 13 | |
michael@0 | 14 | #ifdef UNICODE |
michael@0 | 15 | |
michael@0 | 16 | #ifndef _T |
michael@0 | 17 | #define __T(x) L ## x |
michael@0 | 18 | #define _T(x) __T(x) |
michael@0 | 19 | #define _TEXT(x) __T(x) |
michael@0 | 20 | #endif |
michael@0 | 21 | |
michael@0 | 22 | #else |
michael@0 | 23 | |
michael@0 | 24 | #ifndef _T |
michael@0 | 25 | #define _T(x) x |
michael@0 | 26 | #define _TEXT(x) x |
michael@0 | 27 | #endif |
michael@0 | 28 | |
michael@0 | 29 | #endif // UNICODE |
michael@0 | 30 | |
michael@0 | 31 | static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; |
michael@0 | 32 | |
michael@0 | 33 | typedef struct _stack_t { |
michael@0 | 34 | struct _stack_t *next; |
michael@0 | 35 | TCHAR text[MAX_PATH]; |
michael@0 | 36 | } stack_t; |
michael@0 | 37 | |
michael@0 | 38 | int popstring(stack_t **stacktop, LPTSTR str, int len); |
michael@0 | 39 | void pushstring(stack_t **stacktop, LPCTSTR str, int len); |
michael@0 | 40 | |
michael@0 | 41 | struct CertificateCheckInfo |
michael@0 | 42 | { |
michael@0 | 43 | LPCWSTR name; |
michael@0 | 44 | LPCWSTR issuer; |
michael@0 | 45 | }; |
michael@0 | 46 | |
michael@0 | 47 | /** |
michael@0 | 48 | * Checks to see if a file stored at filePath matches the specified info. This |
michael@0 | 49 | * only supports the name and issuer attributes currently. |
michael@0 | 50 | * |
michael@0 | 51 | * @param certContext The certificate context of the file |
michael@0 | 52 | * @param infoToMatch The acceptable information to match |
michael@0 | 53 | * @return FALSE if the info does not match or if any error occurs in the check |
michael@0 | 54 | */ |
michael@0 | 55 | BOOL |
michael@0 | 56 | DoCertificateAttributesMatch(PCCERT_CONTEXT certContext, |
michael@0 | 57 | CertificateCheckInfo &infoToMatch) |
michael@0 | 58 | { |
michael@0 | 59 | DWORD dwData; |
michael@0 | 60 | LPTSTR szName = NULL; |
michael@0 | 61 | |
michael@0 | 62 | // Pass in NULL to get the needed size of the issuer buffer. |
michael@0 | 63 | dwData = CertGetNameString(certContext, |
michael@0 | 64 | CERT_NAME_SIMPLE_DISPLAY_TYPE, |
michael@0 | 65 | CERT_NAME_ISSUER_FLAG, NULL, |
michael@0 | 66 | NULL, 0); |
michael@0 | 67 | |
michael@0 | 68 | if (!dwData) { |
michael@0 | 69 | return FALSE; |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | // Allocate memory for Issuer name buffer. |
michael@0 | 73 | szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); |
michael@0 | 74 | if (!szName) { |
michael@0 | 75 | return FALSE; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | // Get Issuer name. |
michael@0 | 79 | if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, |
michael@0 | 80 | CERT_NAME_ISSUER_FLAG, NULL, szName, dwData)) { |
michael@0 | 81 | LocalFree(szName); |
michael@0 | 82 | return FALSE; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | // If the issuer does not match, return a failure. |
michael@0 | 86 | if (!infoToMatch.issuer || |
michael@0 | 87 | wcscmp(szName, infoToMatch.issuer)) { |
michael@0 | 88 | LocalFree(szName); |
michael@0 | 89 | return FALSE; |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | LocalFree(szName); |
michael@0 | 93 | szName = NULL; |
michael@0 | 94 | |
michael@0 | 95 | // Pass in NULL to get the needed size of the name buffer. |
michael@0 | 96 | dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, |
michael@0 | 97 | 0, NULL, NULL, 0); |
michael@0 | 98 | if (!dwData) { |
michael@0 | 99 | return FALSE; |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | // Allocate memory for the name buffer. |
michael@0 | 103 | szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); |
michael@0 | 104 | if (!szName) { |
michael@0 | 105 | return FALSE; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | // Obtain the name. |
michael@0 | 109 | if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, |
michael@0 | 110 | NULL, szName, dwData))) { |
michael@0 | 111 | LocalFree(szName); |
michael@0 | 112 | return FALSE; |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | // If the issuer does not match, return a failure. |
michael@0 | 116 | if (!infoToMatch.name || |
michael@0 | 117 | wcscmp(szName, infoToMatch.name)) { |
michael@0 | 118 | LocalFree(szName); |
michael@0 | 119 | return FALSE; |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | // We have a match! |
michael@0 | 123 | LocalFree(szName); |
michael@0 | 124 | |
michael@0 | 125 | // If there were any errors we would have aborted by now. |
michael@0 | 126 | return TRUE; |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | /** |
michael@0 | 130 | * Checks to see if a file stored at filePath matches the specified info. This |
michael@0 | 131 | * only supports the name and issuer attributes currently. |
michael@0 | 132 | * |
michael@0 | 133 | * @param filePath The PE file path to check |
michael@0 | 134 | * @param infoToMatch The acceptable information to match |
michael@0 | 135 | * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info |
michael@0 | 136 | * does not match, or the last error otherwise. |
michael@0 | 137 | */ |
michael@0 | 138 | DWORD |
michael@0 | 139 | CheckCertificateForPEFile(LPCWSTR filePath, |
michael@0 | 140 | CertificateCheckInfo &infoToMatch) |
michael@0 | 141 | { |
michael@0 | 142 | HCERTSTORE certStore = NULL; |
michael@0 | 143 | HCRYPTMSG cryptMsg = NULL; |
michael@0 | 144 | PCCERT_CONTEXT certContext = NULL; |
michael@0 | 145 | PCMSG_SIGNER_INFO signerInfo = NULL; |
michael@0 | 146 | DWORD lastError = ERROR_SUCCESS; |
michael@0 | 147 | |
michael@0 | 148 | // Get the HCERTSTORE and HCRYPTMSG from the signed file. |
michael@0 | 149 | DWORD encoding, contentType, formatType; |
michael@0 | 150 | BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, |
michael@0 | 151 | filePath, |
michael@0 | 152 | CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, |
michael@0 | 153 | CERT_QUERY_CONTENT_FLAG_ALL, |
michael@0 | 154 | 0, &encoding, &contentType, |
michael@0 | 155 | &formatType, &certStore, &cryptMsg, NULL); |
michael@0 | 156 | if (!result) { |
michael@0 | 157 | lastError = GetLastError(); |
michael@0 | 158 | goto cleanup; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | // Pass in NULL to get the needed signer information size. |
michael@0 | 162 | DWORD signerInfoSize; |
michael@0 | 163 | result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, |
michael@0 | 164 | NULL, &signerInfoSize); |
michael@0 | 165 | if (!result) { |
michael@0 | 166 | lastError = GetLastError(); |
michael@0 | 167 | goto cleanup; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | // Allocate the needed size for the signer information. |
michael@0 | 171 | signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); |
michael@0 | 172 | if (!signerInfo) { |
michael@0 | 173 | lastError = GetLastError(); |
michael@0 | 174 | goto cleanup; |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | // Get the signer information (PCMSG_SIGNER_INFO). |
michael@0 | 178 | // In particular we want the issuer and serial number. |
michael@0 | 179 | result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, |
michael@0 | 180 | (PVOID)signerInfo, &signerInfoSize); |
michael@0 | 181 | if (!result) { |
michael@0 | 182 | lastError = GetLastError(); |
michael@0 | 183 | goto cleanup; |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | // Search for the signer certificate in the certificate store. |
michael@0 | 187 | CERT_INFO certInfo; |
michael@0 | 188 | certInfo.Issuer = signerInfo->Issuer; |
michael@0 | 189 | certInfo.SerialNumber = signerInfo->SerialNumber; |
michael@0 | 190 | certContext = CertFindCertificateInStore(certStore, ENCODING, 0, |
michael@0 | 191 | CERT_FIND_SUBJECT_CERT, |
michael@0 | 192 | (PVOID)&certInfo, NULL); |
michael@0 | 193 | if (!certContext) { |
michael@0 | 194 | lastError = GetLastError(); |
michael@0 | 195 | goto cleanup; |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | if (!DoCertificateAttributesMatch(certContext, infoToMatch)) { |
michael@0 | 199 | lastError = ERROR_NOT_FOUND; |
michael@0 | 200 | goto cleanup; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | cleanup: |
michael@0 | 204 | if (signerInfo) { |
michael@0 | 205 | LocalFree(signerInfo); |
michael@0 | 206 | } |
michael@0 | 207 | if (certContext) { |
michael@0 | 208 | CertFreeCertificateContext(certContext); |
michael@0 | 209 | } |
michael@0 | 210 | if (certStore) { |
michael@0 | 211 | CertCloseStore(certStore, 0); |
michael@0 | 212 | } |
michael@0 | 213 | if (cryptMsg) { |
michael@0 | 214 | CryptMsgClose(cryptMsg); |
michael@0 | 215 | } |
michael@0 | 216 | return lastError; |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | /** |
michael@0 | 220 | * Compares the certificate name and issuer values for a signed file's with the |
michael@0 | 221 | * values provided. |
michael@0 | 222 | * |
michael@0 | 223 | * @param stacktop A pointer to the top of the stack. The stack should contain |
michael@0 | 224 | * from the top the file's path, the expected value for the |
michael@0 | 225 | * certificate's name attribute, and the expected value for |
michael@0 | 226 | * the certificate's issuer attribute. |
michael@0 | 227 | * @param variables A pointer to the NSIS variables |
michael@0 | 228 | * @return 1 if the certificate name and issuer attributes matched the expected |
michael@0 | 229 | * values, 0 if they don't match the expected values. |
michael@0 | 230 | */ |
michael@0 | 231 | extern "C" void __declspec(dllexport) |
michael@0 | 232 | VerifyCertNameIssuer(HWND hwndParent, int string_size, |
michael@0 | 233 | TCHAR *variables, stack_t **stacktop, void *extra) |
michael@0 | 234 | { |
michael@0 | 235 | TCHAR tmp1[MAX_PATH + 1] = { _T('\0') }; |
michael@0 | 236 | TCHAR tmp2[MAX_PATH + 1] = { _T('\0') }; |
michael@0 | 237 | TCHAR tmp3[MAX_PATH + 1] = { _T('\0') }; |
michael@0 | 238 | WCHAR filePath[MAX_PATH + 1] = { L'\0' }; |
michael@0 | 239 | WCHAR certName[MAX_PATH + 1] = { L'\0' }; |
michael@0 | 240 | WCHAR certIssuer[MAX_PATH + 1] = { L'\0' }; |
michael@0 | 241 | |
michael@0 | 242 | popstring(stacktop, tmp1, MAX_PATH); |
michael@0 | 243 | popstring(stacktop, tmp2, MAX_PATH); |
michael@0 | 244 | popstring(stacktop, tmp3, MAX_PATH); |
michael@0 | 245 | |
michael@0 | 246 | #if !defined(UNICODE) |
michael@0 | 247 | MultiByteToWideChar(CP_ACP, 0, tmp1, -1, filePath, MAX_PATH); |
michael@0 | 248 | MultiByteToWideChar(CP_ACP, 0, tmp2, -1, certName, MAX_PATH); |
michael@0 | 249 | MultiByteToWideChar(CP_ACP, 0, tmp3, -1, certIssuer, MAX_PATH); |
michael@0 | 250 | #else |
michael@0 | 251 | wcsncpy(filePath, tmp1, MAX_PATH); |
michael@0 | 252 | wcsncpy(certName, tmp2, MAX_PATH); |
michael@0 | 253 | wcsncpy(certIssuer, tmp3, MAX_PATH); |
michael@0 | 254 | #endif |
michael@0 | 255 | |
michael@0 | 256 | CertificateCheckInfo allowedCertificate = { |
michael@0 | 257 | certName, |
michael@0 | 258 | certIssuer, |
michael@0 | 259 | }; |
michael@0 | 260 | |
michael@0 | 261 | LONG retCode = CheckCertificateForPEFile(filePath, allowedCertificate); |
michael@0 | 262 | if (retCode == ERROR_SUCCESS) { |
michael@0 | 263 | pushstring(stacktop, TEXT("1"), 2); |
michael@0 | 264 | } else { |
michael@0 | 265 | pushstring(stacktop, TEXT("0"), 2); |
michael@0 | 266 | } |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | /** |
michael@0 | 270 | * Verifies the trust of a signed file's certificate. |
michael@0 | 271 | * |
michael@0 | 272 | * @param filePath The file path to check. |
michael@0 | 273 | * @return ERROR_SUCCESS if successful, or the last error code otherwise. |
michael@0 | 274 | */ |
michael@0 | 275 | DWORD |
michael@0 | 276 | VerifyCertificateTrustForFile(LPCWSTR filePath) |
michael@0 | 277 | { |
michael@0 | 278 | // Setup the file to check. |
michael@0 | 279 | WINTRUST_FILE_INFO fileToCheck; |
michael@0 | 280 | ZeroMemory(&fileToCheck, sizeof(fileToCheck)); |
michael@0 | 281 | fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); |
michael@0 | 282 | fileToCheck.pcwszFilePath = filePath; |
michael@0 | 283 | |
michael@0 | 284 | // Setup what to check, we want to check it is signed and trusted. |
michael@0 | 285 | WINTRUST_DATA trustData; |
michael@0 | 286 | ZeroMemory(&trustData, sizeof(trustData)); |
michael@0 | 287 | trustData.cbStruct = sizeof(trustData); |
michael@0 | 288 | trustData.pPolicyCallbackData = NULL; |
michael@0 | 289 | trustData.pSIPClientData = NULL; |
michael@0 | 290 | trustData.dwUIChoice = WTD_UI_NONE; |
michael@0 | 291 | trustData.fdwRevocationChecks = WTD_REVOKE_NONE; |
michael@0 | 292 | trustData.dwUnionChoice = WTD_CHOICE_FILE; |
michael@0 | 293 | trustData.dwStateAction = 0; |
michael@0 | 294 | trustData.hWVTStateData = NULL; |
michael@0 | 295 | trustData.pwszURLReference = NULL; |
michael@0 | 296 | // no UI |
michael@0 | 297 | trustData.dwUIContext = 0; |
michael@0 | 298 | trustData.pFile = &fileToCheck; |
michael@0 | 299 | |
michael@0 | 300 | GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; |
michael@0 | 301 | // Check if the file is signed by something that is trusted. |
michael@0 | 302 | LONG ret = WinVerifyTrust(NULL, &policyGUID, &trustData); |
michael@0 | 303 | return ret; |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | /** |
michael@0 | 307 | * Verifies the trust of a signed file's certificate. |
michael@0 | 308 | * |
michael@0 | 309 | * @param stacktop A pointer to the top of the stack. This should be the file |
michael@0 | 310 | * path for the file that will have its trust verified. |
michael@0 | 311 | * @param variables A pointer to the NSIS variables |
michael@0 | 312 | * @return 1 if the file's trust was verified successfully, 0 if it was not |
michael@0 | 313 | */ |
michael@0 | 314 | extern "C" void __declspec(dllexport) |
michael@0 | 315 | VerifyCertTrust(HWND hwndParent, int string_size, |
michael@0 | 316 | TCHAR *variables, stack_t **stacktop, void *extra) |
michael@0 | 317 | { |
michael@0 | 318 | TCHAR tmp[MAX_PATH + 1] = { _T('\0') }; |
michael@0 | 319 | WCHAR filePath[MAX_PATH + 1] = { L'\0' }; |
michael@0 | 320 | |
michael@0 | 321 | popstring(stacktop, tmp, MAX_PATH); |
michael@0 | 322 | |
michael@0 | 323 | #if !defined(UNICODE) |
michael@0 | 324 | MultiByteToWideChar(CP_ACP, 0, tmp, -1, filePath, MAX_PATH); |
michael@0 | 325 | #else |
michael@0 | 326 | wcsncpy(filePath, tmp, MAX_PATH); |
michael@0 | 327 | #endif |
michael@0 | 328 | |
michael@0 | 329 | LONG retCode = VerifyCertificateTrustForFile(filePath); |
michael@0 | 330 | if (retCode == ERROR_SUCCESS) { |
michael@0 | 331 | pushstring(stacktop, TEXT("1"), 2); |
michael@0 | 332 | } else { |
michael@0 | 333 | pushstring(stacktop, TEXT("0"), 2); |
michael@0 | 334 | } |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | BOOL WINAPI |
michael@0 | 338 | DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) |
michael@0 | 339 | { |
michael@0 | 340 | return TRUE; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | /** |
michael@0 | 344 | * Removes an element from the top of the NSIS stack |
michael@0 | 345 | * |
michael@0 | 346 | * @param stacktop A pointer to the top of the stack |
michael@0 | 347 | * @param str The string to pop to |
michael@0 | 348 | * @param len The max length |
michael@0 | 349 | * @return 0 on success |
michael@0 | 350 | */ |
michael@0 | 351 | int popstring(stack_t **stacktop, TCHAR *str, int len) |
michael@0 | 352 | { |
michael@0 | 353 | // Removes the element from the top of the stack and puts it in the buffer |
michael@0 | 354 | stack_t *th; |
michael@0 | 355 | if (!stacktop || !*stacktop) { |
michael@0 | 356 | return 1; |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | th = (*stacktop); |
michael@0 | 360 | lstrcpyn(str,th->text, len); |
michael@0 | 361 | *stacktop = th->next; |
michael@0 | 362 | GlobalFree((HGLOBAL)th); |
michael@0 | 363 | return 0; |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | /** |
michael@0 | 367 | * Adds an element to the top of the NSIS stack |
michael@0 | 368 | * |
michael@0 | 369 | * @param stacktop A pointer to the top of the stack |
michael@0 | 370 | * @param str The string to push on the stack |
michael@0 | 371 | * @param len The length of the string to push on the stack |
michael@0 | 372 | * @return 0 on success |
michael@0 | 373 | */ |
michael@0 | 374 | void pushstring(stack_t **stacktop, const TCHAR *str, int len) |
michael@0 | 375 | { |
michael@0 | 376 | stack_t *th; |
michael@0 | 377 | if (!stacktop) { |
michael@0 | 378 | return; |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | th = (stack_t*)GlobalAlloc(GPTR, sizeof(stack_t) + len); |
michael@0 | 382 | lstrcpyn(th->text, str, len); |
michael@0 | 383 | th->next = *stacktop; |
michael@0 | 384 | *stacktop = th; |
michael@0 | 385 | } |