|
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 #include "certificatecheck.h" |
|
12 #include "servicebase.h" |
|
13 |
|
14 #pragma comment(lib, "wintrust.lib") |
|
15 #pragma comment(lib, "crypt32.lib") |
|
16 |
|
17 static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; |
|
18 |
|
19 /** |
|
20 * Checks to see if a file stored at filePath matches the specified info. |
|
21 * |
|
22 * @param filePath The PE file path to check |
|
23 * @param infoToMatch The acceptable information to match |
|
24 * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info |
|
25 * does not match, or the last error otherwise. |
|
26 */ |
|
27 DWORD |
|
28 CheckCertificateForPEFile(LPCWSTR filePath, |
|
29 CertificateCheckInfo &infoToMatch) |
|
30 { |
|
31 HCERTSTORE certStore = nullptr; |
|
32 HCRYPTMSG cryptMsg = nullptr; |
|
33 PCCERT_CONTEXT certContext = nullptr; |
|
34 PCMSG_SIGNER_INFO signerInfo = nullptr; |
|
35 DWORD lastError = ERROR_SUCCESS; |
|
36 |
|
37 // Get the HCERTSTORE and HCRYPTMSG from the signed file. |
|
38 DWORD encoding, contentType, formatType; |
|
39 BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, |
|
40 filePath, |
|
41 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, |
|
42 CERT_QUERY_CONTENT_FLAG_ALL, |
|
43 0, &encoding, &contentType, |
|
44 &formatType, &certStore, &cryptMsg, nullptr); |
|
45 if (!result) { |
|
46 lastError = GetLastError(); |
|
47 LOG_WARN(("CryptQueryObject failed. (%d)", lastError)); |
|
48 goto cleanup; |
|
49 } |
|
50 |
|
51 // Pass in nullptr to get the needed signer information size. |
|
52 DWORD signerInfoSize; |
|
53 result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, |
|
54 nullptr, &signerInfoSize); |
|
55 if (!result) { |
|
56 lastError = GetLastError(); |
|
57 LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); |
|
58 goto cleanup; |
|
59 } |
|
60 |
|
61 // Allocate the needed size for the signer information. |
|
62 signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); |
|
63 if (!signerInfo) { |
|
64 lastError = GetLastError(); |
|
65 LOG_WARN(("Unable to allocate memory for Signer Info. (%d)", lastError)); |
|
66 goto cleanup; |
|
67 } |
|
68 |
|
69 // Get the signer information (PCMSG_SIGNER_INFO). |
|
70 // In particular we want the issuer and serial number. |
|
71 result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, |
|
72 (PVOID)signerInfo, &signerInfoSize); |
|
73 if (!result) { |
|
74 lastError = GetLastError(); |
|
75 LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); |
|
76 goto cleanup; |
|
77 } |
|
78 |
|
79 // Search for the signer certificate in the certificate store. |
|
80 CERT_INFO certInfo; |
|
81 certInfo.Issuer = signerInfo->Issuer; |
|
82 certInfo.SerialNumber = signerInfo->SerialNumber; |
|
83 certContext = CertFindCertificateInStore(certStore, ENCODING, 0, |
|
84 CERT_FIND_SUBJECT_CERT, |
|
85 (PVOID)&certInfo, nullptr); |
|
86 if (!certContext) { |
|
87 lastError = GetLastError(); |
|
88 LOG_WARN(("CertFindCertificateInStore failed. (%d)", lastError)); |
|
89 goto cleanup; |
|
90 } |
|
91 |
|
92 if (!DoCertificateAttributesMatch(certContext, infoToMatch)) { |
|
93 lastError = ERROR_NOT_FOUND; |
|
94 LOG_WARN(("Certificate did not match issuer or name. (%d)", lastError)); |
|
95 goto cleanup; |
|
96 } |
|
97 |
|
98 cleanup: |
|
99 if (signerInfo) { |
|
100 LocalFree(signerInfo); |
|
101 } |
|
102 if (certContext) { |
|
103 CertFreeCertificateContext(certContext); |
|
104 } |
|
105 if (certStore) { |
|
106 CertCloseStore(certStore, 0); |
|
107 } |
|
108 if (cryptMsg) { |
|
109 CryptMsgClose(cryptMsg); |
|
110 } |
|
111 return lastError; |
|
112 } |
|
113 |
|
114 /** |
|
115 * Checks to see if a file stored at filePath matches the specified info. |
|
116 * |
|
117 * @param certContext The certificate context of the file |
|
118 * @param infoToMatch The acceptable information to match |
|
119 * @return FALSE if the info does not match or if any error occurs in the check |
|
120 */ |
|
121 BOOL |
|
122 DoCertificateAttributesMatch(PCCERT_CONTEXT certContext, |
|
123 CertificateCheckInfo &infoToMatch) |
|
124 { |
|
125 DWORD dwData; |
|
126 LPTSTR szName = nullptr; |
|
127 |
|
128 if (infoToMatch.issuer) { |
|
129 // Pass in nullptr to get the needed size of the issuer buffer. |
|
130 dwData = CertGetNameString(certContext, |
|
131 CERT_NAME_SIMPLE_DISPLAY_TYPE, |
|
132 CERT_NAME_ISSUER_FLAG, nullptr, |
|
133 nullptr, 0); |
|
134 |
|
135 if (!dwData) { |
|
136 LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); |
|
137 return FALSE; |
|
138 } |
|
139 |
|
140 // Allocate memory for Issuer name buffer. |
|
141 LPTSTR szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); |
|
142 if (!szName) { |
|
143 LOG_WARN(("Unable to allocate memory for issuer name. (%d)", |
|
144 GetLastError())); |
|
145 return FALSE; |
|
146 } |
|
147 |
|
148 // Get Issuer name. |
|
149 if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, |
|
150 CERT_NAME_ISSUER_FLAG, nullptr, szName, dwData)) { |
|
151 LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); |
|
152 LocalFree(szName); |
|
153 return FALSE; |
|
154 } |
|
155 |
|
156 // If the issuer does not match, return a failure. |
|
157 if (!infoToMatch.issuer || |
|
158 wcscmp(szName, infoToMatch.issuer)) { |
|
159 LocalFree(szName); |
|
160 return FALSE; |
|
161 } |
|
162 |
|
163 LocalFree(szName); |
|
164 szName = nullptr; |
|
165 } |
|
166 |
|
167 if (infoToMatch.name) { |
|
168 // Pass in nullptr to get the needed size of the name buffer. |
|
169 dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, |
|
170 0, nullptr, nullptr, 0); |
|
171 if (!dwData) { |
|
172 LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); |
|
173 return FALSE; |
|
174 } |
|
175 |
|
176 // Allocate memory for the name buffer. |
|
177 szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); |
|
178 if (!szName) { |
|
179 LOG_WARN(("Unable to allocate memory for subject name. (%d)", |
|
180 GetLastError())); |
|
181 return FALSE; |
|
182 } |
|
183 |
|
184 // Obtain the name. |
|
185 if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, |
|
186 nullptr, szName, dwData))) { |
|
187 LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); |
|
188 LocalFree(szName); |
|
189 return FALSE; |
|
190 } |
|
191 |
|
192 // If the issuer does not match, return a failure. |
|
193 if (!infoToMatch.name || |
|
194 wcscmp(szName, infoToMatch.name)) { |
|
195 LocalFree(szName); |
|
196 return FALSE; |
|
197 } |
|
198 |
|
199 // We have a match! |
|
200 LocalFree(szName); |
|
201 } |
|
202 |
|
203 // If there were any errors we would have aborted by now. |
|
204 return TRUE; |
|
205 } |
|
206 |
|
207 /** |
|
208 * Duplicates the specified string |
|
209 * |
|
210 * @param inputString The string to duplicate |
|
211 * @return The duplicated string which should be freed by the caller. |
|
212 */ |
|
213 LPWSTR |
|
214 AllocateAndCopyWideString(LPCWSTR inputString) |
|
215 { |
|
216 LPWSTR outputString = |
|
217 (LPWSTR)LocalAlloc(LPTR, (wcslen(inputString) + 1) * sizeof(WCHAR)); |
|
218 if (outputString) { |
|
219 lstrcpyW(outputString, inputString); |
|
220 } |
|
221 return outputString; |
|
222 } |
|
223 |
|
224 /** |
|
225 * Verifies the trust of the specified file path. |
|
226 * |
|
227 * @param filePath The file path to check. |
|
228 * @return ERROR_SUCCESS if successful, or the last error code otherwise. |
|
229 */ |
|
230 DWORD |
|
231 VerifyCertificateTrustForFile(LPCWSTR filePath) |
|
232 { |
|
233 // Setup the file to check. |
|
234 WINTRUST_FILE_INFO fileToCheck; |
|
235 ZeroMemory(&fileToCheck, sizeof(fileToCheck)); |
|
236 fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); |
|
237 fileToCheck.pcwszFilePath = filePath; |
|
238 |
|
239 // Setup what to check, we want to check it is signed and trusted. |
|
240 WINTRUST_DATA trustData; |
|
241 ZeroMemory(&trustData, sizeof(trustData)); |
|
242 trustData.cbStruct = sizeof(trustData); |
|
243 trustData.pPolicyCallbackData = nullptr; |
|
244 trustData.pSIPClientData = nullptr; |
|
245 trustData.dwUIChoice = WTD_UI_NONE; |
|
246 trustData.fdwRevocationChecks = WTD_REVOKE_NONE; |
|
247 trustData.dwUnionChoice = WTD_CHOICE_FILE; |
|
248 trustData.dwStateAction = 0; |
|
249 trustData.hWVTStateData = nullptr; |
|
250 trustData.pwszURLReference = nullptr; |
|
251 // no UI |
|
252 trustData.dwUIContext = 0; |
|
253 trustData.pFile = &fileToCheck; |
|
254 |
|
255 GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; |
|
256 // Check if the file is signed by something that is trusted. |
|
257 LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); |
|
258 if (ERROR_SUCCESS == ret) { |
|
259 // The hash that represents the subject is trusted and there were no |
|
260 // verification errors. No publisher nor time stamp chain errors. |
|
261 LOG(("The file \"%ls\" is signed and the signature was verified.", |
|
262 filePath)); |
|
263 return ERROR_SUCCESS; |
|
264 } |
|
265 |
|
266 DWORD lastError = GetLastError(); |
|
267 LOG_WARN(("There was an error validating trust of the certificate for file" |
|
268 " \"%ls\". Returned: %d. (%d)", filePath, ret, lastError)); |
|
269 return ret; |
|
270 } |