vb@432
|
1 |
#include "stdafx.h"
|
vb@88
|
2 |
|
vb@88
|
3 |
#include "GateKeeper.h"
|
vb@110
|
4 |
#include "pEpCOMServerAdapter.h"
|
vb@432
|
5 |
#include "CMainWindow.h"
|
vb@88
|
6 |
|
vb@88
|
7 |
using namespace std;
|
vb@88
|
8 |
|
vb@432
|
9 |
extern CMainWindow mainWindow;
|
vb@432
|
10 |
|
vb@130
|
11 |
// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd388945(v=vs.85).aspx
|
vb@123
|
12 |
|
vb@123
|
13 |
struct PUBLIC_KEY_VALUES {
|
vb@123
|
14 |
BLOBHEADER blobheader;
|
vb@123
|
15 |
RSAPUBKEY rsapubkey;
|
vb@123
|
16 |
BYTE modulus[4096];
|
vb@123
|
17 |
};
|
vb@123
|
18 |
|
vb@123
|
19 |
static void ReverseMemCopy(
|
vb@123
|
20 |
_Out_ BYTE *pbDest,
|
vb@123
|
21 |
_In_ BYTE const *pbSource,
|
vb@123
|
22 |
_In_ DWORD cb
|
markus@269
|
23 |
)
|
vb@123
|
24 |
{
|
vb@123
|
25 |
for (DWORD i = 0; i < cb; i++) {
|
vb@123
|
26 |
pbDest[cb - 1 - i] = pbSource[i];
|
vb@123
|
27 |
}
|
vb@123
|
28 |
}
|
vb@123
|
29 |
|
vb@123
|
30 |
static NTSTATUS ImportRsaPublicKey(
|
vb@123
|
31 |
_In_ BCRYPT_ALG_HANDLE hAlg, // CNG provider
|
vb@123
|
32 |
_In_ PUBLIC_KEY_VALUES *pKey, // Pointer to the RSAPUBKEY blob.
|
vb@123
|
33 |
_In_ BCRYPT_KEY_HANDLE *phKey // Receives a handle the imported public key.
|
markus@269
|
34 |
)
|
vb@123
|
35 |
{
|
vb@123
|
36 |
NTSTATUS hr = 0;
|
vb@123
|
37 |
|
vb@123
|
38 |
BYTE *pbPublicKey = NULL;
|
vb@123
|
39 |
DWORD cbKey = 0;
|
vb@123
|
40 |
|
vb@123
|
41 |
// Layout of the RSA public key blob:
|
vb@123
|
42 |
|
vb@123
|
43 |
// +----------------------------------------------------------------+
|
vb@123
|
44 |
// | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) |
|
vb@123
|
45 |
// +----------------------------------------------------------------+
|
vb@123
|
46 |
//
|
vb@123
|
47 |
// sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus
|
vb@123
|
48 |
// <--------------------------><------------><---------------------->
|
vb@123
|
49 |
//
|
vb@123
|
50 |
// BE = Big Endian Format
|
vb@123
|
51 |
|
vb@123
|
52 |
DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
|
vb@123
|
53 |
DWORD dwExp = pKey->rsapubkey.pubexp;
|
vb@123
|
54 |
DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
|
vb@123
|
55 |
(dwExp & 0x00FF0000) ? 3 :
|
vb@123
|
56 |
(dwExp & 0x0000FF00) ? 2 : 1;
|
vb@123
|
57 |
|
vb@123
|
58 |
BCRYPT_RSAKEY_BLOB *pRsaBlob;
|
vb@123
|
59 |
PBYTE pbCurrent;
|
vb@123
|
60 |
|
vb@123
|
61 |
if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
|
vb@123
|
62 |
goto cleanup;
|
vb@123
|
63 |
}
|
vb@123
|
64 |
|
vb@123
|
65 |
cbKey += cbExp;
|
vb@123
|
66 |
|
markus@269
|
67 |
pbPublicKey = (PBYTE)CoTaskMemAlloc(cbKey);
|
vb@123
|
68 |
if (pbPublicKey == NULL) {
|
vb@123
|
69 |
hr = E_OUTOFMEMORY;
|
vb@123
|
70 |
goto cleanup;
|
vb@123
|
71 |
}
|
vb@123
|
72 |
|
vb@123
|
73 |
ZeroMemory(pbPublicKey, cbKey);
|
markus@269
|
74 |
pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey);
|
vb@123
|
75 |
|
vb@123
|
76 |
//
|
vb@123
|
77 |
// Make the Public Key Blob Header
|
vb@123
|
78 |
//
|
vb@123
|
79 |
|
vb@123
|
80 |
pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
|
vb@123
|
81 |
pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
|
vb@123
|
82 |
pRsaBlob->cbPublicExp = cbExp;
|
vb@123
|
83 |
pRsaBlob->cbModulus = cbModulus;
|
vb@123
|
84 |
pRsaBlob->cbPrime1 = 0;
|
vb@123
|
85 |
pRsaBlob->cbPrime2 = 0;
|
vb@123
|
86 |
|
markus@269
|
87 |
pbCurrent = (PBYTE)(pRsaBlob + 1);
|
vb@123
|
88 |
|
vb@123
|
89 |
//
|
vb@123
|
90 |
// Copy pubExp Big Endian
|
vb@123
|
91 |
//
|
vb@123
|
92 |
|
markus@269
|
93 |
ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp);
|
vb@123
|
94 |
pbCurrent += cbExp;
|
vb@123
|
95 |
|
vb@123
|
96 |
//
|
vb@123
|
97 |
// Copy Modulus Big Endian
|
vb@123
|
98 |
//
|
vb@123
|
99 |
|
vb@123
|
100 |
ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
|
vb@123
|
101 |
|
vb@123
|
102 |
//
|
vb@123
|
103 |
// Import the public key
|
vb@123
|
104 |
//
|
vb@123
|
105 |
|
markus@269
|
106 |
hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR)pbPublicKey, cbKey, 0);
|
vb@123
|
107 |
|
vb@123
|
108 |
cleanup:
|
vb@123
|
109 |
CoTaskMemFree(pbPublicKey);
|
vb@123
|
110 |
return hr;
|
vb@123
|
111 |
}
|
vb@123
|
112 |
|
vb@96
|
113 |
namespace pEp {
|
vb@96
|
114 |
const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
|
vb@96
|
115 |
const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
|
vb@135
|
116 |
const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater");
|
vb@96
|
117 |
|
vb@96
|
118 |
const time_t GateKeeper::cycle = 7200; // 7200 sec is 2 h
|
vb@137
|
119 |
const time_t GateKeeper::fraction = 10; // first update is at 10% of cycle
|
Thomas@380
|
120 |
const DWORD GateKeeper::waiting = 10000; // 10 sec
|
vb@88
|
121 |
|
vb@117
|
122 |
GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
|
vb@228
|
123 |
: _self(self), now(time(NULL)), next(now /*+ time_diff()*/), hkUpdater(NULL),
|
markus@269
|
124 |
internet(NULL), hAES(NULL), hRSA(NULL)
|
vb@96
|
125 |
{
|
Thomas@370
|
126 |
if (the_gatekeeper)
|
Thomas@370
|
127 |
throw runtime_error("second instance of GateKeeper was initialized");
|
vb@366
|
128 |
|
markus@269
|
129 |
DeleteFile(get_lockFile().c_str());
|
vb@208
|
130 |
|
vb@96
|
131 |
LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
|
vb@96
|
132 |
assert(lResult == ERROR_SUCCESS);
|
vb@96
|
133 |
if (lResult == ERROR_SUCCESS)
|
vb@96
|
134 |
cu_open = true;
|
vb@96
|
135 |
else
|
vb@96
|
136 |
cu_open = false;
|
vb@88
|
137 |
|
vb@96
|
138 |
if (cu_open) {
|
vb@96
|
139 |
LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
|
markus@269
|
140 |
if (lResult != ERROR_SUCCESS)
|
markus@269
|
141 |
RegCreateKeyEx(cu, updater_reg_path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hkUpdater, NULL);
|
markus@269
|
142 |
}
|
vb@366
|
143 |
|
Thomas@370
|
144 |
the_gatekeeper = this;
|
vb@96
|
145 |
}
|
markus@269
|
146 |
|
vb@96
|
147 |
GateKeeper::~GateKeeper()
|
vb@96
|
148 |
{
|
Thomas@370
|
149 |
the_gatekeeper = nullptr;
|
vb@366
|
150 |
|
vb@96
|
151 |
if (cu_open) {
|
vb@96
|
152 |
if (hkUpdater)
|
vb@96
|
153 |
RegCloseKey(hkUpdater);
|
vb@96
|
154 |
RegCloseKey(cu);
|
vb@96
|
155 |
}
|
vb@96
|
156 |
}
|
vb@88
|
157 |
|
vb@96
|
158 |
time_t GateKeeper::time_diff()
|
vb@96
|
159 |
{
|
vb@96
|
160 |
try {
|
vb@96
|
161 |
static random_device rd;
|
vb@96
|
162 |
static mt19937 gen(rd());
|
vb@88
|
163 |
|
markus@269
|
164 |
uniform_int_distribution<time_t> dist(0, cycle / fraction);
|
vb@96
|
165 |
|
vb@96
|
166 |
return dist(gen);
|
vb@96
|
167 |
}
|
vb@96
|
168 |
catch (exception&) {
|
vb@96
|
169 |
assert(0);
|
vb@96
|
170 |
return 0;
|
vb@96
|
171 |
}
|
vb@88
|
172 |
}
|
vb@96
|
173 |
|
vb@96
|
174 |
void GateKeeper::keep()
|
vb@96
|
175 |
{
|
vb@96
|
176 |
if (!cu_open)
|
vb@96
|
177 |
return;
|
vb@96
|
178 |
|
vb@96
|
179 |
while (1) {
|
vb@96
|
180 |
keep_plugin();
|
vb@96
|
181 |
|
vb@96
|
182 |
now = time(NULL);
|
vb@96
|
183 |
assert(now != -1);
|
vb@96
|
184 |
|
vb@366
|
185 |
if (now > next) {
|
vb@96
|
186 |
next = now + GateKeeper::cycle;
|
vb@428
|
187 |
if (update_enabled())
|
vb@429
|
188 |
update_now();
|
vb@134
|
189 |
}
|
Thomas@370
|
190 |
|
Thomas@370
|
191 |
Sleep(waiting);
|
markus@284
|
192 |
}
|
markus@284
|
193 |
}
|
vb@96
|
194 |
|
vb@96
|
195 |
void GateKeeper::keep_plugin()
|
vb@96
|
196 |
{
|
markus@269
|
197 |
HKEY hkPluginStart = NULL;
|
vb@141
|
198 |
|
vb@141
|
199 |
LONG lResult = RegOpenKeyEx(cu, plugin_reg_path, 0, KEY_WRITE, &hkPluginStart);
|
vb@96
|
200 |
if (lResult != ERROR_SUCCESS)
|
vb@96
|
201 |
return;
|
vb@96
|
202 |
|
vb@141
|
203 |
DWORD v = 3;
|
markus@269
|
204 |
lResult = RegSetValueEx(hkPluginStart, plugin_reg_value_name, 0, REG_DWORD, (const BYTE *)&v, sizeof(DWORD));
|
vb@141
|
205 |
assert(lResult == ERROR_SUCCESS);
|
vb@141
|
206 |
|
vb@141
|
207 |
RegCloseKey(hkPluginStart);
|
vb@96
|
208 |
}
|
vb@96
|
209 |
|
vb@111
|
210 |
string GateKeeper::update_key()
|
vb@110
|
211 |
{
|
vb@110
|
212 |
static string key;
|
vb@110
|
213 |
|
vb@110
|
214 |
if (key.length() == 0) {
|
vb@110
|
215 |
HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
|
vb@110
|
216 |
assert(res);
|
vb@110
|
217 |
if (!res)
|
vb@110
|
218 |
throw runtime_error("FindResource: IRD_UPDATEKEY");
|
vb@110
|
219 |
|
vb@110
|
220 |
HGLOBAL hRes = LoadResource(_self->hModule(), res);
|
vb@110
|
221 |
assert(hRes);
|
vb@110
|
222 |
if (!hRes)
|
vb@110
|
223 |
throw runtime_error("LoadResource: IRD_UPDATEKEY");
|
vb@110
|
224 |
|
vb@110
|
225 |
key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
|
vb@110
|
226 |
UnlockResource(hRes);
|
vb@110
|
227 |
}
|
vb@110
|
228 |
|
vb@110
|
229 |
return key;
|
vb@110
|
230 |
}
|
vb@110
|
231 |
|
vb@116
|
232 |
BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
|
vb@112
|
233 |
{
|
vb@112
|
234 |
aeskey_t key;
|
vb@112
|
235 |
|
vb@112
|
236 |
static random_device rd;
|
vb@112
|
237 |
static mt19937 gen(rd());
|
vb@112
|
238 |
|
vb@123
|
239 |
uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
|
vb@112
|
240 |
|
vb@123
|
241 |
for (int i = 0; i < 8; i++)
|
markus@269
|
242 |
key.dw_key[i] = (uint32_t)dist(gen);
|
vb@113
|
243 |
|
vb@116
|
244 |
BCRYPT_KEY_HANDLE hKey;
|
vb@142
|
245 |
|
markus@269
|
246 |
NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR)&key, (ULONG) sizeof(aeskey_t), 0);
|
vb@116
|
247 |
assert(status == 0);
|
vb@116
|
248 |
if (status)
|
vb@116
|
249 |
throw runtime_error("BCryptGenerateSymmetricKey");
|
vb@116
|
250 |
|
vb@142
|
251 |
#ifndef NDEBUG
|
vb@131
|
252 |
DWORD keylength = 0;
|
vb@131
|
253 |
ULONG copied = 0;
|
markus@269
|
254 |
status = BCryptGetProperty(hKey, BCRYPT_KEY_LENGTH, (PUCHAR)&keylength, sizeof(DWORD), &copied, 0);
|
vb@131
|
255 |
assert(keylength == 256);
|
vb@142
|
256 |
#endif
|
vb@131
|
257 |
|
vb@116
|
258 |
return hKey;
|
vb@113
|
259 |
}
|
vb@113
|
260 |
|
vb@121
|
261 |
string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
|
vb@113
|
262 |
{
|
vb@113
|
263 |
string result;
|
vb@113
|
264 |
|
vb@299
|
265 |
BCRYPT_KEY_HANDLE hUpdateKey = NULL;
|
vb@117
|
266 |
string _update_key = update_key();
|
vb@113
|
267 |
|
vb@299
|
268 |
PCERT_PUBLIC_KEY_INFO uk = NULL;
|
vb@299
|
269 |
DWORD uk_size = 0;
|
vb@142
|
270 |
|
vb@123
|
271 |
BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
|
markus@269
|
272 |
(const BYTE *)_update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
|
vb@123
|
273 |
if (!bResult)
|
vb@123
|
274 |
throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
|
vb@123
|
275 |
|
vb@299
|
276 |
PUBLIC_KEY_VALUES *_uk = NULL;
|
vb@299
|
277 |
DWORD _uk_size = 0;
|
vb@142
|
278 |
|
vb@123
|
279 |
bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
|
vb@123
|
280 |
uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
|
vb@123
|
281 |
LocalFree(uk);
|
vb@123
|
282 |
if (!bResult)
|
vb@123
|
283 |
throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
|
vb@123
|
284 |
|
vb@123
|
285 |
HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
|
vb@123
|
286 |
LocalFree(_uk);
|
vb@302
|
287 |
if (!hUpdateKey)
|
vb@123
|
288 |
throw runtime_error("ImportRsaPublicKey");
|
vb@117
|
289 |
|
vb@129
|
290 |
ULONG psize;
|
vb@129
|
291 |
NTSTATUS status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, NULL, 0, &psize, 0);
|
vb@129
|
292 |
char *prop = new char[psize];
|
markus@269
|
293 |
TCHAR *_prop = (TCHAR *)prop;
|
vb@299
|
294 |
status = BCryptGetProperty(hUpdateKey, BCRYPT_ALGORITHM_NAME, (PUCHAR)prop, psize, &psize, 0);
|
vb@302
|
295 |
if (status)
|
vb@302
|
296 |
throw runtime_error("BCryptGetProperty: BCRYPT_ALGORITHM_NAME");
|
vb@129
|
297 |
|
vb@129
|
298 |
ULONG export_size;
|
vb@129
|
299 |
status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, NULL,
|
vb@129
|
300 |
&export_size, 0);
|
vb@129
|
301 |
if (status)
|
vb@129
|
302 |
throw runtime_error("BCryptExportKey: measuring export size");
|
vb@129
|
303 |
|
vb@129
|
304 |
PUCHAR _delivery_key = new UCHAR[export_size];
|
vb@120
|
305 |
ULONG copied;
|
vb@129
|
306 |
status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, _delivery_key, export_size,
|
markus@269
|
307 |
&copied, 0);
|
vb@129
|
308 |
if (status) {
|
vb@129
|
309 |
delete[] _delivery_key;
|
vb@120
|
310 |
throw runtime_error("BCryptExportKey: delivery_key");
|
vb@129
|
311 |
}
|
vb@120
|
312 |
|
vb@117
|
313 |
BCRYPT_OAEP_PADDING_INFO pi;
|
vb@119
|
314 |
memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
|
vb@117
|
315 |
pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
|
vb@117
|
316 |
|
vb@301
|
317 |
ULONG result_size = 0;
|
vb@118
|
318 |
PUCHAR _result = NULL;
|
vb@129
|
319 |
ULONG blob_size = export_size - sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
|
vb@129
|
320 |
PUCHAR blob = _delivery_key + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
|
vb@129
|
321 |
status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
|
vb@119
|
322 |
if (status) {
|
vb@129
|
323 |
delete[] _delivery_key;
|
vb@119
|
324 |
BCryptDestroyKey(hUpdateKey);
|
vb@117
|
325 |
throw runtime_error("BCryptEncrypt: calculating result size");
|
vb@119
|
326 |
}
|
vb@117
|
327 |
|
vb@301
|
328 |
_result = new UCHAR[result_size + 1];
|
vb@129
|
329 |
status = BCryptEncrypt(hUpdateKey, blob, blob_size, &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
|
vb@129
|
330 |
delete[] _delivery_key;
|
vb@119
|
331 |
if (status) {
|
vb@119
|
332 |
BCryptDestroyKey(hUpdateKey);
|
vb@119
|
333 |
delete[] _result;
|
vb@118
|
334 |
throw runtime_error("BCryptEncrypt: encrypting using update_key");
|
vb@119
|
335 |
}
|
vb@117
|
336 |
|
vb@120
|
337 |
BCryptDestroyKey(hUpdateKey);
|
vb@120
|
338 |
|
vb@117
|
339 |
stringstream s;
|
vb@129
|
340 |
for (ULONG i = 0; i < copied; i++) {
|
vb@129
|
341 |
s << hex << setw(2) << setfill('0');
|
markus@269
|
342 |
s << (int)_result[i];
|
vb@129
|
343 |
}
|
vb@117
|
344 |
delete[] _result;
|
vb@117
|
345 |
s >> result;
|
vb@117
|
346 |
|
vb@113
|
347 |
return result;
|
vb@112
|
348 |
}
|
vb@112
|
349 |
|
vb@428
|
350 |
void GateKeeper::enable_update()
|
vb@428
|
351 |
{
|
vb@428
|
352 |
LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_WRITE, &hkUpdater);
|
vb@428
|
353 |
if (lResult != ERROR_SUCCESS)
|
vb@428
|
354 |
return;
|
vb@428
|
355 |
|
vb@428
|
356 |
lResult = RegSetValueExW(hkUpdater, NULL, 0, REG_SZ, (const BYTE *) _T("1"), sizeof(TCHAR)*2);
|
vb@428
|
357 |
}
|
vb@428
|
358 |
|
vb@428
|
359 |
void GateKeeper::disable_update()
|
vb@428
|
360 |
{
|
vb@428
|
361 |
LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_WRITE, &hkUpdater);
|
vb@428
|
362 |
if (lResult != ERROR_SUCCESS)
|
vb@428
|
363 |
return;
|
vb@428
|
364 |
|
vb@428
|
365 |
lResult = RegSetValueEx(hkUpdater, NULL, 0, REG_SZ, (const BYTE *) _T("0"), sizeof(TCHAR) * 2);
|
vb@428
|
366 |
}
|
vb@428
|
367 |
|
vb@428
|
368 |
bool GateKeeper::update_enabled()
|
vb@428
|
369 |
{
|
vb@428
|
370 |
bool enabled = true;
|
vb@428
|
371 |
|
vb@428
|
372 |
DWORD esize;
|
vb@428
|
373 |
RegGetValue(cu, updater_reg_path, NULL, RRF_RT_REG_SZ, NULL, NULL, &esize);
|
vb@428
|
374 |
if (esize) {
|
vb@428
|
375 |
TCHAR* edata = new TCHAR[esize];
|
vb@428
|
376 |
RegGetValue(cu, updater_reg_path, NULL, RRF_RT_REG_SZ, NULL, edata, &esize);
|
vb@428
|
377 |
if (tstring(edata) == _T("0"))
|
vb@428
|
378 |
enabled = false;
|
vb@428
|
379 |
delete[] edata;
|
vb@428
|
380 |
}
|
vb@428
|
381 |
|
vb@428
|
382 |
return enabled;
|
vb@428
|
383 |
}
|
vb@428
|
384 |
|
vb@214
|
385 |
GateKeeper::product_list GateKeeper::registered_products()
|
vb@96
|
386 |
{
|
vb@214
|
387 |
product_list products;
|
vb@88
|
388 |
|
vb@96
|
389 |
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
|
vb@300
|
390 |
static TCHAR value_name[16384];
|
vb@96
|
391 |
DWORD value_name_size;
|
vb@300
|
392 |
static TCHAR value[L_MAX_URL_LENGTH + 1];
|
vb@96
|
393 |
DWORD value_size;
|
vb@88
|
394 |
|
vb@96
|
395 |
LONG lResult = ERROR_SUCCESS;
|
vb@96
|
396 |
for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
|
vb@138
|
397 |
value_name_size = 16383;
|
vb@96
|
398 |
value_size = L_MAX_URL_LENGTH + 1;
|
markus@269
|
399 |
lResult = RegEnumValue(hkUpdater, i, value_name, &value_name_size, NULL, NULL, (LPBYTE)value, &value_size);
|
vb@139
|
400 |
if (lResult == ERROR_SUCCESS) {
|
vb@96
|
401 |
products.push_back({ value_name, value });
|
vb@139
|
402 |
}
|
vb@88
|
403 |
}
|
vb@88
|
404 |
|
vb@96
|
405 |
return products;
|
vb@88
|
406 |
}
|
vb@88
|
407 |
|
Thomas@360
|
408 |
void GateKeeper::execute_file(tstring filename)
|
vb@126
|
409 |
{
|
markus@269
|
410 |
HANDLE hMutex = CreateMutex(NULL, TRUE, _T("PEPINSTALLERMUTEX"));
|
markus@269
|
411 |
if (hMutex) {
|
markus@269
|
412 |
CloseHandle(hMutex);
|
markus@269
|
413 |
ShellExecute(NULL, _T("open"), filename.c_str(), NULL, NULL, SW_SHOW);
|
markus@269
|
414 |
}
|
vb@126
|
415 |
}
|
vb@126
|
416 |
|
markus@269
|
417 |
tstring GateKeeper::get_lockFile()
|
markus@269
|
418 |
{
|
markus@269
|
419 |
static const tstring _fileName = _T("\\pEpSetup.lck");
|
markus@269
|
420 |
static tstring fileName;
|
vb@207
|
421 |
|
markus@269
|
422 |
if (fileName.length() == 0) {
|
markus@269
|
423 |
unique_ptr < TCHAR[] > _pathName(new TCHAR[MAX_PATH + 1]);
|
markus@269
|
424 |
DWORD size = GetTempPath(MAX_PATH, _pathName.get());
|
markus@269
|
425 |
if (size > MAX_PATH - _fileName.size())
|
markus@269
|
426 |
throw runtime_error("TEMP path too long");
|
vb@207
|
427 |
|
markus@269
|
428 |
fileName = _pathName.get();
|
markus@269
|
429 |
fileName += _fileName;
|
markus@269
|
430 |
}
|
vb@207
|
431 |
|
markus@269
|
432 |
return fileName;
|
markus@269
|
433 |
}
|
vb@208
|
434 |
|
Thomas@364
|
435 |
// Retrieving Headers Using HTTP_QUERY_CUSTOM
|
Thomas@364
|
436 |
static tstring httpQueryCustom(HINTERNET hHttp, tstring header)
|
Thomas@364
|
437 |
{
|
Thomas@364
|
438 |
DWORD dwResult = 0;
|
Thomas@364
|
439 |
LPTSTR lpOutBuffer = StrDup(header.c_str());
|
Thomas@361
|
440 |
|
Thomas@364
|
441 |
retry:
|
Thomas@361
|
442 |
|
Thomas@364
|
443 |
if (!HttpQueryInfo(hHttp, HTTP_QUERY_CUSTOM, (LPVOID)lpOutBuffer, &dwResult, NULL))
|
Thomas@364
|
444 |
{
|
Thomas@364
|
445 |
if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
|
Thomas@364
|
446 |
{
|
Thomas@364
|
447 |
// Code to handle the case where the header isn't available.
|
Thomas@364
|
448 |
LocalFree(lpOutBuffer);
|
Thomas@364
|
449 |
throw(runtime_error("ERROR_HTTP_HEADER_NOT_FOUND"));
|
Thomas@364
|
450 |
}
|
Thomas@364
|
451 |
else
|
Thomas@364
|
452 |
{
|
Thomas@364
|
453 |
// Check for an insufficient buffer.
|
Thomas@364
|
454 |
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
Thomas@364
|
455 |
{
|
Thomas@364
|
456 |
// Allocate the necessary buffer.
|
Thomas@364
|
457 |
LocalFree(lpOutBuffer);
|
Thomas@364
|
458 |
lpOutBuffer = (LPTSTR)LocalAlloc(LMEM_FIXED, dwResult + 1);
|
Thomas@361
|
459 |
|
Thomas@364
|
460 |
// Rewrite the header name in the buffer.
|
Thomas@364
|
461 |
StringCchPrintf(lpOutBuffer, dwResult, header.c_str());
|
Thomas@361
|
462 |
|
Thomas@364
|
463 |
// Retry the call.
|
Thomas@364
|
464 |
goto retry;
|
Thomas@364
|
465 |
}
|
Thomas@364
|
466 |
else
|
Thomas@364
|
467 |
{
|
Thomas@364
|
468 |
// Error handling code.
|
Thomas@364
|
469 |
LocalFree(lpOutBuffer);
|
Thomas@364
|
470 |
// FIXME: Add GetLastError()
|
Thomas@364
|
471 |
throw(runtime_error("Unknown"));
|
Thomas@364
|
472 |
}
|
Thomas@364
|
473 |
}
|
Thomas@364
|
474 |
}
|
Thomas@361
|
475 |
|
Thomas@364
|
476 |
tstring result(lpOutBuffer);
|
Thomas@364
|
477 |
LocalFree(lpOutBuffer);
|
Thomas@361
|
478 |
|
Thomas@364
|
479 |
return result;
|
Thomas@364
|
480 |
}
|
Thomas@361
|
481 |
|
vb@363
|
482 |
bool GateKeeper::update_product(product p, DWORD context)
|
vb@208
|
483 |
{
|
markus@269
|
484 |
{
|
markus@269
|
485 |
HANDLE file = CreateFile(get_lockFile().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
|
markus@269
|
486 |
if (file == INVALID_HANDLE_VALUE) {
|
vb@363
|
487 |
return false;
|
markus@269
|
488 |
}
|
markus@269
|
489 |
else {
|
markus@269
|
490 |
CloseHandle(file);
|
markus@269
|
491 |
DeleteFile(get_lockFile().c_str());
|
markus@269
|
492 |
}
|
markus@269
|
493 |
}
|
vb@199
|
494 |
|
vb@127
|
495 |
BCRYPT_KEY_HANDLE dk = delivery_key();
|
vb@126
|
496 |
#ifdef UNICODE
|
vb@127
|
497 |
tstring delivery = utility::utf16_string(wrapped_delivery_key(dk));
|
vb@126
|
498 |
#else
|
vb@126
|
499 |
tstring delivery = wrapped_delivery_key(delivery_key());
|
vb@126
|
500 |
#endif
|
vb@124
|
501 |
tstring url = p.second;
|
vb@124
|
502 |
url += _T("&challenge=");
|
vb@124
|
503 |
url += delivery;
|
vb@124
|
504 |
tstring headers;
|
vb@124
|
505 |
HINTERNET hUrl = InternetOpenUrl(internet, url.c_str(), headers.c_str(), headers.length(),
|
markus@269
|
506 |
INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
|
vb@97
|
507 |
if (hUrl == NULL)
|
vb@363
|
508 |
return false;
|
vb@94
|
509 |
|
vb@127
|
510 |
string crypted;
|
vb@127
|
511 |
string unencrypted;
|
vb@133
|
512 |
UCHAR iv[12];
|
vb@133
|
513 |
UCHAR nonce[sizeof(iv)];
|
vb@133
|
514 |
UCHAR tag[16];
|
vb@138
|
515 |
tstring filename;
|
vb@138
|
516 |
HANDLE hFile = NULL;
|
vb@138
|
517 |
char *unencrypted_buffer = NULL;
|
Thomas@364
|
518 |
bool result = false;
|
vb@107
|
519 |
|
vb@130
|
520 |
try {
|
Thomas@361
|
521 |
|
vb@133
|
522 |
DWORD reading;
|
vb@133
|
523 |
InternetReadFile(hUrl, iv, sizeof(iv), &reading);
|
vb@133
|
524 |
|
vb@432
|
525 |
if (reading)
|
vb@432
|
526 |
mainWindow.ShowNotificationInfo(_T("Downloading update"), _T("An update for p≡p arrived and is being downloaded."));
|
vb@432
|
527 |
|
vb@133
|
528 |
if (reading) do {
|
markus@269
|
529 |
static char buffer[1024 * 1024];
|
markus@269
|
530 |
BOOL bResult = InternetReadFile(hUrl, buffer, 1024 * 1024, &reading);
|
vb@130
|
531 |
if (!bResult || !reading)
|
vb@130
|
532 |
break;
|
vb@130
|
533 |
crypted += string(buffer, reading);
|
vb@130
|
534 |
} while (1);
|
vb@279
|
535 |
|
Thomas@364
|
536 |
tstring contentDisposition = httpQueryCustom(hUrl, _T("Content-Disposition"));
|
Thomas@361
|
537 |
|
Thomas@364
|
538 |
tregex filenameRegex(_T("filename=.([^\"]*)"), regex::extended); //FIXME: case insensitive
|
Thomas@364
|
539 |
tsmatch match;
|
Thomas@361
|
540 |
|
Thomas@364
|
541 |
if (regex_search(contentDisposition, match, filenameRegex)) {
|
Thomas@364
|
542 |
filename = match[1];
|
Thomas@364
|
543 |
}
|
Thomas@361
|
544 |
|
vb@279
|
545 |
InternetCloseHandle(hUrl);
|
vb@279
|
546 |
hUrl = NULL;
|
vb@279
|
547 |
|
vb@279
|
548 |
memcpy(nonce, iv, sizeof(iv));
|
vb@279
|
549 |
|
vb@279
|
550 |
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
|
vb@279
|
551 |
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
|
vb@279
|
552 |
authInfo.pbNonce = nonce;
|
vb@279
|
553 |
authInfo.cbNonce = sizeof(nonce);
|
vb@279
|
554 |
authInfo.pbTag = tag;
|
vb@279
|
555 |
authInfo.cbTag = sizeof(tag);
|
vb@279
|
556 |
|
vb@279
|
557 |
ULONG unencrypted_size;
|
vb@279
|
558 |
NTSTATUS status = BCryptDecrypt(dk, (PUCHAR)crypted.data(), crypted.size(),
|
vb@279
|
559 |
&authInfo, iv, sizeof(iv), NULL, 0, &unencrypted_size, 0);
|
vb@279
|
560 |
if (status)
|
vb@279
|
561 |
goto closing;
|
vb@279
|
562 |
|
vb@279
|
563 |
unencrypted_buffer = new char[unencrypted_size];
|
vb@279
|
564 |
|
vb@279
|
565 |
PUCHAR crypted_data = (PUCHAR)crypted.data();
|
vb@279
|
566 |
ULONG crypted_size = (ULONG)crypted.size() - sizeof(tag);
|
vb@279
|
567 |
memcpy(tag, crypted_data + crypted_size, sizeof(tag));
|
vb@279
|
568 |
|
vb@279
|
569 |
status = BCryptDecrypt(dk, crypted_data, crypted_size,
|
vb@279
|
570 |
&authInfo, iv, sizeof(iv), (PUCHAR)unencrypted_buffer, unencrypted_size, &unencrypted_size, 0);
|
vb@279
|
571 |
if (status)
|
vb@279
|
572 |
goto closing;
|
vb@279
|
573 |
|
vb@279
|
574 |
BCryptDestroyKey(dk);
|
vb@279
|
575 |
|
vb@431
|
576 |
TCHAR download_path[MAX_PATH + 1];
|
vb@431
|
577 |
PWSTR _downloads;
|
vb@431
|
578 |
SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &_downloads);
|
vb@431
|
579 |
StringCchCopy(download_path, MAX_PATH, _downloads);
|
vb@431
|
580 |
CoTaskMemFree(_downloads);
|
vb@431
|
581 |
|
vb@431
|
582 |
GetTempPath(MAX_PATH, download_path);
|
Thomas@361
|
583 |
|
Thomas@364
|
584 |
if (filename == _T("")) {
|
vb@431
|
585 |
filename = download_path;
|
Thomas@364
|
586 |
filename += _T("\\pEp_");
|
Thomas@364
|
587 |
filename += delivery.substr(0, 32);
|
Thomas@364
|
588 |
filename += _T(".msi");
|
Thomas@364
|
589 |
}
|
Thomas@364
|
590 |
else {
|
vb@431
|
591 |
filename = tstring(download_path) + _T("\\") + filename;
|
Thomas@364
|
592 |
}
|
vb@279
|
593 |
|
vb@279
|
594 |
hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
vb@279
|
595 |
if (!hFile)
|
vb@279
|
596 |
goto closing;
|
vb@279
|
597 |
DWORD writing;
|
vb@279
|
598 |
WriteFile(hFile, unencrypted_buffer, unencrypted_size, &writing, NULL);
|
vb@279
|
599 |
CloseHandle(hFile);
|
Thomas@364
|
600 |
hFile = NULL;
|
vb@279
|
601 |
delete[] unencrypted_buffer;
|
vb@279
|
602 |
unencrypted_buffer = nullptr;
|
vb@130
|
603 |
}
|
vb@138
|
604 |
catch (exception&) {
|
vb@138
|
605 |
goto closing;
|
vb@130
|
606 |
}
|
vb@133
|
607 |
|
Thomas@360
|
608 |
execute_file(filename);
|
Thomas@364
|
609 |
result = true;
|
vb@127
|
610 |
|
vb@126
|
611 |
closing:
|
vb@127
|
612 |
if (unencrypted_buffer)
|
vb@127
|
613 |
delete[] unencrypted_buffer;
|
vb@126
|
614 |
if (hFile)
|
vb@126
|
615 |
CloseHandle(hFile);
|
vb@126
|
616 |
if (hUrl)
|
vb@126
|
617 |
InternetCloseHandle(hUrl);
|
vb@127
|
618 |
BCryptDestroyKey(dk);
|
vb@363
|
619 |
|
Thomas@364
|
620 |
return result;
|
vb@96
|
621 |
}
|
vb@88
|
622 |
|
vb@429
|
623 |
void GateKeeper::update_now()
|
vb@96
|
624 |
{
|
vb@116
|
625 |
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
|
vb@116
|
626 |
assert(status == 0);
|
vb@116
|
627 |
if (status)
|
vb@116
|
628 |
goto closing;
|
markus@269
|
629 |
status = BCryptSetProperty(hAES, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
|
vb@128
|
630 |
if (status)
|
vb@128
|
631 |
goto closing;
|
vb@116
|
632 |
|
vb@116
|
633 |
status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
|
vb@116
|
634 |
assert(status == 0);
|
vb@116
|
635 |
if (status)
|
vb@116
|
636 |
goto closing;
|
vb@116
|
637 |
|
vb@124
|
638 |
internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
|
vb@124
|
639 |
if (!internet)
|
vb@124
|
640 |
goto closing;
|
vb@97
|
641 |
|
markus@269
|
642 |
{
|
markus@269
|
643 |
product_list products = registered_products();
|
markus@269
|
644 |
DWORD context = 0;
|
vb@138
|
645 |
|
markus@269
|
646 |
for (auto i = products.begin(); i != products.end(); i++) {
|
markus@269
|
647 |
try {
|
markus@269
|
648 |
update_product(*i, context++);
|
markus@269
|
649 |
}
|
markus@269
|
650 |
catch (exception&) {
|
vb@214
|
651 |
|
markus@269
|
652 |
}
|
markus@269
|
653 |
}
|
markus@269
|
654 |
}
|
vb@97
|
655 |
|
vb@116
|
656 |
closing:
|
vb@116
|
657 |
if (internet)
|
vb@116
|
658 |
InternetCloseHandle(internet);
|
vb@116
|
659 |
if (hAES)
|
vb@116
|
660 |
BCryptCloseAlgorithmProvider(hAES, 0);
|
vb@116
|
661 |
if (hRSA)
|
vb@116
|
662 |
BCryptCloseAlgorithmProvider(hRSA, 0);
|
vb@97
|
663 |
internet = NULL;
|
vb@116
|
664 |
hAES = NULL;
|
vb@116
|
665 |
hRSA = NULL;
|
vb@96
|
666 |
}
|
vb@88
|
667 |
|
Thomas@370
|
668 |
GateKeeper *GateKeeper::the_gatekeeper = nullptr;
|
vb@366
|
669 |
|
vb@96
|
670 |
} // namespace pEp
|