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