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