GateKeeper.cpp
author Markus Schaber <markus@pep-security.net>
Wed, 07 Feb 2018 18:53:25 +0100
branchCOM-74
changeset 270 c713a265866f
parent 228 3419c95178c0
child 269 26c8597fe860
permissions -rw-r--r--
COM-74: Expose _PEP_enc_format to app for EncryptMessage

Remove obsolete (and now wrong) assertion of fpr != null
     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 
   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 time_t GateKeeper::fraction = 10;  // first update is at 10% of cycle
   119     const DWORD GateKeeper::waiting = 10000; // 10000 ms is 10 sec
   120 
   121     GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
   122         : _self(self), now(time(NULL)), next(now /*+ time_diff()*/), hkUpdater(NULL),
   123             internet(NULL), hAES(NULL), hRSA(NULL)
   124     {
   125 		DeleteFile(get_lockFile().c_str());
   126 
   127         LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
   128         assert(lResult == ERROR_SUCCESS);
   129         if (lResult == ERROR_SUCCESS)
   130             cu_open = true;
   131         else
   132             cu_open = false;
   133 
   134         if (cu_open) {
   135             LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
   136 			if (lResult != ERROR_SUCCESS)
   137 				RegCreateKeyEx(cu, updater_reg_path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hkUpdater, NULL);
   138 		}
   139     }
   140     
   141     GateKeeper::~GateKeeper()
   142     {
   143         if (cu_open) {
   144             if (hkUpdater)
   145                 RegCloseKey(hkUpdater);
   146             RegCloseKey(cu);
   147         }
   148     }
   149 
   150     time_t GateKeeper::time_diff()
   151     {
   152         try {
   153             static random_device rd;
   154             static mt19937 gen(rd());
   155 
   156             uniform_int_distribution<time_t> dist(0, cycle/fraction);
   157 
   158             return dist(gen);
   159         }
   160         catch (exception&) {
   161             assert(0);
   162             return 0;
   163         }
   164     }
   165 
   166     void GateKeeper::keep()
   167     {
   168         if (!cu_open)
   169             return;
   170 
   171         while (1) {
   172             keep_plugin();
   173 
   174             now = time(NULL);
   175             assert(now != -1);
   176 
   177             if (now > next) {
   178                 next = now + GateKeeper::cycle;
   179                 keep_updated();
   180             }
   181 
   182             Sleep(waiting);
   183         }
   184     }
   185 
   186     void GateKeeper::keep_plugin()
   187     {
   188 		HKEY hkPluginStart = NULL;
   189 
   190         LONG lResult = RegOpenKeyEx(cu, plugin_reg_path, 0, KEY_WRITE, &hkPluginStart);
   191         if (lResult != ERROR_SUCCESS)
   192             return;
   193 
   194         DWORD v = 3;
   195         lResult = RegSetValueEx(hkPluginStart, plugin_reg_value_name, 0, REG_DWORD, (const BYTE *) &v, sizeof(DWORD));
   196         assert(lResult == ERROR_SUCCESS);
   197 
   198         RegCloseKey(hkPluginStart);
   199     }
   200 
   201     string GateKeeper::update_key()
   202     {
   203         static string key;
   204 
   205         if (key.length() == 0) {
   206             HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
   207             assert(res);
   208             if (!res)
   209                 throw runtime_error("FindResource: IRD_UPDATEKEY");
   210 
   211             HGLOBAL hRes = LoadResource(_self->hModule(), res);
   212             assert(hRes);
   213             if (!hRes)
   214                 throw runtime_error("LoadResource: IRD_UPDATEKEY");
   215 
   216             key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
   217             UnlockResource(hRes);
   218         }
   219 
   220         return key;
   221     }
   222 
   223     BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
   224     {
   225         aeskey_t key;
   226 
   227         static random_device rd;
   228         static mt19937 gen(rd());
   229 
   230         uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
   231 
   232         for (int i = 0; i < 8; i++)
   233             key.dw_key[i] = (uint32_t) dist(gen);
   234 
   235         BCRYPT_KEY_HANDLE hKey;
   236 
   237         NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR) &key, (ULONG) sizeof(aeskey_t), 0);
   238         assert(status == 0);
   239         if (status)
   240             throw runtime_error("BCryptGenerateSymmetricKey");
   241 
   242 #ifndef NDEBUG
   243         DWORD keylength = 0;
   244         ULONG copied = 0;
   245         status = BCryptGetProperty(hKey, BCRYPT_KEY_LENGTH, (PUCHAR) &keylength, sizeof(DWORD), &copied, 0);
   246         assert(keylength == 256);
   247 #endif
   248 
   249         return hKey;
   250     }
   251 
   252     string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
   253     {
   254         string result;
   255 
   256         BCRYPT_KEY_HANDLE hUpdateKey;
   257         string _update_key = update_key();
   258 
   259         PCERT_PUBLIC_KEY_INFO uk;
   260         DWORD uk_size;
   261 
   262         BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
   263                 (const BYTE *) _update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
   264         if (!bResult)
   265             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   266 
   267         PUBLIC_KEY_VALUES *_uk;
   268         DWORD _uk_size;
   269 
   270         bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
   271             uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
   272         LocalFree(uk);
   273         if (!bResult)
   274             throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
   275 
   276         HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
   277         LocalFree(_uk);
   278         if (hResult)
   279             throw runtime_error("ImportRsaPublicKey");
   280 
   281         ULONG psize;
   282         NTSTATUS status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, NULL, 0, &psize, 0);
   283         char *prop = new char[psize];
   284         TCHAR *_prop = (TCHAR *) prop;
   285         BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, (PUCHAR) prop, psize, &psize, 0);
   286 
   287         ULONG export_size;
   288         status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, NULL,
   289             &export_size, 0);
   290         if (status)
   291             throw runtime_error("BCryptExportKey: measuring export size");
   292 
   293         PUCHAR _delivery_key = new UCHAR[export_size];
   294         ULONG copied;
   295         status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, _delivery_key, export_size,
   296                 &copied, 0);
   297         if (status) {
   298             delete[] _delivery_key;
   299             throw runtime_error("BCryptExportKey: delivery_key");
   300         }
   301 
   302         BCRYPT_OAEP_PADDING_INFO pi;
   303         memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
   304         pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
   305 
   306         ULONG result_size;
   307         PUCHAR _result = NULL;
   308         ULONG blob_size = export_size - sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
   309         PUCHAR blob = _delivery_key + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
   310         status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
   311         if (status) {
   312             delete[] _delivery_key;
   313             BCryptDestroyKey(hUpdateKey);
   314             throw runtime_error("BCryptEncrypt: calculating result size");
   315         }
   316 
   317         _result = new UCHAR[result_size];
   318         status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
   319         delete[] _delivery_key;
   320         if (status) {
   321             BCryptDestroyKey(hUpdateKey);
   322             delete[] _result;
   323             throw runtime_error("BCryptEncrypt: encrypting using update_key");
   324         }
   325 
   326         BCryptDestroyKey(hUpdateKey);
   327 
   328         stringstream s;
   329         for (ULONG i = 0; i < copied; i++) {
   330             s << hex << setw(2) << setfill('0');
   331             s << (int) _result[i];
   332         }
   333         delete[] _result;
   334         s >> result;
   335 
   336         return result;
   337     }
   338 
   339     GateKeeper::product_list GateKeeper::registered_products()
   340     {
   341         product_list products;
   342 
   343         // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
   344         TCHAR value_name[16384];
   345         DWORD value_name_size;
   346         TCHAR value[L_MAX_URL_LENGTH + 1];
   347         DWORD value_size;
   348 
   349         LONG lResult = ERROR_SUCCESS;
   350         for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
   351             value_name_size = 16383;
   352             value_size = L_MAX_URL_LENGTH + 1;
   353             lResult = RegEnumValue(hkUpdater, i, value_name, &value_name_size, NULL, NULL, (LPBYTE) value, &value_size);
   354             if (lResult == ERROR_SUCCESS) {
   355                 products.push_back({ value_name, value });
   356             }
   357         }
   358 
   359         return products;
   360     }
   361 
   362     void GateKeeper::install_msi(tstring filename)
   363     {
   364 		HANDLE hMutex = CreateMutex(NULL, TRUE, _T("PEPINSTALLERMUTEX"));
   365 		if (hMutex) {
   366 			CloseHandle(hMutex);
   367 			ShellExecute(NULL, _T("open"), filename.c_str(), NULL, NULL, SW_SHOW);
   368 		}
   369     }
   370 
   371 	tstring GateKeeper::get_lockFile()
   372 	{
   373 		static const tstring _fileName = _T("\\pEpSetup.lck");
   374 		static tstring fileName;
   375 
   376 		if (fileName.length() == 0) {
   377 			unique_ptr < TCHAR[] > _pathName(new TCHAR[MAX_PATH + 1]);
   378 			DWORD size = GetTempPath(MAX_PATH, _pathName.get());
   379 			if (size > MAX_PATH - _fileName.size())
   380 				throw runtime_error("TEMP path too long");
   381 
   382 			fileName = _pathName.get();
   383 			fileName += _fileName;
   384 		}
   385 
   386 		return fileName;
   387 	}
   388 
   389     void GateKeeper::update_product(product p, DWORD context)
   390     {
   391 		{
   392 			HANDLE file = CreateFile(get_lockFile().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
   393 			if (file == INVALID_HANDLE_VALUE) {
   394 				return;
   395 			}
   396 			else {
   397 				CloseHandle(file);
   398 				DeleteFile(get_lockFile().c_str());
   399 			}
   400 		}
   401 
   402         BCRYPT_KEY_HANDLE dk = delivery_key();
   403 #ifdef UNICODE
   404         tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
   405 #else
   406         tstring delivery = wrapped_delivery_key(delivery_key());
   407 #endif
   408         tstring url = p.second;
   409         url += _T("&challenge=");
   410         url += delivery;
   411         tstring headers;
   412         HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
   413                 INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
   414         if (hUrl == NULL)
   415             return;
   416 
   417         string crypted;
   418         string unencrypted;
   419         UCHAR iv[12];
   420         UCHAR nonce[sizeof(iv)];
   421         UCHAR tag[16];
   422         tstring filename;
   423         HANDLE hFile = NULL;
   424         char *unencrypted_buffer = NULL;
   425 
   426         try {
   427             DWORD reading;
   428             InternetReadFile(hUrl, iv, sizeof(iv), &reading);
   429 
   430             if (reading) do {
   431                 static char buffer[1024*1024];
   432                 BOOL bResult = InternetReadFile(hUrl, buffer, 1024*1024, &reading);
   433                 if (!bResult || !reading)
   434                     break;
   435                 crypted += string(buffer, reading);
   436             } while (1);
   437         }
   438         catch (exception&) {
   439             goto closing;
   440         }
   441 
   442         InternetCloseHandle(hUrl);
   443         hUrl = NULL;
   444 
   445         memcpy(nonce, iv, sizeof(iv));
   446 
   447         BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
   448         BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
   449         authInfo.pbNonce = nonce;
   450         authInfo.cbNonce = sizeof(nonce);
   451         authInfo.pbTag = tag;
   452         authInfo.cbTag = sizeof(tag);
   453 
   454         ULONG unencrypted_size;
   455         NTSTATUS status = BCryptDecrypt(dk, (PUCHAR) crypted.data(), crypted.size(),
   456                 &authInfo, iv, sizeof(iv), NULL, 0, &unencrypted_size, 0);
   457         if (status)
   458             goto closing;
   459         
   460         unencrypted_buffer = new char[unencrypted_size];
   461         PUCHAR crypted_data = (PUCHAR) crypted.data();
   462         ULONG crypted_size = (ULONG) crypted.size() - sizeof(tag);
   463         memcpy(tag, crypted_data + crypted_size, sizeof(tag));
   464 
   465         status = BCryptDecrypt(dk, crypted_data, crypted_size,
   466             &authInfo, iv, sizeof(iv), (PUCHAR) unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
   467         if (status)
   468             goto closing;
   469 
   470         BCryptDestroyKey(dk);
   471 
   472         TCHAR temp_path[MAX_PATH + 1];
   473         GetTempPath(MAX_PATH, temp_path);
   474         filename = temp_path;
   475         filename += _T("\\pEp_");
   476         filename += delivery.substr(0, 32);
   477         filename += _T(".msi");
   478 
   479         hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   480         if (!hFile)
   481             goto closing;
   482         DWORD writing;
   483         WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
   484         CloseHandle(hFile);
   485 
   486         install_msi(filename);
   487 
   488         return;
   489 
   490     closing:
   491         if (unencrypted_buffer)
   492             delete[] unencrypted_buffer;
   493         if (hFile)
   494             CloseHandle(hFile);
   495         if (hUrl)
   496             InternetCloseHandle(hUrl);
   497         BCryptDestroyKey(dk);
   498     }
   499 
   500     void GateKeeper::keep_updated()
   501     {
   502         NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   503         assert(status == 0);
   504         if (status)
   505             goto closing;
   506         status = BCryptSetProperty(hAES, BCRYPT_CHAINING_MODE, (PUCHAR) BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
   507         if (status)
   508             goto closing;
   509 
   510         status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
   511         assert(status == 0);
   512         if (status)
   513             goto closing;
   514 
   515         internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
   516         if (!internet)
   517             goto closing;
   518 
   519 		{
   520 			product_list products = registered_products();
   521 			DWORD context = 0;
   522 
   523 			for (auto i = products.begin(); i != products.end(); i++) {
   524 				try {
   525 					update_product(*i, context++);
   526 				}
   527 				catch (exception&) {
   528 
   529 				}
   530 			}
   531 		}
   532 
   533     closing:
   534         if (internet)
   535             InternetCloseHandle(internet);
   536         if (hAES)
   537             BCryptCloseAlgorithmProvider(hAES, 0);
   538         if (hRSA)
   539             BCryptCloseAlgorithmProvider(hRSA, 0);
   540         internet = NULL;
   541         hAES = NULL;
   542         hRSA = NULL;
   543     }
   544 
   545 } // namespace pEp