GateKeeper.cpp
author Volker Birk <vb@pep-project.org>
Fri, 24 Jun 2016 22:24:47 +0200
changeset 127 7f39af9b8dee
parent 126 8cf20db557f1
child 128 a026de7eb3cd
permissions -rw-r--r--
decrypt before install
     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 // https://gist.github.com/mcdurdin/5626617
    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 
   113     const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
   114     const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
   115     const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater";)
   116 
   117     const time_t GateKeeper::cycle = 7200;   // 7200 sec is 2 h
   118     const DWORD GateKeeper::waiting = 10000; // 10000 ms is 10 sec
   119 
   120     GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
   121         : _self(self), now(time(NULL)), next(now + time_diff()), hkUpdater(NULL), internet(NULL), hAES(NULL), hRSA(NULL)
   122     {
   123         LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
   124         assert(lResult == ERROR_SUCCESS);
   125         if (lResult == ERROR_SUCCESS)
   126             cu_open = true;
   127         else
   128             cu_open = false;
   129 
   130         if (cu_open) {
   131             LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
   132             assert(lResult == ERROR_SUCCESS);
   133             if (lResult != ERROR_SUCCESS)
   134                 return;
   135         }
   136     }
   137     
   138     GateKeeper::~GateKeeper()
   139     {
   140         if (cu_open) {
   141             if (hkUpdater)
   142                 RegCloseKey(hkUpdater);
   143             RegCloseKey(cu);
   144         }
   145     }
   146 
   147     time_t GateKeeper::time_diff()
   148     {
   149         try {
   150             static random_device rd;
   151             static mt19937 gen(rd());
   152 
   153             uniform_int_distribution<time_t> dist(0, cycle);
   154 
   155             return dist(gen);
   156         }
   157         catch (exception&) {
   158             assert(0);
   159             return 0;
   160         }
   161     }
   162 
   163     void GateKeeper::keep()
   164     {
   165         if (!cu_open)
   166             return;
   167 
   168         while (1) {
   169             keep_plugin();
   170 
   171             now = time(NULL);
   172             assert(now != -1);
   173 
   174             if (now > next) {
   175                 next = now + GateKeeper::cycle;
   176                 keep_updated();
   177             }
   178 
   179             Sleep(waiting);
   180         }
   181     }
   182 
   183     void GateKeeper::keep_plugin()
   184     {
   185         while (!_self->m_bComInitialized)
   186             Sleep(1);
   187 
   188         //MessageBox(NULL, _T("test"), _T("keep_plugin"), MB_ICONINFORMATION | MB_TOPMOST);
   189 
   190         DWORD value;
   191         DWORD size;
   192 
   193         LONG lResult = RegGetValue(cu, plugin_reg_path, plugin_reg_value_name, RRF_RT_REG_DWORD, NULL, &value, &size);
   194         if (lResult != ERROR_SUCCESS)
   195             return;
   196 
   197         if (value != 3) {
   198             lResult = RegSetValue(cu, plugin_reg_path, RRF_RT_REG_DWORD, plugin_reg_value_name, 3);
   199             assert(lResult == ERROR_SUCCESS);
   200         }
   201     }
   202 
   203     string GateKeeper::update_key()
   204     {
   205         static string key;
   206 
   207         if (key.length() == 0) {
   208             HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
   209             assert(res);
   210             if (!res)
   211                 throw runtime_error("FindResource: IRD_UPDATEKEY");
   212 
   213             HGLOBAL hRes = LoadResource(_self->hModule(), res);
   214             assert(hRes);
   215             if (!hRes)
   216                 throw runtime_error("LoadResource: IRD_UPDATEKEY");
   217 
   218             key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
   219             UnlockResource(hRes);
   220         }
   221 
   222         return key;
   223     }
   224 
   225     BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
   226     {
   227         aeskey_t key;
   228 
   229         static random_device rd;
   230         static mt19937 gen(rd());
   231 
   232         uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
   233 
   234         for (int i = 0; i < 8; i++)
   235             key.dw_key[i] = (uint32_t) dist(gen);
   236 
   237         BCRYPT_KEY_HANDLE hKey;
   238         NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR) &key, (ULONG) sizeof(aeskey_t), 0);
   239         assert(status == 0);
   240         if (status)
   241             throw runtime_error("BCryptGenerateSymmetricKey");
   242 
   243         return hKey;
   244     }
   245 
   246     string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
   247     {
   248         string result;
   249 
   250         BCRYPT_KEY_HANDLE hUpdateKey;
   251         string _update_key = update_key();
   252 
   253         PCERT_PUBLIC_KEY_INFO uk;
   254         DWORD uk_size;
   255         BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
   256                 (const BYTE *) _update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
   257         if (!bResult)
   258             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   259 
   260         PUBLIC_KEY_VALUES *_uk;
   261         DWORD _uk_size;
   262         bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
   263             uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
   264         LocalFree(uk);
   265         if (!bResult)
   266             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   267 
   268         HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
   269         LocalFree(_uk);
   270         if (hResult)
   271             throw runtime_error("ImportRsaPublicKey");
   272 
   273         aeskey_t _delivery_key;
   274         ULONG copied;
   275         NTSTATUS status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, (PUCHAR) &_delivery_key, sizeof(aeskey_t),
   276                 &copied, 0);
   277         if (status)
   278             throw runtime_error("BCryptExportKey: delivery_key");
   279 
   280         static random_device rd;
   281         static mt19937 gen(rd());
   282         uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
   283         uint32_t r[64];
   284         for (int i = 0; i < 64; i++)
   285             r[i] = (uint32_t) dist(gen);
   286 
   287         BCRYPT_OAEP_PADDING_INFO pi;
   288         memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
   289         pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
   290         pi.pbLabel = (PUCHAR) r;
   291         pi.cbLabel = sizeof(r);
   292 
   293         ULONG result_size;
   294         PUCHAR _result = NULL;
   295         status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
   296         if (status) {
   297             BCryptDestroyKey(hUpdateKey);
   298             throw runtime_error("BCryptEncrypt: calculating result size");
   299         }
   300 
   301         _result = new UCHAR[result_size];
   302         status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
   303         if (status) {
   304             BCryptDestroyKey(hUpdateKey);
   305             delete[] _result;
   306             throw runtime_error("BCryptEncrypt: encrypting using update_key");
   307         }
   308 
   309         BCryptDestroyKey(hUpdateKey);
   310 
   311         stringstream s;
   312         s << hex << setw(2) << setfill('0');
   313         for (ULONG i = 0; i < copied; i++)
   314             s << (int) _result[i];
   315         delete[] _result;
   316         s >> result;
   317 
   318         return result;
   319     }
   320 
   321     GateKeeper::product_list& GateKeeper::registered_products()
   322     {
   323         static product_list products;
   324 
   325         // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
   326         static TCHAR value_name[16384];
   327         DWORD value_name_size;
   328         static TCHAR value[L_MAX_URL_LENGTH + 1];
   329         DWORD value_size;
   330 
   331         products.empty();
   332 
   333         LONG lResult = ERROR_SUCCESS;
   334         for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
   335             value_size = L_MAX_URL_LENGTH + 1;
   336             lResult = RegEnumValue(hkUpdater, 0, value_name, &value_name_size, NULL, NULL, (LPBYTE) value, &value_size);
   337             if (lResult == ERROR_SUCCESS)
   338                 products.push_back({ value_name, value });
   339         }
   340 
   341         return products;
   342     }
   343 
   344     void GateKeeper::install_msi(tstring filename)
   345     {
   346 
   347     }
   348 
   349     void GateKeeper::update_product(product p, DWORD context)
   350     {
   351         BCRYPT_KEY_HANDLE dk = delivery_key();
   352 #ifdef UNICODE
   353         tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
   354 #else
   355         tstring delivery = wrapped_delivery_key(delivery_key());
   356 #endif
   357         tstring url = p.second;
   358         url += _T("&challenge=");
   359         url += delivery;
   360         tstring headers;
   361         HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
   362                 INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
   363         if (hUrl == NULL)
   364             return;
   365 
   366         string crypted;
   367         string unencrypted;
   368 
   369         do {
   370             static char buffer[32768];
   371             DWORD reading;
   372             BOOL bResult = InternetReadFile(hUrl, buffer, 32768, &reading);
   373             if (!bResult || !reading)
   374                 break;
   375             crypted += string(buffer, reading);
   376         } while (1);
   377 
   378         InternetCloseHandle(hUrl);
   379         hUrl = NULL;
   380 
   381         tstring filename;
   382         HANDLE hFile = NULL;
   383         char *unencrypted_buffer = NULL;
   384 
   385         ULONG unencrypted_size;
   386         NTSTATUS status = BCryptDecrypt(dk, (PUCHAR) crypted.data(), crypted.size(),
   387                 NULL, NULL, 0, NULL, 0, &unencrypted_size, 0);
   388         if (status)
   389             goto closing;
   390         
   391         unencrypted_buffer = new char[unencrypted_size];
   392 
   393         status = BCryptDecrypt(dk, (PUCHAR) crypted.data(), crypted.size(),
   394             NULL, NULL, 0, (PUCHAR) unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
   395         if (status) {
   396             delete[] unencrypted_buffer;
   397             goto closing;
   398         }
   399 
   400         TCHAR temp_path[MAX_PATH + 1];
   401         GetTempPath(MAX_PATH, temp_path);
   402         filename = temp_path;
   403         filename += _T("\\pEp_");
   404         filename += delivery.substr(0, 32);
   405         filename += _T(".msi");
   406 
   407         hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   408         if (!hFile)
   409             goto closing;
   410         DWORD writing;
   411         WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
   412         CloseHandle(hFile);
   413 
   414         install_msi(filename);
   415 
   416         DeleteFile(filename.c_str());
   417         BCryptDestroyKey(dk);
   418         return;
   419 
   420     closing:
   421         if (unencrypted_buffer)
   422             delete[] unencrypted_buffer;
   423         if (hFile)
   424             CloseHandle(hFile);
   425         if (hUrl)
   426             InternetCloseHandle(hUrl);
   427         if (filename.length())
   428             DeleteFile(filename.c_str());
   429         BCryptDestroyKey(dk);
   430     }
   431 
   432     void GateKeeper::keep_updated()
   433     {
   434         return; // disabled for now
   435 
   436         NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   437         assert(status == 0);
   438         if (status)
   439             goto closing;
   440 
   441         status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   442         assert(status == 0);
   443         if (status)
   444             goto closing;
   445 
   446         internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
   447         if (!internet)
   448             goto closing;
   449 
   450         product_list& products = registered_products();
   451         DWORD context = 0;
   452         for (auto i = products.begin(); i != products.end(); i++) {
   453             try {
   454                 update_product(*i, context++);
   455             }
   456             catch (exception&) {
   457             
   458             }
   459         }
   460 
   461     closing:
   462         if (internet)
   463             InternetCloseHandle(internet);
   464         if (hAES)
   465             BCryptCloseAlgorithmProvider(hAES, 0);
   466         if (hRSA)
   467             BCryptCloseAlgorithmProvider(hRSA, 0);
   468         internet = NULL;
   469         hAES = NULL;
   470         hRSA = NULL;
   471     }
   472 
   473 } // namespace pEp