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 ()__anon2b30b1da0111::CertStoreDeleter39 void operator()(pointer aStore) { ::CertCloseStore(aStore, 0); }
40 };
41
42 struct CryptMsgDeleter {
43 typedef HCRYPTMSG pointer;
operator ()__anon2b30b1da0111::CryptMsgDeleter44 void operator()(pointer aMsg) { ::CryptMsgClose(aMsg); }
45 };
46
47 struct CertContextDeleter {
operator ()__anon2b30b1da0111::CertContextDeleter48 void operator()(PCCERT_CONTEXT aCertContext) {
49 ::CertFreeCertificateContext(aCertContext);
50 }
51 };
52
53 struct CATAdminContextDeleter {
54 typedef HCATADMIN pointer;
operator ()__anon2b30b1da0111::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 aTrustData.dwUIChoice = WTD_UI_NONE;
170 aTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
171 aTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
172 aTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
173
174 const HWND hwnd = (HWND)INVALID_HANDLE_VALUE;
175 GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
176 LONG result = ::WinVerifyTrust(hwnd, &policyGUID, &aTrustData);
177
178 aTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
179 ::WinVerifyTrust(hwnd, &policyGUID, &aTrustData);
180
181 return result == ERROR_SUCCESS;
182 }
183
VerifySignature(const wchar_t * aFilePath)184 bool SignedBinary::VerifySignature(const wchar_t* aFilePath) {
185 // First, try the binary itself
186 if (QueryObject(aFilePath)) {
187 mTrustSource = TrustSource::eEmbedded;
188 if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
189 return true;
190 }
191
192 WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)};
193 fileInfo.pcwszFilePath = aFilePath;
194
195 WINTRUST_DATA trustData = {sizeof(trustData)};
196 trustData.dwUnionChoice = WTD_CHOICE_FILE;
197 trustData.pFile = &fileInfo;
198
199 return VerifySignatureInternal(trustData);
200 }
201
202 // We didn't find anything in the binary, so now try a catalog file.
203
204 // First, we open a catalog admin context.
205 HCATADMIN rawCatAdmin;
206
207 // Windows 7 also exports the CryptCATAdminAcquireContext2 API, but it does
208 // *not* sign its binaries with SHA-256, so we use the old API in that case.
209 if (mozilla::IsWin8OrLater()) {
210 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
211 decltype(&::CryptCATAdminAcquireContext2)>
212 pCryptCATAdminAcquireContext2(L"wintrust.dll",
213 "CryptCATAdminAcquireContext2");
214 if (!pCryptCATAdminAcquireContext2) {
215 return false;
216 }
217
218 CERT_STRONG_SIGN_PARA policy = {sizeof(policy)};
219 policy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
220 policy.pszOID = const_cast<char*>(
221 szOID_CERT_STRONG_SIGN_OS_CURRENT); // -Wwritable-strings
222
223 if (!pCryptCATAdminAcquireContext2(&rawCatAdmin, nullptr,
224 BCRYPT_SHA256_ALGORITHM, &policy, 0)) {
225 return false;
226 }
227 } else {
228 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
229 decltype(&::CryptCATAdminAcquireContext)>
230 pCryptCATAdminAcquireContext(L"wintrust.dll",
231 "CryptCATAdminAcquireContext");
232
233 if (!pCryptCATAdminAcquireContext ||
234 !pCryptCATAdminAcquireContext(&rawCatAdmin, nullptr, 0)) {
235 return false;
236 }
237 }
238
239 CATAdminContextUniquePtr catAdmin(rawCatAdmin);
240
241 // Now we need to hash the file at aFilePath.
242 // Since we're hashing this file, let's open it with a sequential scan hint.
243 HANDLE rawFile =
244 ::CreateFileW(aFilePath, GENERIC_READ,
245 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
246 nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
247 if (rawFile == INVALID_HANDLE_VALUE) {
248 return false;
249 }
250
251 nsAutoHandle file(rawFile);
252 DWORD hashLen = 0;
253 mozilla::UniquePtr<BYTE[]> hashBuf;
254
255 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
256 decltype(&::CryptCATAdminCalcHashFromFileHandle2)>
257 pCryptCATAdminCalcHashFromFileHandle2(
258 L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle2");
259 if (pCryptCATAdminCalcHashFromFileHandle2) {
260 if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
261 nullptr, 0) &&
262 ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
263 return false;
264 }
265
266 hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
267
268 if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
269 hashBuf.get(), 0)) {
270 return false;
271 }
272 } else {
273 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
274 decltype(&::CryptCATAdminCalcHashFromFileHandle)>
275 pCryptCATAdminCalcHashFromFileHandle(
276 L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle");
277
278 if (!pCryptCATAdminCalcHashFromFileHandle) {
279 return false;
280 }
281
282 if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, nullptr, 0) &&
283 ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
284 return false;
285 }
286
287 hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
288
289 if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, hashBuf.get(),
290 0)) {
291 return false;
292 }
293 }
294
295 // Now that we've hashed the file, query the catalog system to see if any
296 // catalogs reference a binary with our hash.
297
298 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
299 decltype(&::CryptCATAdminEnumCatalogFromHash)>
300 pCryptCATAdminEnumCatalogFromHash(L"wintrust.dll",
301 "CryptCATAdminEnumCatalogFromHash");
302 if (!pCryptCATAdminEnumCatalogFromHash) {
303 return false;
304 }
305
306 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
307 decltype(&::CryptCATAdminReleaseCatalogContext)>
308 pCryptCATAdminReleaseCatalogContext(L"wintrust.dll",
309 "CryptCATAdminReleaseCatalogContext");
310 if (!pCryptCATAdminReleaseCatalogContext) {
311 return false;
312 }
313
314 HCATINFO catInfoHdl = pCryptCATAdminEnumCatalogFromHash(
315 rawCatAdmin, hashBuf.get(), hashLen, 0, nullptr);
316 if (!catInfoHdl) {
317 return false;
318 }
319
320 // We can't use UniquePtr for this because the deleter function requires two
321 // parameters.
322 auto cleanCatInfoHdl =
323 mozilla::MakeScopeExit([rawCatAdmin, catInfoHdl]() -> void {
324 pCryptCATAdminReleaseCatalogContext(rawCatAdmin, catInfoHdl, 0);
325 });
326
327 // We found a catalog! Now query for the path to the catalog file.
328
329 static const mozilla::StaticDynamicallyLinkedFunctionPtr<
330 decltype(&::CryptCATCatalogInfoFromContext)>
331 pCryptCATCatalogInfoFromContext(L"wintrust.dll",
332 "CryptCATCatalogInfoFromContext");
333 if (!pCryptCATCatalogInfoFromContext) {
334 return false;
335 }
336
337 CATALOG_INFO_ catInfo = {sizeof(catInfo)};
338 if (!pCryptCATCatalogInfoFromContext(catInfoHdl, &catInfo, 0)) {
339 return false;
340 }
341
342 if (!QueryObject(catInfo.wszCatalogFile)) {
343 return false;
344 }
345
346 mTrustSource = TrustSource::eCatalog;
347
348 if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
349 return true;
350 }
351
352 // WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string
353 // representation of the file hash, so we build that here.
354
355 DWORD strHashBufLen = (hashLen * 2) + 1;
356 auto strHashBuf = mozilla::MakeUnique<wchar_t[]>(strHashBufLen);
357 if (!::CryptBinaryToStringW(hashBuf.get(), hashLen,
358 CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF,
359 strHashBuf.get(), &strHashBufLen)) {
360 return false;
361 }
362
363 // Ensure that the tag is uppercase for WinVerifyTrust
364 // NB: CryptBinaryToStringW overwrites strHashBufLen with the length excluding
365 // the null terminator, so we need to add it back for this call.
366 if (_wcsupr_s(strHashBuf.get(), strHashBufLen + 1)) {
367 return false;
368 }
369
370 // Now, given the path to the catalog, and the path to the member (ie, the
371 // binary whose hash we are validating), we may now validate. If the
372 // validation is successful, we then QueryObject on the *catalog file*
373 // instead of the binary.
374
375 WINTRUST_CATALOG_INFO wtCatInfo = {sizeof(wtCatInfo)};
376 wtCatInfo.pcwszCatalogFilePath = catInfo.wszCatalogFile;
377 wtCatInfo.pcwszMemberTag = strHashBuf.get();
378 wtCatInfo.pcwszMemberFilePath = aFilePath;
379 wtCatInfo.hMemberFile = rawFile;
380 if (mozilla::IsWin8OrLater()) {
381 wtCatInfo.hCatAdmin = rawCatAdmin;
382 }
383
384 WINTRUST_DATA trustData = {sizeof(trustData)};
385 trustData.dwUnionChoice = WTD_CHOICE_CATALOG;
386 trustData.pCatalog = &wtCatInfo;
387
388 return VerifySignatureInternal(trustData);
389 }
390
GetOrgName()391 mozilla::UniquePtr<wchar_t[]> SignedBinary::GetOrgName() {
392 DWORD charCount = CertGetNameStringW(
393 mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0);
394 if (charCount <= 1) {
395 // Not found
396 return nullptr;
397 }
398
399 auto result = mozilla::MakeUnique<wchar_t[]>(charCount);
400 charCount = CertGetNameStringW(mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE,
401 0, nullptr, result.get(), charCount);
402 MOZ_ASSERT(charCount > 1);
403
404 return result;
405 }
406
407 } // anonymous namespace
408
409 namespace mozilla {
410
411 class AuthenticodeImpl : public Authenticode {
412 public:
413 virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
414 const wchar_t* aFilePath,
415 AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override;
416 };
417
GetBinaryOrgName(const wchar_t * aFilePath,AuthenticodeFlags aFlags)418 UniquePtr<wchar_t[]> AuthenticodeImpl::GetBinaryOrgName(
419 const wchar_t* aFilePath, AuthenticodeFlags aFlags) {
420 SignedBinary bin(aFilePath, aFlags);
421 if (!bin) {
422 return nullptr;
423 }
424
425 return bin.GetOrgName();
426 }
427
428 static AuthenticodeImpl sAuthenticodeImpl;
429
GetAuthenticode()430 Authenticode* GetAuthenticode() { return &sAuthenticodeImpl; }
431
432 } // namespace mozilla
433