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