3 #include "GateKeeper.h"
4 #include "pEpCOMServerAdapter.h"
8 // from https://msdn.microsoft.com/en-us/library/windows/desktop/dd388945(v=vs.85).aspx
10 struct PUBLIC_KEY_VALUES {
11 BLOBHEADER blobheader;
16 static void ReverseMemCopy(
18 _In_ BYTE const *pbSource,
22 for (DWORD i = 0; i < cb; i++) {
23 pbDest[cb - 1 - i] = pbSource[i];
27 static NTSTATUS ImportRsaPublicKey(
28 _In_ BCRYPT_ALG_HANDLE hAlg, // CNG provider
29 _In_ PUBLIC_KEY_VALUES *pKey, // Pointer to the RSAPUBKEY blob.
30 _In_ BCRYPT_KEY_HANDLE *phKey // Receives a handle the imported public key.
35 BYTE *pbPublicKey = NULL;
38 // Layout of the RSA public key blob:
40 // +----------------------------------------------------------------+
41 // | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) |
42 // +----------------------------------------------------------------+
44 // sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus
45 // <--------------------------><------------><---------------------->
47 // BE = Big Endian Format
49 DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
50 DWORD dwExp = pKey->rsapubkey.pubexp;
51 DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
52 (dwExp & 0x00FF0000) ? 3 :
53 (dwExp & 0x0000FF00) ? 2 : 1;
55 BCRYPT_RSAKEY_BLOB *pRsaBlob;
58 if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
64 pbPublicKey = (PBYTE)CoTaskMemAlloc(cbKey);
65 if (pbPublicKey == NULL) {
70 ZeroMemory(pbPublicKey, cbKey);
71 pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey);
74 // Make the Public Key Blob Header
77 pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
78 pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
79 pRsaBlob->cbPublicExp = cbExp;
80 pRsaBlob->cbModulus = cbModulus;
81 pRsaBlob->cbPrime1 = 0;
82 pRsaBlob->cbPrime2 = 0;
84 pbCurrent = (PBYTE)(pRsaBlob + 1);
87 // Copy pubExp Big Endian
90 ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp);
94 // Copy Modulus Big Endian
97 ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
100 // Import the public key
103 hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR)pbPublicKey, cbKey, 0);
106 CoTaskMemFree(pbPublicKey);
111 const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
112 const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
113 const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater");
115 const time_t GateKeeper::cycle = 7200; // 7200 sec is 2 h
116 const time_t GateKeeper::fraction = 10; // first update is at 10% of cycle
117 const DWORD GateKeeper::waiting = 10000; // 10 sec
119 GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
120 : _self(self), now(time(NULL)), next(now /*+ time_diff()*/), hkUpdater(NULL),
121 internet(NULL), hAES(NULL), hRSA(NULL)
124 throw runtime_error("second instance of GateKeeper was initialized");
126 DeleteFile(get_lockFile().c_str());
128 LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
129 assert(lResult == ERROR_SUCCESS);
130 if (lResult == ERROR_SUCCESS)
136 LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
137 if (lResult != ERROR_SUCCESS)
138 RegCreateKeyEx(cu, updater_reg_path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hkUpdater, NULL);
141 the_gatekeeper = this;
144 GateKeeper::~GateKeeper()
146 the_gatekeeper = nullptr;
150 RegCloseKey(hkUpdater);
155 time_t GateKeeper::time_diff()
158 static random_device rd;
159 static mt19937 gen(rd());
161 uniform_int_distribution<time_t> dist(0, cycle / fraction);
171 void GateKeeper::keep()
183 next = now + GateKeeper::cycle;
184 if (update_enabled())
192 void GateKeeper::keep_plugin()
194 HKEY hkPluginStart = NULL;
196 LONG lResult = RegOpenKeyEx(cu, plugin_reg_path, 0, KEY_WRITE, &hkPluginStart);
197 if (lResult != ERROR_SUCCESS)
201 lResult = RegSetValueEx(hkPluginStart, plugin_reg_value_name, 0, REG_DWORD, (const BYTE *)&v, sizeof(DWORD));
202 assert(lResult == ERROR_SUCCESS);
204 RegCloseKey(hkPluginStart);
207 string GateKeeper::update_key()
211 if (key.length() == 0) {
212 HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
215 throw runtime_error("FindResource: IRD_UPDATEKEY");
217 HGLOBAL hRes = LoadResource(_self->hModule(), res);
220 throw runtime_error("LoadResource: IRD_UPDATEKEY");
222 key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
223 UnlockResource(hRes);
229 BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
233 static random_device rd;
234 static mt19937 gen(rd());
236 uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
238 for (int i = 0; i < 8; i++)
239 key.dw_key[i] = (uint32_t)dist(gen);
241 BCRYPT_KEY_HANDLE hKey;
243 NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR)&key, (ULONG) sizeof(aeskey_t), 0);
246 throw runtime_error("BCryptGenerateSymmetricKey");
251 status = BCryptGetProperty(hKey, BCRYPT_KEY_LENGTH, (PUCHAR)&keylength, sizeof(DWORD), &copied, 0);
252 assert(keylength == 256);
258 string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
262 BCRYPT_KEY_HANDLE hUpdateKey = NULL;
263 string _update_key = update_key();
265 PCERT_PUBLIC_KEY_INFO uk = NULL;
268 BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
269 (const BYTE *)_update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
271 throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
273 PUBLIC_KEY_VALUES *_uk = NULL;
276 bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
277 uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
280 throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
282 HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
285 throw runtime_error("ImportRsaPublicKey");
288 NTSTATUS status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, NULL, 0, &psize, 0);
289 char *prop = new char[psize];
290 TCHAR *_prop = (TCHAR *)prop;
291 status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, (PUCHAR)prop, psize, &psize, 0);
293 throw runtime_error("BCryptGetProperty: BCRYPT_ALGORITHM_NAME");
296 status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, NULL,
299 throw runtime_error("BCryptExportKey: measuring export size");
301 PUCHAR _delivery_key = new UCHAR[export_size];
303 status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, _delivery_key, export_size,
306 delete[] _delivery_key;
307 throw runtime_error("BCryptExportKey: delivery_key");
310 BCRYPT_OAEP_PADDING_INFO pi;
311 memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
312 pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
314 ULONG result_size = 0;
315 PUCHAR _result = NULL;
316 ULONG blob_size = export_size - sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
317 PUCHAR blob = _delivery_key + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
318 status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
320 delete[] _delivery_key;
321 BCryptDestroyKey(hUpdateKey);
322 throw runtime_error("BCryptEncrypt: calculating result size");
325 _result = new UCHAR[result_size + 1];
326 status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
327 delete[] _delivery_key;
329 BCryptDestroyKey(hUpdateKey);
331 throw runtime_error("BCryptEncrypt: encrypting using update_key");
334 BCryptDestroyKey(hUpdateKey);
337 for (ULONG i = 0; i < copied; i++) {
338 s << hex << setw(2) << setfill('0');
339 s << (int)_result[i];
347 void GateKeeper::enable_update()
349 LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_WRITE, &hkUpdater);
350 if (lResult != ERROR_SUCCESS)
353 lResult = RegSetValueExW(hkUpdater, NULL, 0, REG_SZ, (const BYTE *) _T("1"), sizeof(TCHAR)*2);
356 void GateKeeper::disable_update()
358 LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_WRITE, &hkUpdater);
359 if (lResult != ERROR_SUCCESS)
362 lResult = RegSetValueEx(hkUpdater, NULL, 0, REG_SZ, (const BYTE *) _T("0"), sizeof(TCHAR) * 2);
365 bool GateKeeper::update_enabled()
370 RegGetValue(cu, updater_reg_path, NULL, RRF_RT_REG_SZ, NULL, NULL, &esize);
372 TCHAR* edata = new TCHAR[esize];
373 RegGetValue(cu, updater_reg_path, NULL, RRF_RT_REG_SZ, NULL, edata, &esize);
374 if (tstring(edata) == _T("0"))
382 GateKeeper::product_list GateKeeper::registered_products()
384 product_list products;
386 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
387 static TCHAR value_name[16384];
388 DWORD value_name_size;
389 static TCHAR value[L_MAX_URL_LENGTH + 1];
392 LONG lResult = ERROR_SUCCESS;
393 for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
394 value_name_size = 16383;
395 value_size = L_MAX_URL_LENGTH + 1;
396 lResult = RegEnumValue(hkUpdater, i, value_name, &value_name_size, NULL, NULL, (LPBYTE)value, &value_size);
397 if (lResult == ERROR_SUCCESS) {
398 products.push_back({ value_name, value });
405 void GateKeeper::execute_file(tstring filename)
407 HANDLE hMutex = CreateMutex(NULL, TRUE, _T("PEPINSTALLERMUTEX"));
410 ShellExecute(NULL, _T("open"), filename.c_str(), NULL, NULL, SW_SHOW);
414 tstring GateKeeper::get_lockFile()
416 static const tstring _fileName = _T("\\pEpSetup.lck");
417 static tstring fileName;
419 if (fileName.length() == 0) {
420 unique_ptr < TCHAR[] > _pathName(new TCHAR[MAX_PATH + 1]);
421 DWORD size = GetTempPath(MAX_PATH, _pathName.get());
422 if (size > MAX_PATH - _fileName.size())
423 throw runtime_error("TEMP path too long");
425 fileName = _pathName.get();
426 fileName += _fileName;
432 // Retrieving Headers Using HTTP_QUERY_CUSTOM
433 static tstring httpQueryCustom(HINTERNET hHttp, tstring header)
436 LPTSTR lpOutBuffer = StrDup(header.c_str());
440 if (!HttpQueryInfo(hHttp, HTTP_QUERY_CUSTOM, (LPVOID)lpOutBuffer, &dwResult, NULL))
442 if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
444 // Code to handle the case where the header isn't available.
445 LocalFree(lpOutBuffer);
446 throw(runtime_error("ERROR_HTTP_HEADER_NOT_FOUND"));
450 // Check for an insufficient buffer.
451 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
453 // Allocate the necessary buffer.
454 LocalFree(lpOutBuffer);
455 lpOutBuffer = (LPTSTR)LocalAlloc(LMEM_FIXED, dwResult + 1);
457 // Rewrite the header name in the buffer.
458 StringCchPrintf(lpOutBuffer, dwResult, header.c_str());
465 // Error handling code.
466 LocalFree(lpOutBuffer);
467 // FIXME: Add GetLastError()
468 throw(runtime_error("Unknown"));
473 tstring result(lpOutBuffer);
474 LocalFree(lpOutBuffer);
479 bool GateKeeper::update_product(product p, DWORD context)
482 HANDLE file = CreateFile(get_lockFile().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
483 if (file == INVALID_HANDLE_VALUE) {
488 DeleteFile(get_lockFile().c_str());
492 BCRYPT_KEY_HANDLE dk = delivery_key();
494 tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
496 tstring delivery = wrapped_delivery_key(delivery_key());
498 tstring url = p.second;
499 url += _T("&challenge=");
502 HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
503 INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
510 UCHAR nonce[sizeof(iv)];
514 char *unencrypted_buffer = NULL;
520 InternetReadFile(hUrl, iv, sizeof(iv), &reading);
523 static char buffer[1024 * 1024];
524 BOOL bResult = InternetReadFile(hUrl, buffer, 1024 * 1024, &reading);
525 if (!bResult || !reading)
527 crypted += string(buffer, reading);
530 tstring contentDisposition = httpQueryCustom(hUrl, _T("Content-Disposition"));
532 tregex filenameRegex(_T("filename=.([^\"]*)"), regex::extended); //FIXME: case insensitive
535 if (regex_search(contentDisposition, match, filenameRegex)) {
539 InternetCloseHandle(hUrl);
542 memcpy(nonce, iv, sizeof(iv));
544 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
545 BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
546 authInfo.pbNonce = nonce;
547 authInfo.cbNonce = sizeof(nonce);
548 authInfo.pbTag = tag;
549 authInfo.cbTag = sizeof(tag);
551 ULONG unencrypted_size;
552 NTSTATUS status = BCryptDecrypt(dk, (PUCHAR)crypted.data(), crypted.size(),
553 &authInfo, iv, sizeof(iv), NULL, 0, &unencrypted_size, 0);
557 unencrypted_buffer = new char[unencrypted_size];
559 PUCHAR crypted_data = (PUCHAR)crypted.data();
560 ULONG crypted_size = (ULONG)crypted.size() - sizeof(tag);
561 memcpy(tag, crypted_data + crypted_size, sizeof(tag));
563 status = BCryptDecrypt(dk, crypted_data, crypted_size,
564 &authInfo, iv, sizeof(iv), (PUCHAR)unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
568 BCryptDestroyKey(dk);
570 TCHAR download_path[MAX_PATH + 1];
572 SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &_downloads);
573 StringCchCopy(download_path, MAX_PATH, _downloads);
574 CoTaskMemFree(_downloads);
576 GetTempPath(MAX_PATH, download_path);
578 if (filename == _T("")) {
579 filename = download_path;
580 filename += _T("\\pEp_");
581 filename += delivery.substr(0, 32);
582 filename += _T(".msi");
585 filename = tstring(download_path) + _T("\\") + filename;
588 hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
592 WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
595 delete[] unencrypted_buffer;
596 unencrypted_buffer = nullptr;
602 execute_file(filename);
606 if (unencrypted_buffer)
607 delete[] unencrypted_buffer;
611 InternetCloseHandle(hUrl);
612 BCryptDestroyKey(dk);
617 void GateKeeper::update_now()
619 NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
623 status = BCryptSetProperty(hAES, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
627 status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
632 internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
637 product_list products = registered_products();
640 for (auto i = products.begin(); i != products.end(); i++) {
642 update_product(*i, context++);
652 InternetCloseHandle(internet);
654 BCryptCloseAlgorithmProvider(hAES, 0);
656 BCryptCloseAlgorithmProvider(hRSA, 0);
662 GateKeeper *GateKeeper::the_gatekeeper = nullptr;