1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // We need Windows 8 functions and structures to be able to verify SHA-256.
8 #if defined(_WIN32_WINNT)
9 # undef _WIN32_WINNT
10 # define _WIN32_WINNT _WIN32_WINNT_WIN8
11 #endif // defined(_WIN32_WINNT)
12 #if defined(NTDDI_VERSION)
13 # undef NTDDI_VERSION
14 # define NTDDI_VERSION NTDDI_WIN8
15 #endif // defined(NTDDI_VERSION)
16
17 #include "Authenticode.h"
18
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/Assertions.h"
21 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/UniquePtr.h"
24 #include "mozilla/WindowsVersion.h"
25 #include "nsWindowsHelpers.h"
26
27 #include <windows.h>
28 #include <softpub.h>
29 #include <wincrypt.h>
30 #include <wintrust.h>
31 #include <mscat.h>
32
33 #include <string.h>
34
35 namespace {
36
37 struct CertStoreDeleter {
38 typedef HCERTSTORE pointer;
operator ()__anonec013a610111::CertStoreDeleter39 void operator()(pointer aStore) { ::CertCloseStore(aStore, 0); }
40 };
41
42 struct CryptMsgDeleter {
43 typedef HCRYPTMSG pointer;
operator ()__anonec013a610111::CryptMsgDeleter44 void operator()(pointer aMsg) { ::CryptMsgClose(aMsg); }
45 };
46
47 struct CertContextDeleter {
operator ()__anonec013a610111::CertContextDeleter48 void operator()(PCCERT_CONTEXT aCertContext) {
49 ::CertFreeCertificateContext(aCertContext);
50 }
51 };
52
53 struct CATAdminContextDeleter {
54 typedef HCATADMIN pointer;
operator ()__anonec013a610111::CATAdminContextDeleter55 void operator()(pointer aCtx) {
56 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
57 decltype(&::CryptCATAdminReleaseContext)>
58 pCryptCATAdminReleaseContext(L"wintrust.dll",
59 "CryptCATAdminReleaseContext");
60
61 MOZ_ASSERT(!!pCryptCATAdminReleaseContext);
62 if (!pCryptCATAdminReleaseContext) {
63 return;
64 }
65
66 pCryptCATAdminReleaseContext(aCtx, 0);
67 }
68 };
69
70 typedef mozilla::UniquePtr<HCERTSTORE, CertStoreDeleter> CertStoreUniquePtr;
71 typedef mozilla::UniquePtr<HCRYPTMSG, CryptMsgDeleter> CryptMsgUniquePtr;
72 typedef mozilla::UniquePtr<const CERT_CONTEXT, CertContextDeleter>
73 CertContextUniquePtr;
74 typedef mozilla::UniquePtr<HCATADMIN, CATAdminContextDeleter>
75 CATAdminContextUniquePtr;
76
77 static const DWORD kEncodingTypes = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
78
79 class SignedBinary final {
80 public:
81 SignedBinary(const wchar_t* aFilePath, mozilla::AuthenticodeFlags aFlags);
82
operator bool() const83 explicit operator bool() const { return mCertStore && mCryptMsg && mCertCtx; }
84
85 mozilla::UniquePtr<wchar_t[]> GetOrgName();
86
87 SignedBinary(const SignedBinary&) = delete;
88 SignedBinary(SignedBinary&&) = delete;
89 SignedBinary& operator=(const SignedBinary&) = delete;
90 SignedBinary& operator=(SignedBinary&&) = delete;
91
92 private:
93 bool VerifySignature(const wchar_t* aFilePath);
94 bool QueryObject(const wchar_t* aFilePath);
95 static bool VerifySignatureInternal(WINTRUST_DATA& aTrustData);
96
97 private:
98 enum class TrustSource { eNone, eEmbedded, eCatalog };
99
100 private:
101 const mozilla::AuthenticodeFlags mFlags;
102 TrustSource mTrustSource;
103 CertStoreUniquePtr mCertStore;
104 CryptMsgUniquePtr mCryptMsg;
105 CertContextUniquePtr mCertCtx;
106 };
107
SignedBinary(const wchar_t * aFilePath,mozilla::AuthenticodeFlags aFlags)108 SignedBinary::SignedBinary(const wchar_t* aFilePath,
109 mozilla::AuthenticodeFlags aFlags)
110 : mFlags(aFlags), mTrustSource(TrustSource::eNone) {
111 if (!VerifySignature(aFilePath)) {
112 return;
113 }
114
115 DWORD certInfoLen = 0;
116 BOOL ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
117 nullptr, &certInfoLen);
118 if (!ok) {
119 return;
120 }
121
122 auto certInfoBuf = mozilla::MakeUnique<char[]>(certInfoLen);
123
124 ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
125 certInfoBuf.get(), &certInfoLen);
126 if (!ok) {
127 return;
128 }
129
130 auto certInfo = reinterpret_cast<CERT_INFO*>(certInfoBuf.get());
131
132 PCCERT_CONTEXT certCtx =
133 CertFindCertificateInStore(mCertStore.get(), kEncodingTypes, 0,
134 CERT_FIND_SUBJECT_CERT, certInfo, nullptr);
135 if (!certCtx) {
136 return;
137 }
138
139 mCertCtx.reset(certCtx);
140 }
141
QueryObject(const wchar_t * aFilePath)142 bool SignedBinary::QueryObject(const wchar_t* aFilePath) {
143 DWORD encodingType, contentType, formatType;
144 HCERTSTORE rawCertStore;
145 HCRYPTMSG rawCryptMsg;
146 BOOL result = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, aFilePath,
147 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
148 CERT_QUERY_FORMAT_FLAG_BINARY, 0,
149 &encodingType, &contentType, &formatType,
150 &rawCertStore, &rawCryptMsg, nullptr);
151 if (!result) {
152 return false;
153 }
154
155 mCertStore.reset(rawCertStore);
156 mCryptMsg.reset(rawCryptMsg);
157
158 return true;
159 }
160
161 /**
162 * @param aTrustData must be a WINTRUST_DATA structure that has been zeroed out
163 * and then populated at least with its |cbStruct|,
164 * |dwUnionChoice|, and appropriate union field. This function
165 * will then populate the remaining fields as appropriate.
166 */
167 /* static */
VerifySignatureInternal(WINTRUST_DATA & aTrustData)168 bool SignedBinary::VerifySignatureInternal(WINTRUST_DATA& aTrustData) {
169 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
170 decltype(&::WinVerifyTrust)>
171 pWinVerifyTrust(L"wintrust.dll", "WinVerifyTrust");
172 if (!pWinVerifyTrust) {
173 return false;
174 }
175
176 aTrustData.dwUIChoice = WTD_UI_NONE;
177 aTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
178 aTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
179 aTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
180
181 const HWND hwnd = (HWND)INVALID_HANDLE_VALUE;
182 GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
183 LONG result = pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
184
185 aTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
186 pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
187
188 return result == ERROR_SUCCESS;
189 }
190
VerifySignature(const wchar_t * aFilePath)191 bool SignedBinary::VerifySignature(const wchar_t* aFilePath) {
192 // First, try the binary itself
193 if (QueryObject(aFilePath)) {
194 mTrustSource = TrustSource::eEmbedded;
195 if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
196 return true;
197 }
198
199 WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)};
200 fileInfo.pcwszFilePath = aFilePath;
201
202 WINTRUST_DATA trustData = {sizeof(trustData)};
203 trustData.dwUnionChoice = WTD_CHOICE_FILE;
204 trustData.pFile = &fileInfo;
205
206 return VerifySignatureInternal(trustData);
207 }
208
209 // We didn't find anything in the binary, so now try a catalog file.
210
211 // First, we open a catalog admin context.
212 HCATADMIN rawCatAdmin;
213
214 // Windows 7 also exports the CryptCATAdminAcquireContext2 API, but it does
215 // *not* sign its binaries with SHA-256, so we use the old API in that case.
216 if (mozilla::IsWin8OrLater()) {
217 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
218 decltype(&::CryptCATAdminAcquireContext2)>
219 pCryptCATAdminAcquireContext2(L"wintrust.dll",
220 "CryptCATAdminAcquireContext2");
221 if (!pCryptCATAdminAcquireContext2) {
222 return false;
223 }
224
225 CERT_STRONG_SIGN_PARA policy = {sizeof(policy)};
226 policy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
227 policy.pszOID = const_cast<char*>(
228 szOID_CERT_STRONG_SIGN_OS_CURRENT); // -Wwritable-strings
229
230 if (!pCryptCATAdminAcquireContext2(&rawCatAdmin, nullptr,
231 BCRYPT_SHA256_ALGORITHM, &policy, 0)) {
232 return false;
233 }
234 } else {
235 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
236 decltype(&::CryptCATAdminAcquireContext)>
237 pCryptCATAdminAcquireContext(L"wintrust.dll",
238 "CryptCATAdminAcquireContext");
239
240 if (!pCryptCATAdminAcquireContext ||
241 !pCryptCATAdminAcquireContext(&rawCatAdmin, nullptr, 0)) {
242 return false;
243 }
244 }
245
246 CATAdminContextUniquePtr catAdmin(rawCatAdmin);
247
248 // Now we need to hash the file at aFilePath.
249 // Since we're hashing this file, let's open it with a sequential scan hint.
250 HANDLE rawFile =
251 ::CreateFileW(aFilePath, GENERIC_READ,
252 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
253 nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
254 if (rawFile == INVALID_HANDLE_VALUE) {
255 return false;
256 }
257
258 nsAutoHandle file(rawFile);
259 DWORD hashLen = 0;
260 mozilla::UniquePtr<BYTE[]> hashBuf;
261
262 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
263 decltype(&::CryptCATAdminCalcHashFromFileHandle2)>
264 pCryptCATAdminCalcHashFromFileHandle2(
265 L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle2");
266 if (pCryptCATAdminCalcHashFromFileHandle2) {
267 if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
268 nullptr, 0) &&
269 ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
270 return false;
271 }
272
273 hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
274
275 if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
276 hashBuf.get(), 0)) {
277 return false;
278 }
279 } else {
280 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
281 decltype(&::CryptCATAdminCalcHashFromFileHandle)>
282 pCryptCATAdminCalcHashFromFileHandle(
283 L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle");
284
285 if (!pCryptCATAdminCalcHashFromFileHandle) {
286 return false;
287 }
288
289 if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, nullptr, 0) &&
290 ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
291 return false;
292 }
293
294 hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
295
296 if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, hashBuf.get(),
297 0)) {
298 return false;
299 }
300 }
301
302 // Now that we've hashed the file, query the catalog system to see if any
303 // catalogs reference a binary with our hash.
304
305 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
306 decltype(&::CryptCATAdminEnumCatalogFromHash)>
307 pCryptCATAdminEnumCatalogFromHash(L"wintrust.dll",
308 "CryptCATAdminEnumCatalogFromHash");
309 if (!pCryptCATAdminEnumCatalogFromHash) {
310 return false;
311 }
312
313 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
314 decltype(&::CryptCATAdminReleaseCatalogContext)>
315 pCryptCATAdminReleaseCatalogContext(L"wintrust.dll",
316 "CryptCATAdminReleaseCatalogContext");
317 if (!pCryptCATAdminReleaseCatalogContext) {
318 return false;
319 }
320
321 HCATINFO catInfoHdl = pCryptCATAdminEnumCatalogFromHash(
322 rawCatAdmin, hashBuf.get(), hashLen, 0, nullptr);
323 if (!catInfoHdl) {
324 return false;
325 }
326
327 // We can't use UniquePtr for this because the deleter function requires two
328 // parameters.
329 auto cleanCatInfoHdl =
330 mozilla::MakeScopeExit([rawCatAdmin, catInfoHdl]() -> void {
331 pCryptCATAdminReleaseCatalogContext(rawCatAdmin, catInfoHdl, 0);
332 });
333
334 // We found a catalog! Now query for the path to the catalog file.
335
336 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
337 decltype(&::CryptCATCatalogInfoFromContext)>
338 pCryptCATCatalogInfoFromContext(L"wintrust.dll",
339 "CryptCATCatalogInfoFromContext");
340 if (!pCryptCATCatalogInfoFromContext) {
341 return false;
342 }
343
344 CATALOG_INFO_ catInfo = {sizeof(catInfo)};
345 if (!pCryptCATCatalogInfoFromContext(catInfoHdl, &catInfo, 0)) {
346 return false;
347 }
348
349 if (!QueryObject(catInfo.wszCatalogFile)) {
350 return false;
351 }
352
353 mTrustSource = TrustSource::eCatalog;
354
355 if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
356 return true;
357 }
358
359 // WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string
360 // representation of the file hash, so we build that here.
361
362 DWORD strHashBufLen = (hashLen * 2) + 1;
363 auto strHashBuf = mozilla::MakeUnique<wchar_t[]>(strHashBufLen);
364 if (!::CryptBinaryToStringW(hashBuf.get(), hashLen,
365 CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF,
366 strHashBuf.get(), &strHashBufLen)) {
367 return false;
368 }
369
370 // Ensure that the tag is uppercase for WinVerifyTrust
371 // NB: CryptBinaryToStringW overwrites strHashBufLen with the length excluding
372 // the null terminator, so we need to add it back for this call.
373 if (_wcsupr_s(strHashBuf.get(), strHashBufLen + 1)) {
374 return false;
375 }
376
377 // Now, given the path to the catalog, and the path to the member (ie, the
378 // binary whose hash we are validating), we may now validate. If the
379 // validation is successful, we then QueryObject on the *catalog file*
380 // instead of the binary.
381
382 WINTRUST_CATALOG_INFO wtCatInfo = {sizeof(wtCatInfo)};
383 wtCatInfo.pcwszCatalogFilePath = catInfo.wszCatalogFile;
384 wtCatInfo.pcwszMemberTag = strHashBuf.get();
385 wtCatInfo.pcwszMemberFilePath = aFilePath;
386 wtCatInfo.hMemberFile = rawFile;
387 if (mozilla::IsWin8OrLater()) {
388 wtCatInfo.hCatAdmin = rawCatAdmin;
389 }
390
391 WINTRUST_DATA trustData = {sizeof(trustData)};
392 trustData.dwUnionChoice = WTD_CHOICE_CATALOG;
393 trustData.pCatalog = &wtCatInfo;
394
395 return VerifySignatureInternal(trustData);
396 }
397
GetOrgName()398 mozilla::UniquePtr<wchar_t[]> SignedBinary::GetOrgName() {
399 DWORD charCount = CertGetNameStringW(
400 mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0);
401 if (charCount <= 1) {
402 // Not found
403 return nullptr;
404 }
405
406 auto result = mozilla::MakeUnique<wchar_t[]>(charCount);
407 charCount = CertGetNameStringW(mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE,
408 0, nullptr, result.get(), charCount);
409 MOZ_ASSERT(charCount > 1);
410
411 return result;
412 }
413
414 } // anonymous namespace
415
416 namespace mozilla {
417
418 class AuthenticodeImpl : public Authenticode {
419 public:
420 virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
421 const wchar_t* aFilePath,
422 AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override;
423 };
424
GetBinaryOrgName(const wchar_t * aFilePath,AuthenticodeFlags aFlags)425 UniquePtr<wchar_t[]> AuthenticodeImpl::GetBinaryOrgName(
426 const wchar_t* aFilePath, AuthenticodeFlags aFlags) {
427 SignedBinary bin(aFilePath, aFlags);
428 if (!bin) {
429 return nullptr;
430 }
431
432 return bin.GetOrgName();
433 }
434
435 static AuthenticodeImpl sAuthenticodeImpl;
436
GetAuthenticode()437 Authenticode* GetAuthenticode() { return &sAuthenticodeImpl; }
438
439 } // namespace mozilla
440