GateKeeper.cpp
author Thomas
Thu, 03 Oct 2019 14:09:52 +0200
branchsync
changeset 359 bc971f0f2e8c
parent 302 b3444780fa9f
child 360 57c7ace37425
permissions -rw-r--r--
Do not add file ending to files that are delivered over the update system
     1 #include "stdafx.h"
     2 
     3 #include "GateKeeper.h"
     4 #include "pEpCOMServerAdapter.h"
     5 #include "utf8_helper.h"
     6 
     7 using namespace std;
     8 
     9 // from https://msdn.microsoft.com/en-us/library/windows/desktop/dd388945(v=vs.85).aspx
    10 
    11 struct PUBLIC_KEY_VALUES {
    12     BLOBHEADER blobheader;
    13     RSAPUBKEY rsapubkey;
    14     BYTE modulus[4096];
    15 };
    16 
    17 static void ReverseMemCopy(
    18     _Out_ BYTE       *pbDest,
    19     _In_  BYTE const *pbSource,
    20     _In_  DWORD       cb
    21 )
    22 {
    23     for (DWORD i = 0; i < cb; i++) {
    24         pbDest[cb - 1 - i] = pbSource[i];
    25     }
    26 }
    27 
    28 static NTSTATUS ImportRsaPublicKey(
    29     _In_ BCRYPT_ALG_HANDLE  hAlg,    // CNG provider
    30     _In_ PUBLIC_KEY_VALUES *pKey,    // Pointer to the RSAPUBKEY blob.
    31     _In_ BCRYPT_KEY_HANDLE *phKey    // Receives a handle the imported public key.
    32 )
    33 {
    34     NTSTATUS hr = 0;
    35 
    36     BYTE *pbPublicKey = NULL;
    37     DWORD cbKey = 0;
    38 
    39     // Layout of the RSA public key blob:
    40 
    41     //  +----------------------------------------------------------------+
    42     //  |     BCRYPT_RSAKEY_BLOB    | BE( dwExp ) |   BE( Modulus )      |
    43     //  +----------------------------------------------------------------+
    44     //
    45     //  sizeof(BCRYPT_RSAKEY_BLOB)       cbExp           cbModulus 
    46     //  <--------------------------><------------><---------------------->
    47     //
    48     //   BE = Big Endian Format                                                     
    49 
    50     DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
    51     DWORD dwExp = pKey->rsapubkey.pubexp;
    52     DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
    53         (dwExp & 0x00FF0000) ? 3 :
    54         (dwExp & 0x0000FF00) ? 2 : 1;
    55 
    56     BCRYPT_RSAKEY_BLOB *pRsaBlob;
    57     PBYTE pbCurrent;
    58 
    59     if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
    60         goto cleanup;
    61     }
    62 
    63     cbKey += cbExp;
    64 
    65     pbPublicKey = (PBYTE)CoTaskMemAlloc(cbKey);
    66     if (pbPublicKey == NULL) {
    67         hr = E_OUTOFMEMORY;
    68         goto cleanup;
    69     }
    70 
    71     ZeroMemory(pbPublicKey, cbKey);
    72     pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey);
    73 
    74     //
    75     // Make the Public Key Blob Header
    76     //
    77 
    78     pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
    79     pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
    80     pRsaBlob->cbPublicExp = cbExp;
    81     pRsaBlob->cbModulus = cbModulus;
    82     pRsaBlob->cbPrime1 = 0;
    83     pRsaBlob->cbPrime2 = 0;
    84 
    85     pbCurrent = (PBYTE)(pRsaBlob + 1);
    86 
    87     //
    88     // Copy pubExp Big Endian 
    89     //
    90 
    91     ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp);
    92     pbCurrent += cbExp;
    93 
    94     //
    95     // Copy Modulus Big Endian 
    96     //
    97 
    98     ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
    99 
   100     //
   101     // Import the public key
   102     //
   103 
   104     hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR)pbPublicKey, cbKey, 0);
   105 
   106 cleanup:
   107     CoTaskMemFree(pbPublicKey);
   108     return hr;
   109 }
   110 
   111 namespace pEp {
   112     std::mutex GateKeeper::update_wait_mtx;
   113     std::condition_variable GateKeeper::update_wait_var;
   114     bool GateKeeper::update_wait_forced = false;
   115 
   116     const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
   117     const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
   118     const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater");
   119 
   120     const time_t GateKeeper::cycle = 7200;   // 7200 sec is 2 h
   121     const time_t GateKeeper::fraction = 10;  // first update is at 10% of cycle
   122     const chrono::seconds GateKeeper::waiting = 10s; //  10 sec
   123 
   124     GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
   125         : _self(self), now(time(NULL)), next(now /*+ time_diff()*/), hkUpdater(NULL),
   126         internet(NULL), hAES(NULL), hRSA(NULL)
   127     {
   128         DeleteFile(get_lockFile().c_str());
   129 
   130         LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
   131         assert(lResult == ERROR_SUCCESS);
   132         if (lResult == ERROR_SUCCESS)
   133             cu_open = true;
   134         else
   135             cu_open = false;
   136 
   137         if (cu_open) {
   138             LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
   139             if (lResult != ERROR_SUCCESS)
   140                 RegCreateKeyEx(cu, updater_reg_path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hkUpdater, NULL);
   141         }
   142     }
   143 
   144     GateKeeper::~GateKeeper()
   145     {
   146         if (cu_open) {
   147             if (hkUpdater)
   148                 RegCloseKey(hkUpdater);
   149             RegCloseKey(cu);
   150         }
   151     }
   152 
   153     time_t GateKeeper::time_diff()
   154     {
   155         try {
   156             static random_device rd;
   157             static mt19937 gen(rd());
   158 
   159             uniform_int_distribution<time_t> dist(0, cycle / fraction);
   160 
   161             return dist(gen);
   162         }
   163         catch (exception&) {
   164             assert(0);
   165             return 0;
   166         }
   167     }
   168 
   169     void GateKeeper::keep()
   170     {
   171         if (!cu_open)
   172             return;
   173 
   174         while (1) {
   175             keep_plugin();
   176 
   177             now = time(NULL);
   178             assert(now != -1);
   179 
   180             bool force_check;
   181             // We need to sleep, but we should be interruptible by the update_now() method.
   182             {
   183                 std::unique_lock<std::mutex> guard(GateKeeper::update_wait_mtx);
   184                 GateKeeper::update_wait_var.wait_for(guard, waiting);
   185                 force_check = GateKeeper::update_wait_forced;
   186                 GateKeeper::update_wait_forced = false;
   187             }
   188 
   189             if (force_check || now > next) {
   190                 next = now + GateKeeper::cycle;
   191                 keep_updated();
   192             }
   193         }
   194     }
   195 
   196     void GateKeeper::update_now() 
   197     {
   198         // Signal the GateKeeper thread that we need to check for updates now.
   199         std::unique_lock<std::mutex> guard(GateKeeper::update_wait_mtx);
   200         GateKeeper::update_wait_forced = true;
   201         GateKeeper::update_wait_var.notify_all();
   202     }
   203 
   204     void GateKeeper::keep_plugin()
   205     {
   206         HKEY hkPluginStart = NULL;
   207 
   208         LONG lResult = RegOpenKeyEx(cu, plugin_reg_path, 0, KEY_WRITE, &hkPluginStart);
   209         if (lResult != ERROR_SUCCESS)
   210             return;
   211 
   212         DWORD v = 3;
   213         lResult = RegSetValueEx(hkPluginStart, plugin_reg_value_name, 0, REG_DWORD, (const BYTE *)&v, sizeof(DWORD));
   214         assert(lResult == ERROR_SUCCESS);
   215 
   216         RegCloseKey(hkPluginStart);
   217     }
   218 
   219     string GateKeeper::update_key()
   220     {
   221         static string key;
   222 
   223         if (key.length() == 0) {
   224             HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
   225             assert(res);
   226             if (!res)
   227                 throw runtime_error("FindResource: IRD_UPDATEKEY");
   228 
   229             HGLOBAL hRes = LoadResource(_self->hModule(), res);
   230             assert(hRes);
   231             if (!hRes)
   232                 throw runtime_error("LoadResource: IRD_UPDATEKEY");
   233 
   234             key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
   235             UnlockResource(hRes);
   236         }
   237 
   238         return key;
   239     }
   240 
   241     BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
   242     {
   243         aeskey_t key;
   244 
   245         static random_device rd;
   246         static mt19937 gen(rd());
   247 
   248         uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
   249 
   250         for (int i = 0; i < 8; i++)
   251             key.dw_key[i] = (uint32_t)dist(gen);
   252 
   253         BCRYPT_KEY_HANDLE hKey;
   254 
   255         NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR)&key, (ULONG) sizeof(aeskey_t), 0);
   256         assert(status == 0);
   257         if (status)
   258             throw runtime_error("BCryptGenerateSymmetricKey");
   259 
   260 #ifndef NDEBUG
   261         DWORD keylength = 0;
   262         ULONG copied = 0;
   263         status = BCryptGetProperty(hKey, BCRYPT_KEY_LENGTH, (PUCHAR)&keylength, sizeof(DWORD), &copied, 0);
   264         assert(keylength == 256);
   265 #endif
   266 
   267         return hKey;
   268     }
   269 
   270     string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
   271     {
   272         string result;
   273 
   274         BCRYPT_KEY_HANDLE hUpdateKey = NULL;
   275         string _update_key = update_key();
   276 
   277         PCERT_PUBLIC_KEY_INFO uk = NULL;
   278         DWORD uk_size = 0;
   279 
   280         BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
   281             (const BYTE *)_update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
   282         if (!bResult)
   283             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   284 
   285         PUBLIC_KEY_VALUES *_uk = NULL;
   286         DWORD _uk_size = 0;
   287 
   288         bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
   289             uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
   290         LocalFree(uk);
   291         if (!bResult)
   292             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   293 
   294         HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
   295         LocalFree(_uk);
   296         if (!hUpdateKey)
   297             throw runtime_error("ImportRsaPublicKey");
   298 
   299         ULONG psize;
   300         NTSTATUS status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, NULL, 0, &psize, 0);
   301         char *prop = new char[psize];
   302         TCHAR *_prop = (TCHAR *)prop;
   303         status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, (PUCHAR)prop, psize, &psize, 0);
   304         if (status)
   305             throw runtime_error("BCryptGetProperty: BCRYPT_ALGORITHM_NAME");
   306 
   307         ULONG export_size;
   308         status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, NULL,
   309             &export_size, 0);
   310         if (status)
   311             throw runtime_error("BCryptExportKey: measuring export size");
   312 
   313         PUCHAR _delivery_key = new UCHAR[export_size];
   314         ULONG copied;
   315         status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, _delivery_key, export_size,
   316             &copied, 0);
   317         if (status) {
   318             delete[] _delivery_key;
   319             throw runtime_error("BCryptExportKey: delivery_key");
   320         }
   321 
   322         BCRYPT_OAEP_PADDING_INFO pi;
   323         memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
   324         pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
   325 
   326         ULONG result_size = 0;
   327         PUCHAR _result = NULL;
   328         ULONG blob_size = export_size - sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
   329         PUCHAR blob = _delivery_key + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
   330         status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
   331         if (status) {
   332             delete[] _delivery_key;
   333             BCryptDestroyKey(hUpdateKey);
   334             throw runtime_error("BCryptEncrypt: calculating result size");
   335         }
   336 
   337         _result = new UCHAR[result_size + 1];
   338         status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
   339         delete[] _delivery_key;
   340         if (status) {
   341             BCryptDestroyKey(hUpdateKey);
   342             delete[] _result;
   343             throw runtime_error("BCryptEncrypt: encrypting using update_key");
   344         }
   345 
   346         BCryptDestroyKey(hUpdateKey);
   347 
   348         stringstream s;
   349         for (ULONG i = 0; i < copied; i++) {
   350             s << hex << setw(2) << setfill('0');
   351             s << (int)_result[i];
   352         }
   353         delete[] _result;
   354         s >> result;
   355 
   356         return result;
   357     }
   358 
   359     GateKeeper::product_list GateKeeper::registered_products()
   360     {
   361         product_list products;
   362 
   363         // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
   364         static TCHAR value_name[16384];
   365         DWORD value_name_size;
   366         static TCHAR value[L_MAX_URL_LENGTH + 1];
   367         DWORD value_size;
   368 
   369         LONG lResult = ERROR_SUCCESS;
   370         for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
   371             value_name_size = 16383;
   372             value_size = L_MAX_URL_LENGTH + 1;
   373             lResult = RegEnumValue(hkUpdater, i, value_name, &value_name_size, NULL, NULL, (LPBYTE)value, &value_size);
   374             if (lResult == ERROR_SUCCESS) {
   375                 products.push_back({ value_name, value });
   376             }
   377         }
   378 
   379         return products;
   380     }
   381 
   382     void GateKeeper::install_msi(tstring filename)
   383     {
   384         HANDLE hMutex = CreateMutex(NULL, TRUE, _T("PEPINSTALLERMUTEX"));
   385         if (hMutex) {
   386             CloseHandle(hMutex);
   387             ShellExecute(NULL, _T("open"), filename.c_str(), NULL, NULL, SW_SHOW);
   388         }
   389     }
   390 
   391     tstring GateKeeper::get_lockFile()
   392     {
   393         static const tstring _fileName = _T("\\pEpSetup.lck");
   394         static tstring fileName;
   395 
   396         if (fileName.length() == 0) {
   397             unique_ptr < TCHAR[] > _pathName(new TCHAR[MAX_PATH + 1]);
   398             DWORD size = GetTempPath(MAX_PATH, _pathName.get());
   399             if (size > MAX_PATH - _fileName.size())
   400                 throw runtime_error("TEMP path too long");
   401 
   402             fileName = _pathName.get();
   403             fileName += _fileName;
   404         }
   405 
   406         return fileName;
   407     }
   408 
   409     void GateKeeper::update_product(product p, DWORD context)
   410     {
   411         {
   412             HANDLE file = CreateFile(get_lockFile().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
   413             if (file == INVALID_HANDLE_VALUE) {
   414                 return;
   415             }
   416             else {
   417                 CloseHandle(file);
   418                 DeleteFile(get_lockFile().c_str());
   419             }
   420         }
   421 
   422         BCRYPT_KEY_HANDLE dk = delivery_key();
   423 #ifdef UNICODE
   424         tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
   425 #else
   426         tstring delivery = wrapped_delivery_key(delivery_key());
   427 #endif
   428         tstring url = p.second;
   429         url += _T("&challenge=");
   430         url += delivery;
   431         tstring headers;
   432         HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
   433             INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
   434         if (hUrl == NULL)
   435             return;
   436 
   437         string crypted;
   438         string unencrypted;
   439         UCHAR iv[12];
   440         UCHAR nonce[sizeof(iv)];
   441         UCHAR tag[16];
   442         tstring filename;
   443         HANDLE hFile = NULL;
   444         char *unencrypted_buffer = NULL;
   445 
   446         try {
   447             DWORD reading;
   448             InternetReadFile(hUrl, iv, sizeof(iv), &reading);
   449 
   450             if (reading) do {
   451                 static char buffer[1024 * 1024];
   452                 BOOL bResult = InternetReadFile(hUrl, buffer, 1024 * 1024, &reading);
   453                 if (!bResult || !reading)
   454                     break;
   455                 crypted += string(buffer, reading);
   456             } while (1);
   457 
   458             InternetCloseHandle(hUrl);
   459             hUrl = NULL;
   460 
   461             memcpy(nonce, iv, sizeof(iv));
   462 
   463             BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
   464             BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
   465             authInfo.pbNonce = nonce;
   466             authInfo.cbNonce = sizeof(nonce);
   467             authInfo.pbTag = tag;
   468             authInfo.cbTag = sizeof(tag);
   469 
   470             ULONG unencrypted_size;
   471             NTSTATUS status = BCryptDecrypt(dk, (PUCHAR)crypted.data(), crypted.size(),
   472                 &authInfo, iv, sizeof(iv), NULL, 0, &unencrypted_size, 0);
   473             if (status)
   474                 goto closing;
   475 
   476             unencrypted_buffer = new char[unencrypted_size];
   477 
   478             PUCHAR crypted_data = (PUCHAR)crypted.data();
   479             ULONG crypted_size = (ULONG)crypted.size() - sizeof(tag);
   480             memcpy(tag, crypted_data + crypted_size, sizeof(tag));
   481 
   482             status = BCryptDecrypt(dk, crypted_data, crypted_size,
   483                 &authInfo, iv, sizeof(iv), (PUCHAR)unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
   484             if (status)
   485                 goto closing;
   486 
   487             BCryptDestroyKey(dk);
   488 
   489             TCHAR temp_path[MAX_PATH + 1];
   490             GetTempPath(MAX_PATH, temp_path);
   491             filename = temp_path;
   492             filename += _T("\\pEp_");
   493             filename += delivery.substr(0, 32);
   494 
   495             hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   496             if (!hFile)
   497                 goto closing;
   498             DWORD writing;
   499             WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
   500             CloseHandle(hFile);
   501             delete[] unencrypted_buffer;
   502             unencrypted_buffer = nullptr;
   503         }
   504         catch (exception&) {
   505             goto closing;
   506         }
   507 
   508         install_msi(filename);
   509 
   510     closing:
   511         if (unencrypted_buffer)
   512             delete[] unencrypted_buffer;
   513         if (hFile)
   514             CloseHandle(hFile);
   515         if (hUrl)
   516             InternetCloseHandle(hUrl);
   517         BCryptDestroyKey(dk);
   518     }
   519 
   520     void GateKeeper::keep_updated()
   521     {
   522         NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   523         assert(status == 0);
   524         if (status)
   525             goto closing;
   526         status = BCryptSetProperty(hAES, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
   527         if (status)
   528             goto closing;
   529 
   530         status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   531         assert(status == 0);
   532         if (status)
   533             goto closing;
   534 
   535         internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
   536         if (!internet)
   537             goto closing;
   538 
   539         {
   540             product_list products = registered_products();
   541             DWORD context = 0;
   542 
   543             for (auto i = products.begin(); i != products.end(); i++) {
   544                 try {
   545                     update_product(*i, context++);
   546                 }
   547                 catch (exception&) {
   548 
   549                 }
   550             }
   551         }
   552 
   553     closing:
   554         if (internet)
   555             InternetCloseHandle(internet);
   556         if (hAES)
   557             BCryptCloseAlgorithmProvider(hAES, 0);
   558         if (hRSA)
   559             BCryptCloseAlgorithmProvider(hRSA, 0);
   560         internet = NULL;
   561         hAES = NULL;
   562         hRSA = NULL;
   563     }
   564 
   565 } // namespace pEp