3 #include "GateKeeper.h"
4 #include "pEpCOMServerAdapter.h"
5 #include "utf8_helper.h"
9 // from https://msdn.microsoft.com/en-us/library/windows/desktop/dd388945(v=vs.85).aspx
11 struct PUBLIC_KEY_VALUES {
12 BLOBHEADER blobheader;
17 static void ReverseMemCopy(
19 _In_ BYTE const *pbSource,
23 for (DWORD i = 0; i < cb; i++) {
24 pbDest[cb - 1 - i] = pbSource[i];
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.
36 BYTE *pbPublicKey = NULL;
39 // Layout of the RSA public key blob:
41 // +----------------------------------------------------------------+
42 // | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) |
43 // +----------------------------------------------------------------+
45 // sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus
46 // <--------------------------><------------><---------------------->
48 // BE = Big Endian Format
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;
56 BCRYPT_RSAKEY_BLOB *pRsaBlob;
59 if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
65 pbPublicKey = (PBYTE)CoTaskMemAlloc(cbKey);
66 if (pbPublicKey == NULL) {
71 ZeroMemory(pbPublicKey, cbKey);
72 pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey);
75 // Make the Public Key Blob Header
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;
85 pbCurrent = (PBYTE)(pRsaBlob + 1);
88 // Copy pubExp Big Endian
91 ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp);
95 // Copy Modulus Big Endian
98 ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
101 // Import the public key
104 hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR)pbPublicKey, cbKey, 0);
107 CoTaskMemFree(pbPublicKey);
112 std::mutex GateKeeper::update_wait_mtx;
113 std::condition_variable GateKeeper::update_wait_var;
114 bool GateKeeper::update_wait_forced = false;
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");
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
124 GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
125 : _self(self), now(time(NULL)), next(now /*+ time_diff()*/), hkUpdater(NULL),
126 internet(NULL), hAES(NULL), hRSA(NULL)
128 DeleteFile(get_lockFile().c_str());
130 LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
131 assert(lResult == ERROR_SUCCESS);
132 if (lResult == ERROR_SUCCESS)
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);
144 GateKeeper::~GateKeeper()
148 RegCloseKey(hkUpdater);
153 time_t GateKeeper::time_diff()
156 static random_device rd;
157 static mt19937 gen(rd());
159 uniform_int_distribution<time_t> dist(0, cycle / fraction);
169 void GateKeeper::keep()
181 // We need to sleep, but we should be interruptible by the update_now() method.
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;
189 if (force_check || now > next) {
190 next = now + GateKeeper::cycle;
196 void GateKeeper::update_now()
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();
204 void GateKeeper::keep_plugin()
206 HKEY hkPluginStart = NULL;
208 LONG lResult = RegOpenKeyEx(cu, plugin_reg_path, 0, KEY_WRITE, &hkPluginStart);
209 if (lResult != ERROR_SUCCESS)
213 lResult = RegSetValueEx(hkPluginStart, plugin_reg_value_name, 0, REG_DWORD, (const BYTE *)&v, sizeof(DWORD));
214 assert(lResult == ERROR_SUCCESS);
216 RegCloseKey(hkPluginStart);
219 string GateKeeper::update_key()
223 if (key.length() == 0) {
224 HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
227 throw runtime_error("FindResource: IRD_UPDATEKEY");
229 HGLOBAL hRes = LoadResource(_self->hModule(), res);
232 throw runtime_error("LoadResource: IRD_UPDATEKEY");
234 key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
235 UnlockResource(hRes);
241 BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
245 static random_device rd;
246 static mt19937 gen(rd());
248 uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
250 for (int i = 0; i < 8; i++)
251 key.dw_key[i] = (uint32_t)dist(gen);
253 BCRYPT_KEY_HANDLE hKey;
255 NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR)&key, (ULONG) sizeof(aeskey_t), 0);
258 throw runtime_error("BCryptGenerateSymmetricKey");
263 status = BCryptGetProperty(hKey, BCRYPT_KEY_LENGTH, (PUCHAR)&keylength, sizeof(DWORD), &copied, 0);
264 assert(keylength == 256);
270 string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
274 BCRYPT_KEY_HANDLE hUpdateKey = NULL;
275 string _update_key = update_key();
277 PCERT_PUBLIC_KEY_INFO uk = NULL;
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);
283 throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
285 PUBLIC_KEY_VALUES *_uk = NULL;
288 bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
289 uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
292 throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
294 HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
296 if (!SUCCEEDED(hResult))
297 throw runtime_error("ImportRsaPublicKey");
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);
306 status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, NULL,
309 throw runtime_error("BCryptExportKey: measuring export size");
311 PUCHAR _delivery_key = new UCHAR[export_size];
313 status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, _delivery_key, export_size,
316 delete[] _delivery_key;
317 throw runtime_error("BCryptExportKey: delivery_key");
320 BCRYPT_OAEP_PADDING_INFO pi;
321 memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
322 pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
325 PUCHAR _result = NULL;
326 ULONG blob_size = export_size - sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
327 PUCHAR blob = _delivery_key + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
328 status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
330 delete[] _delivery_key;
331 BCryptDestroyKey(hUpdateKey);
332 throw runtime_error("BCryptEncrypt: calculating result size");
335 _result = new UCHAR[result_size];
336 status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
337 delete[] _delivery_key;
339 BCryptDestroyKey(hUpdateKey);
341 throw runtime_error("BCryptEncrypt: encrypting using update_key");
344 BCryptDestroyKey(hUpdateKey);
347 for (ULONG i = 0; i < copied; i++) {
348 s << hex << setw(2) << setfill('0');
349 s << (int)_result[i];
357 GateKeeper::product_list GateKeeper::registered_products()
359 product_list products;
361 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
362 TCHAR value_name[16384];
363 DWORD value_name_size;
364 TCHAR value[L_MAX_URL_LENGTH + 1];
367 LONG lResult = ERROR_SUCCESS;
368 for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
369 value_name_size = 16383;
370 value_size = L_MAX_URL_LENGTH + 1;
371 lResult = RegEnumValue(hkUpdater, i, value_name, &value_name_size, NULL, NULL, (LPBYTE)value, &value_size);
372 if (lResult == ERROR_SUCCESS) {
373 products.push_back({ value_name, value });
380 void GateKeeper::install_msi(tstring filename)
382 HANDLE hMutex = CreateMutex(NULL, TRUE, _T("PEPINSTALLERMUTEX"));
385 ShellExecute(NULL, _T("open"), filename.c_str(), NULL, NULL, SW_SHOW);
389 tstring GateKeeper::get_lockFile()
391 static const tstring _fileName = _T("\\pEpSetup.lck");
392 static tstring fileName;
394 if (fileName.length() == 0) {
395 unique_ptr < TCHAR[] > _pathName(new TCHAR[MAX_PATH + 1]);
396 DWORD size = GetTempPath(MAX_PATH, _pathName.get());
397 if (size > MAX_PATH - _fileName.size())
398 throw runtime_error("TEMP path too long");
400 fileName = _pathName.get();
401 fileName += _fileName;
407 void GateKeeper::update_product(product p, DWORD context)
410 HANDLE file = CreateFile(get_lockFile().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
411 if (file == INVALID_HANDLE_VALUE) {
416 DeleteFile(get_lockFile().c_str());
420 BCRYPT_KEY_HANDLE dk = delivery_key();
422 tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
424 tstring delivery = wrapped_delivery_key(delivery_key());
426 tstring url = p.second;
427 url += _T("&challenge=");
430 HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
431 INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
438 UCHAR nonce[sizeof(iv)];
442 char *unencrypted_buffer = NULL;
446 InternetReadFile(hUrl, iv, sizeof(iv), &reading);
449 static char buffer[1024 * 1024];
450 BOOL bResult = InternetReadFile(hUrl, buffer, 1024 * 1024, &reading);
451 if (!bResult || !reading)
453 crypted += string(buffer, reading);
456 InternetCloseHandle(hUrl);
459 memcpy(nonce, iv, sizeof(iv));
461 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
462 BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
463 authInfo.pbNonce = nonce;
464 authInfo.cbNonce = sizeof(nonce);
465 authInfo.pbTag = tag;
466 authInfo.cbTag = sizeof(tag);
468 ULONG unencrypted_size;
469 NTSTATUS status = BCryptDecrypt(dk, (PUCHAR)crypted.data(), crypted.size(),
470 &authInfo, iv, sizeof(iv), NULL, 0, &unencrypted_size, 0);
474 unencrypted_buffer = new char[unencrypted_size];
476 PUCHAR crypted_data = (PUCHAR)crypted.data();
477 ULONG crypted_size = (ULONG)crypted.size() - sizeof(tag);
478 memcpy(tag, crypted_data + crypted_size, sizeof(tag));
480 status = BCryptDecrypt(dk, crypted_data, crypted_size,
481 &authInfo, iv, sizeof(iv), (PUCHAR)unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
485 BCryptDestroyKey(dk);
487 TCHAR temp_path[MAX_PATH + 1];
488 GetTempPath(MAX_PATH, temp_path);
489 filename = temp_path;
490 filename += _T("\\pEp_");
491 filename += delivery.substr(0, 32);
492 filename += _T(".msi");
494 hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
498 WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
500 delete[] unencrypted_buffer;
501 unencrypted_buffer = nullptr;
507 install_msi(filename);
510 if (unencrypted_buffer)
511 delete[] unencrypted_buffer;
515 InternetCloseHandle(hUrl);
516 BCryptDestroyKey(dk);
519 void GateKeeper::keep_updated()
521 NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
525 status = BCryptSetProperty(hAES, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
529 status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
534 internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
539 product_list products = registered_products();
542 for (auto i = products.begin(); i != products.end(); i++) {
544 update_product(*i, context++);
554 InternetCloseHandle(internet);
556 BCryptCloseAlgorithmProvider(hAES, 0);
558 BCryptCloseAlgorithmProvider(hRSA, 0);