other-licenses/nsis/Contrib/CertCheck/CertCheck.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     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/. */
     5 #include <stdio.h>
     6 #include <stdlib.h>
     7 #include <windows.h>
     8 #include <softpub.h>
     9 #include <wintrust.h>
    11 #pragma comment(lib, "wintrust.lib")
    12 #pragma comment(lib, "crypt32.lib")
    14 #ifdef UNICODE
    16 #ifndef _T
    17 #define __T(x)   L ## x
    18 #define _T(x)    __T(x)
    19 #define _TEXT(x) __T(x)
    20 #endif
    22 #else
    24 #ifndef _T
    25 #define _T(x)    x
    26 #define _TEXT(x) x
    27 #endif
    29 #endif // UNICODE
    31 static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    33 typedef struct _stack_t {
    34   struct _stack_t *next;
    35   TCHAR text[MAX_PATH];
    36 } stack_t;
    38 int popstring(stack_t **stacktop, LPTSTR str, int len);
    39 void pushstring(stack_t **stacktop, LPCTSTR str, int len);
    41 struct CertificateCheckInfo
    42 {
    43   LPCWSTR name;
    44   LPCWSTR issuer;
    45 };
    47 /**
    48  * Checks to see if a file stored at filePath matches the specified info. This
    49  * only supports the name and issuer attributes currently.
    50  *
    51  * @param  certContext  The certificate context of the file
    52  * @param  infoToMatch  The acceptable information to match
    53  * @return FALSE if the info does not match or if any error occurs in the check
    54  */
    55 BOOL
    56 DoCertificateAttributesMatch(PCCERT_CONTEXT certContext,
    57                              CertificateCheckInfo &infoToMatch)
    58 {
    59   DWORD dwData;
    60   LPTSTR szName = NULL;
    62   // Pass in NULL to get the needed size of the issuer buffer.
    63   dwData = CertGetNameString(certContext,
    64                              CERT_NAME_SIMPLE_DISPLAY_TYPE,
    65                              CERT_NAME_ISSUER_FLAG, NULL,
    66                              NULL, 0);
    68   if (!dwData) {
    69     return FALSE;
    70   }
    72   // Allocate memory for Issuer name buffer.
    73   szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
    74   if (!szName) {
    75     return FALSE;
    76   }
    78   // Get Issuer name.
    79   if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
    80                          CERT_NAME_ISSUER_FLAG, NULL, szName, dwData)) {
    81     LocalFree(szName);
    82     return FALSE;
    83   }
    85   // If the issuer does not match, return a failure.
    86   if (!infoToMatch.issuer ||
    87       wcscmp(szName, infoToMatch.issuer)) {
    88     LocalFree(szName);
    89     return FALSE;
    90   }
    92   LocalFree(szName);
    93   szName = NULL;
    95   // Pass in NULL to get the needed size of the name buffer.
    96   dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
    97                              0, NULL, NULL, 0);
    98   if (!dwData) {
    99     return FALSE;
   100   }
   102   // Allocate memory for the name buffer.
   103   szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
   104   if (!szName) {
   105     return FALSE;
   106   }
   108   // Obtain the name.
   109   if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
   110                           NULL, szName, dwData))) {
   111     LocalFree(szName);
   112     return FALSE;
   113   }
   115   // If the issuer does not match, return a failure.
   116   if (!infoToMatch.name ||
   117       wcscmp(szName, infoToMatch.name)) {
   118     LocalFree(szName);
   119     return FALSE;
   120   }
   122   // We have a match!
   123   LocalFree(szName);
   125   // If there were any errors we would have aborted by now.
   126   return TRUE;
   127 }
   129 /**
   130  * Checks to see if a file stored at filePath matches the specified info. This
   131  * only supports the name and issuer attributes currently.
   132  *
   133  * @param  filePath    The PE file path to check
   134  * @param  infoToMatch The acceptable information to match
   135  * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info
   136  *         does not match, or the last error otherwise.
   137  */
   138 DWORD
   139 CheckCertificateForPEFile(LPCWSTR filePath,
   140                           CertificateCheckInfo &infoToMatch)
   141 {
   142   HCERTSTORE certStore = NULL;
   143   HCRYPTMSG cryptMsg = NULL;
   144   PCCERT_CONTEXT certContext = NULL;
   145   PCMSG_SIGNER_INFO signerInfo = NULL;
   146   DWORD lastError = ERROR_SUCCESS;
   148   // Get the HCERTSTORE and HCRYPTMSG from the signed file.
   149   DWORD encoding, contentType, formatType;
   150   BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
   151                                  filePath,
   152                                  CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
   153                                  CERT_QUERY_CONTENT_FLAG_ALL,
   154                                  0, &encoding, &contentType,
   155                                  &formatType, &certStore, &cryptMsg, NULL);
   156   if (!result) {
   157     lastError = GetLastError();
   158     goto cleanup;
   159   }
   161   // Pass in NULL to get the needed signer information size.
   162   DWORD signerInfoSize;
   163   result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0,
   164                             NULL, &signerInfoSize);
   165   if (!result) {
   166     lastError = GetLastError();
   167     goto cleanup;
   168   }
   170   // Allocate the needed size for the signer information.
   171   signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize);
   172   if (!signerInfo) {
   173     lastError = GetLastError();
   174     goto cleanup;
   175   }
   177   // Get the signer information (PCMSG_SIGNER_INFO).
   178   // In particular we want the issuer and serial number.
   179   result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0,
   180                             (PVOID)signerInfo, &signerInfoSize);
   181   if (!result) {
   182     lastError = GetLastError();
   183     goto cleanup;
   184   }
   186   // Search for the signer certificate in the certificate store.
   187   CERT_INFO certInfo;
   188   certInfo.Issuer = signerInfo->Issuer;
   189   certInfo.SerialNumber = signerInfo->SerialNumber;
   190   certContext = CertFindCertificateInStore(certStore, ENCODING, 0,
   191                                            CERT_FIND_SUBJECT_CERT,
   192                                            (PVOID)&certInfo, NULL);
   193   if (!certContext) {
   194     lastError = GetLastError();
   195     goto cleanup;
   196   }
   198   if (!DoCertificateAttributesMatch(certContext, infoToMatch)) {
   199     lastError = ERROR_NOT_FOUND;
   200     goto cleanup;
   201   }
   203 cleanup:
   204   if (signerInfo) {
   205     LocalFree(signerInfo);
   206   }
   207   if (certContext) {
   208     CertFreeCertificateContext(certContext);
   209   }
   210   if (certStore) {
   211     CertCloseStore(certStore, 0);
   212   }
   213   if (cryptMsg) {
   214     CryptMsgClose(cryptMsg);
   215   }
   216   return lastError;
   217 }
   219 /**
   220  * Compares the certificate name and issuer values for a signed file's with the
   221  * values provided.
   222  *
   223  * @param  stacktop  A pointer to the top of the stack. The stack should contain
   224  *                   from the top the file's path, the expected value for the
   225  *                   certificate's name attribute, and the expected value for
   226  *                   the certificate's issuer attribute.
   227  * @param  variables A pointer to the NSIS variables
   228  * @return 1 if the certificate name and issuer attributes matched the expected
   229  *         values, 0 if they don't match the expected values.
   230  */
   231 extern "C" void __declspec(dllexport)
   232 VerifyCertNameIssuer(HWND hwndParent, int string_size,
   233                TCHAR *variables, stack_t **stacktop, void *extra)
   234 {
   235   TCHAR tmp1[MAX_PATH + 1] = { _T('\0') };
   236   TCHAR tmp2[MAX_PATH + 1] = { _T('\0') };
   237   TCHAR tmp3[MAX_PATH + 1] = { _T('\0') };
   238   WCHAR filePath[MAX_PATH + 1] = { L'\0' };
   239   WCHAR certName[MAX_PATH + 1] = { L'\0' };
   240   WCHAR certIssuer[MAX_PATH + 1] = { L'\0' };
   242   popstring(stacktop, tmp1, MAX_PATH);
   243   popstring(stacktop, tmp2, MAX_PATH);
   244   popstring(stacktop, tmp3, MAX_PATH);
   246 #if !defined(UNICODE)
   247     MultiByteToWideChar(CP_ACP, 0, tmp1, -1, filePath, MAX_PATH);
   248     MultiByteToWideChar(CP_ACP, 0, tmp2, -1, certName, MAX_PATH);
   249     MultiByteToWideChar(CP_ACP, 0, tmp3, -1, certIssuer, MAX_PATH);
   250 #else
   251     wcsncpy(filePath, tmp1, MAX_PATH);
   252     wcsncpy(certName, tmp2, MAX_PATH);
   253     wcsncpy(certIssuer, tmp3, MAX_PATH);
   254 #endif
   256   CertificateCheckInfo allowedCertificate = {
   257     certName,
   258     certIssuer,
   259   };
   261   LONG retCode = CheckCertificateForPEFile(filePath, allowedCertificate);
   262   if (retCode == ERROR_SUCCESS) {
   263     pushstring(stacktop, TEXT("1"), 2);
   264   } else {
   265     pushstring(stacktop, TEXT("0"), 2);
   266   }
   267 }
   269 /**
   270  * Verifies the trust of a signed file's certificate.
   271  *
   272  * @param  filePath  The file path to check.
   273  * @return ERROR_SUCCESS if successful, or the last error code otherwise.
   274  */
   275 DWORD
   276 VerifyCertificateTrustForFile(LPCWSTR filePath)
   277 {
   278   // Setup the file to check.
   279   WINTRUST_FILE_INFO fileToCheck;
   280   ZeroMemory(&fileToCheck, sizeof(fileToCheck));
   281   fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
   282   fileToCheck.pcwszFilePath = filePath;
   284   // Setup what to check, we want to check it is signed and trusted.
   285   WINTRUST_DATA trustData;
   286   ZeroMemory(&trustData, sizeof(trustData));
   287   trustData.cbStruct = sizeof(trustData);
   288   trustData.pPolicyCallbackData = NULL;
   289   trustData.pSIPClientData = NULL;
   290   trustData.dwUIChoice = WTD_UI_NONE;
   291   trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
   292   trustData.dwUnionChoice = WTD_CHOICE_FILE;
   293   trustData.dwStateAction = 0;
   294   trustData.hWVTStateData = NULL;
   295   trustData.pwszURLReference = NULL;
   296   // no UI
   297   trustData.dwUIContext = 0;
   298   trustData.pFile = &fileToCheck;
   300   GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
   301   // Check if the file is signed by something that is trusted.
   302   LONG ret = WinVerifyTrust(NULL, &policyGUID, &trustData);
   303   return ret;
   304 }
   306 /**
   307  * Verifies the trust of a signed file's certificate.
   308  *
   309  * @param  stacktop  A pointer to the top of the stack. This should be the file
   310  *                   path for the file that will have its trust verified.
   311  * @param  variables A pointer to the NSIS variables
   312  * @return 1 if the file's trust was verified successfully, 0 if it was not
   313  */
   314 extern "C" void __declspec(dllexport)
   315 VerifyCertTrust(HWND hwndParent, int string_size,
   316                 TCHAR *variables, stack_t **stacktop, void *extra)
   317 {
   318   TCHAR tmp[MAX_PATH + 1] = { _T('\0') };
   319   WCHAR filePath[MAX_PATH + 1] = { L'\0' };
   321   popstring(stacktop, tmp, MAX_PATH);
   323 #if !defined(UNICODE)
   324     MultiByteToWideChar(CP_ACP, 0, tmp, -1, filePath, MAX_PATH);
   325 #else
   326     wcsncpy(filePath, tmp, MAX_PATH);
   327 #endif
   329   LONG retCode = VerifyCertificateTrustForFile(filePath);
   330   if (retCode == ERROR_SUCCESS) {
   331     pushstring(stacktop, TEXT("1"), 2);
   332   } else {
   333     pushstring(stacktop, TEXT("0"), 2);
   334   }
   335 }
   337 BOOL WINAPI
   338 DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
   339 {
   340   return TRUE;
   341 }
   343 /**
   344  * Removes an element from the top of the NSIS stack
   345  *
   346  * @param  stacktop A pointer to the top of the stack
   347  * @param  str      The string to pop to
   348  * @param  len      The max length
   349  * @return 0 on success
   350 */
   351 int popstring(stack_t **stacktop, TCHAR *str, int len)
   352 {
   353   // Removes the element from the top of the stack and puts it in the buffer
   354   stack_t *th;
   355   if (!stacktop || !*stacktop) {
   356     return 1;
   357   }
   359   th = (*stacktop);
   360   lstrcpyn(str,th->text, len);
   361   *stacktop = th->next;
   362   GlobalFree((HGLOBAL)th);
   363   return 0;
   364 }
   366 /**
   367  * Adds an element to the top of the NSIS stack
   368  *
   369  * @param  stacktop A pointer to the top of the stack
   370  * @param  str      The string to push on the stack
   371  * @param  len      The length of the string to push on the stack
   372  * @return 0 on success
   373 */
   374 void pushstring(stack_t **stacktop, const TCHAR *str, int len)
   375 {
   376   stack_t *th;
   377   if (!stacktop) { 
   378     return;
   379   }
   381   th = (stack_t*)GlobalAlloc(GPTR, sizeof(stack_t) + len);
   382   lstrcpyn(th->text, str, len);
   383   th->next = *stacktop;
   384   *stacktop = th;
   385 }

mercurial