vb@88
|
1 |
#include "stdafx.h"
|
vb@88
|
2 |
|
vb@88
|
3 |
#include "GateKeeper.h"
|
vb@110
|
4 |
#include "pEpCOMServerAdapter.h"
|
vb@123
|
5 |
#include "utf8_helper.h"
|
vb@88
|
6 |
|
vb@88
|
7 |
using namespace std;
|
vb@88
|
8 |
|
vb@123
|
9 |
// https://gist.github.com/mcdurdin/5626617
|
vb@123
|
10 |
|
vb@123
|
11 |
struct PUBLIC_KEY_VALUES {
|
vb@123
|
12 |
BLOBHEADER blobheader;
|
vb@123
|
13 |
RSAPUBKEY rsapubkey;
|
vb@123
|
14 |
BYTE modulus[4096];
|
vb@123
|
15 |
};
|
vb@123
|
16 |
|
vb@123
|
17 |
static void ReverseMemCopy(
|
vb@123
|
18 |
_Out_ BYTE *pbDest,
|
vb@123
|
19 |
_In_ BYTE const *pbSource,
|
vb@123
|
20 |
_In_ DWORD cb
|
vb@123
|
21 |
)
|
vb@123
|
22 |
{
|
vb@123
|
23 |
for (DWORD i = 0; i < cb; i++) {
|
vb@123
|
24 |
pbDest[cb - 1 - i] = pbSource[i];
|
vb@123
|
25 |
}
|
vb@123
|
26 |
}
|
vb@123
|
27 |
|
vb@123
|
28 |
static NTSTATUS ImportRsaPublicKey(
|
vb@123
|
29 |
_In_ BCRYPT_ALG_HANDLE hAlg, // CNG provider
|
vb@123
|
30 |
_In_ PUBLIC_KEY_VALUES *pKey, // Pointer to the RSAPUBKEY blob.
|
vb@123
|
31 |
_In_ BCRYPT_KEY_HANDLE *phKey // Receives a handle the imported public key.
|
vb@123
|
32 |
)
|
vb@123
|
33 |
{
|
vb@123
|
34 |
NTSTATUS hr = 0;
|
vb@123
|
35 |
|
vb@123
|
36 |
BYTE *pbPublicKey = NULL;
|
vb@123
|
37 |
DWORD cbKey = 0;
|
vb@123
|
38 |
|
vb@123
|
39 |
// Layout of the RSA public key blob:
|
vb@123
|
40 |
|
vb@123
|
41 |
// +----------------------------------------------------------------+
|
vb@123
|
42 |
// | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) |
|
vb@123
|
43 |
// +----------------------------------------------------------------+
|
vb@123
|
44 |
//
|
vb@123
|
45 |
// sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus
|
vb@123
|
46 |
// <--------------------------><------------><---------------------->
|
vb@123
|
47 |
//
|
vb@123
|
48 |
// BE = Big Endian Format
|
vb@123
|
49 |
|
vb@123
|
50 |
DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
|
vb@123
|
51 |
DWORD dwExp = pKey->rsapubkey.pubexp;
|
vb@123
|
52 |
DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
|
vb@123
|
53 |
(dwExp & 0x00FF0000) ? 3 :
|
vb@123
|
54 |
(dwExp & 0x0000FF00) ? 2 : 1;
|
vb@123
|
55 |
|
vb@123
|
56 |
BCRYPT_RSAKEY_BLOB *pRsaBlob;
|
vb@123
|
57 |
PBYTE pbCurrent;
|
vb@123
|
58 |
|
vb@123
|
59 |
if (!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
|
vb@123
|
60 |
goto cleanup;
|
vb@123
|
61 |
}
|
vb@123
|
62 |
|
vb@123
|
63 |
cbKey += cbExp;
|
vb@123
|
64 |
|
vb@123
|
65 |
pbPublicKey = (PBYTE) CoTaskMemAlloc(cbKey);
|
vb@123
|
66 |
if (pbPublicKey == NULL) {
|
vb@123
|
67 |
hr = E_OUTOFMEMORY;
|
vb@123
|
68 |
goto cleanup;
|
vb@123
|
69 |
}
|
vb@123
|
70 |
|
vb@123
|
71 |
ZeroMemory(pbPublicKey, cbKey);
|
vb@123
|
72 |
pRsaBlob = (BCRYPT_RSAKEY_BLOB *) (pbPublicKey);
|
vb@123
|
73 |
|
vb@123
|
74 |
//
|
vb@123
|
75 |
// Make the Public Key Blob Header
|
vb@123
|
76 |
//
|
vb@123
|
77 |
|
vb@123
|
78 |
pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
|
vb@123
|
79 |
pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
|
vb@123
|
80 |
pRsaBlob->cbPublicExp = cbExp;
|
vb@123
|
81 |
pRsaBlob->cbModulus = cbModulus;
|
vb@123
|
82 |
pRsaBlob->cbPrime1 = 0;
|
vb@123
|
83 |
pRsaBlob->cbPrime2 = 0;
|
vb@123
|
84 |
|
vb@123
|
85 |
pbCurrent = (PBYTE) (pRsaBlob + 1);
|
vb@123
|
86 |
|
vb@123
|
87 |
//
|
vb@123
|
88 |
// Copy pubExp Big Endian
|
vb@123
|
89 |
//
|
vb@123
|
90 |
|
vb@123
|
91 |
ReverseMemCopy(pbCurrent, (PBYTE) &dwExp, cbExp);
|
vb@123
|
92 |
pbCurrent += cbExp;
|
vb@123
|
93 |
|
vb@123
|
94 |
//
|
vb@123
|
95 |
// Copy Modulus Big Endian
|
vb@123
|
96 |
//
|
vb@123
|
97 |
|
vb@123
|
98 |
ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
|
vb@123
|
99 |
|
vb@123
|
100 |
//
|
vb@123
|
101 |
// Import the public key
|
vb@123
|
102 |
//
|
vb@123
|
103 |
|
vb@123
|
104 |
hr = BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR) pbPublicKey, cbKey, 0);
|
vb@123
|
105 |
|
vb@123
|
106 |
cleanup:
|
vb@123
|
107 |
CoTaskMemFree(pbPublicKey);
|
vb@123
|
108 |
return hr;
|
vb@123
|
109 |
}
|
vb@123
|
110 |
|
vb@96
|
111 |
namespace pEp {
|
vb@96
|
112 |
|
vb@96
|
113 |
const LPCTSTR GateKeeper::plugin_reg_path = _T("Software\\Microsoft\\Office\\Outlook\\Addins\\pEp");
|
vb@96
|
114 |
const LPCTSTR GateKeeper::plugin_reg_value_name = _T("LoadBehavior");
|
vb@96
|
115 |
const LPCTSTR GateKeeper::updater_reg_path = _T("Software\\pEp\\Updater";)
|
vb@96
|
116 |
|
vb@96
|
117 |
const time_t GateKeeper::cycle = 7200; // 7200 sec is 2 h
|
vb@96
|
118 |
const DWORD GateKeeper::waiting = 10000; // 10000 ms is 10 sec
|
vb@88
|
119 |
|
vb@117
|
120 |
GateKeeper::GateKeeper(CpEpCOMServerAdapterModule * self)
|
vb@116
|
121 |
: _self(self), now(time(NULL)), next(now + time_diff()), hkUpdater(NULL), internet(NULL), hAES(NULL), hRSA(NULL)
|
vb@96
|
122 |
{
|
vb@96
|
123 |
LONG lResult = RegOpenCurrentUser(KEY_READ, &cu);
|
vb@96
|
124 |
assert(lResult == ERROR_SUCCESS);
|
vb@96
|
125 |
if (lResult == ERROR_SUCCESS)
|
vb@96
|
126 |
cu_open = true;
|
vb@96
|
127 |
else
|
vb@96
|
128 |
cu_open = false;
|
vb@88
|
129 |
|
vb@96
|
130 |
if (cu_open) {
|
vb@96
|
131 |
LONG lResult = RegOpenKeyEx(cu, updater_reg_path, 0, KEY_READ, &hkUpdater);
|
vb@96
|
132 |
assert(lResult == ERROR_SUCCESS);
|
vb@96
|
133 |
if (lResult != ERROR_SUCCESS)
|
vb@96
|
134 |
return;
|
vb@96
|
135 |
}
|
vb@96
|
136 |
}
|
vb@96
|
137 |
|
vb@96
|
138 |
GateKeeper::~GateKeeper()
|
vb@96
|
139 |
{
|
vb@96
|
140 |
if (cu_open) {
|
vb@96
|
141 |
if (hkUpdater)
|
vb@96
|
142 |
RegCloseKey(hkUpdater);
|
vb@96
|
143 |
RegCloseKey(cu);
|
vb@96
|
144 |
}
|
vb@96
|
145 |
}
|
vb@88
|
146 |
|
vb@96
|
147 |
time_t GateKeeper::time_diff()
|
vb@96
|
148 |
{
|
vb@96
|
149 |
try {
|
vb@96
|
150 |
static random_device rd;
|
vb@96
|
151 |
static mt19937 gen(rd());
|
vb@88
|
152 |
|
vb@96
|
153 |
uniform_int_distribution<time_t> dist(0, cycle);
|
vb@96
|
154 |
|
vb@96
|
155 |
return dist(gen);
|
vb@96
|
156 |
}
|
vb@96
|
157 |
catch (exception&) {
|
vb@96
|
158 |
assert(0);
|
vb@96
|
159 |
return 0;
|
vb@96
|
160 |
}
|
vb@88
|
161 |
}
|
vb@96
|
162 |
|
vb@96
|
163 |
void GateKeeper::keep()
|
vb@96
|
164 |
{
|
vb@96
|
165 |
if (!cu_open)
|
vb@96
|
166 |
return;
|
vb@96
|
167 |
|
vb@96
|
168 |
while (1) {
|
vb@96
|
169 |
keep_plugin();
|
vb@96
|
170 |
|
vb@96
|
171 |
now = time(NULL);
|
vb@96
|
172 |
assert(now != -1);
|
vb@96
|
173 |
|
vb@96
|
174 |
if (now > next) {
|
vb@96
|
175 |
next = now + GateKeeper::cycle;
|
vb@96
|
176 |
keep_updated();
|
vb@96
|
177 |
}
|
vb@96
|
178 |
|
vb@96
|
179 |
Sleep(waiting);
|
vb@96
|
180 |
}
|
vb@88
|
181 |
}
|
vb@96
|
182 |
|
vb@96
|
183 |
void GateKeeper::keep_plugin()
|
vb@96
|
184 |
{
|
vb@110
|
185 |
while (!_self->m_bComInitialized)
|
vb@110
|
186 |
Sleep(1);
|
vb@110
|
187 |
|
vb@123
|
188 |
//MessageBox(NULL, _T("test"), _T("keep_plugin"), MB_ICONINFORMATION | MB_TOPMOST);
|
vb@110
|
189 |
|
vb@96
|
190 |
DWORD value;
|
vb@96
|
191 |
DWORD size;
|
vb@88
|
192 |
|
vb@96
|
193 |
LONG lResult = RegGetValue(cu, plugin_reg_path, plugin_reg_value_name, RRF_RT_REG_DWORD, NULL, &value, &size);
|
vb@96
|
194 |
if (lResult != ERROR_SUCCESS)
|
vb@96
|
195 |
return;
|
vb@96
|
196 |
|
vb@96
|
197 |
if (value != 3) {
|
vb@96
|
198 |
lResult = RegSetValue(cu, plugin_reg_path, RRF_RT_REG_DWORD, plugin_reg_value_name, 3);
|
vb@96
|
199 |
assert(lResult == ERROR_SUCCESS);
|
vb@96
|
200 |
}
|
vb@96
|
201 |
}
|
vb@96
|
202 |
|
vb@111
|
203 |
string GateKeeper::update_key()
|
vb@110
|
204 |
{
|
vb@110
|
205 |
static string key;
|
vb@110
|
206 |
|
vb@110
|
207 |
if (key.length() == 0) {
|
vb@110
|
208 |
HRSRC res = FindResource(_self->hModule(), MAKEINTRESOURCE(IRD_UPDATEKEY), RT_RCDATA);
|
vb@110
|
209 |
assert(res);
|
vb@110
|
210 |
if (!res)
|
vb@110
|
211 |
throw runtime_error("FindResource: IRD_UPDATEKEY");
|
vb@110
|
212 |
|
vb@110
|
213 |
HGLOBAL hRes = LoadResource(_self->hModule(), res);
|
vb@110
|
214 |
assert(hRes);
|
vb@110
|
215 |
if (!hRes)
|
vb@110
|
216 |
throw runtime_error("LoadResource: IRD_UPDATEKEY");
|
vb@110
|
217 |
|
vb@110
|
218 |
key = string((char *)LockResource(hRes), SizeofResource(_self->hModule(), res));
|
vb@110
|
219 |
UnlockResource(hRes);
|
vb@110
|
220 |
}
|
vb@110
|
221 |
|
vb@110
|
222 |
return key;
|
vb@110
|
223 |
}
|
vb@110
|
224 |
|
vb@116
|
225 |
BCRYPT_KEY_HANDLE GateKeeper::delivery_key()
|
vb@112
|
226 |
{
|
vb@112
|
227 |
aeskey_t key;
|
vb@112
|
228 |
|
vb@112
|
229 |
static random_device rd;
|
vb@112
|
230 |
static mt19937 gen(rd());
|
vb@112
|
231 |
|
vb@123
|
232 |
uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
|
vb@112
|
233 |
|
vb@123
|
234 |
for (int i = 0; i < 8; i++)
|
vb@123
|
235 |
key.dw_key[i] = (uint32_t) dist(gen);
|
vb@113
|
236 |
|
vb@116
|
237 |
BCRYPT_KEY_HANDLE hKey;
|
vb@116
|
238 |
NTSTATUS status = BCryptGenerateSymmetricKey(hAES, &hKey, NULL, 0, (PUCHAR) &key, (ULONG) sizeof(aeskey_t), 0);
|
vb@116
|
239 |
assert(status == 0);
|
vb@116
|
240 |
if (status)
|
vb@116
|
241 |
throw runtime_error("BCryptGenerateSymmetricKey");
|
vb@116
|
242 |
|
vb@116
|
243 |
return hKey;
|
vb@113
|
244 |
}
|
vb@113
|
245 |
|
vb@121
|
246 |
string GateKeeper::wrapped_delivery_key(BCRYPT_KEY_HANDLE hDeliveryKey)
|
vb@113
|
247 |
{
|
vb@113
|
248 |
string result;
|
vb@113
|
249 |
|
vb@116
|
250 |
BCRYPT_KEY_HANDLE hUpdateKey;
|
vb@117
|
251 |
string _update_key = update_key();
|
vb@113
|
252 |
|
vb@123
|
253 |
PCERT_PUBLIC_KEY_INFO uk;
|
vb@123
|
254 |
DWORD uk_size;
|
vb@123
|
255 |
BOOL bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
|
vb@123
|
256 |
(const BYTE *) _update_key.data(), _update_key.size(), CRYPT_DECODE_ALLOC_FLAG, NULL, &uk, &uk_size);
|
vb@123
|
257 |
if (!bResult)
|
vb@123
|
258 |
throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
|
vb@123
|
259 |
|
vb@123
|
260 |
PUBLIC_KEY_VALUES *_uk;
|
vb@123
|
261 |
DWORD _uk_size;
|
vb@123
|
262 |
bResult = CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
|
vb@123
|
263 |
uk->PublicKey.pbData, uk->PublicKey.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &_uk, &_uk_size);
|
vb@123
|
264 |
LocalFree(uk);
|
vb@123
|
265 |
if (!bResult)
|
vb@123
|
266 |
throw runtime_error("CryptDecodeObjectEx: X509_PUBLIC_KEY_INFO");
|
vb@123
|
267 |
|
vb@123
|
268 |
HRESULT hResult = ImportRsaPublicKey(hRSA, _uk, &hUpdateKey);
|
vb@123
|
269 |
LocalFree(_uk);
|
vb@123
|
270 |
if (hResult)
|
vb@123
|
271 |
throw runtime_error("ImportRsaPublicKey");
|
vb@117
|
272 |
|
vb@120
|
273 |
aeskey_t _delivery_key;
|
vb@120
|
274 |
ULONG copied;
|
vb@123
|
275 |
NTSTATUS status = BCryptExportKey(hDeliveryKey, NULL, BCRYPT_KEY_DATA_BLOB, (PUCHAR) &_delivery_key, sizeof(aeskey_t),
|
vb@123
|
276 |
&copied, 0);
|
vb@120
|
277 |
if (status)
|
vb@120
|
278 |
throw runtime_error("BCryptExportKey: delivery_key");
|
vb@120
|
279 |
|
vb@117
|
280 |
static random_device rd;
|
vb@117
|
281 |
static mt19937 gen(rd());
|
vb@123
|
282 |
uniform_int_distribution<int64_t> dist(0, UINT32_MAX);
|
vb@123
|
283 |
uint32_t r[64];
|
vb@123
|
284 |
for (int i = 0; i < 64; i++)
|
vb@123
|
285 |
r[i] = (uint32_t) dist(gen);
|
vb@117
|
286 |
|
vb@117
|
287 |
BCRYPT_OAEP_PADDING_INFO pi;
|
vb@119
|
288 |
memset(&pi, 0, sizeof(BCRYPT_OAEP_PADDING_INFO));
|
vb@117
|
289 |
pi.pszAlgId = BCRYPT_SHA256_ALGORITHM;
|
vb@117
|
290 |
pi.pbLabel = (PUCHAR) r;
|
vb@117
|
291 |
pi.cbLabel = sizeof(r);
|
vb@117
|
292 |
|
vb@117
|
293 |
ULONG result_size;
|
vb@118
|
294 |
PUCHAR _result = NULL;
|
vb@120
|
295 |
status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, NULL, 0, &result_size, BCRYPT_PAD_OAEP);
|
vb@119
|
296 |
if (status) {
|
vb@119
|
297 |
BCryptDestroyKey(hUpdateKey);
|
vb@117
|
298 |
throw runtime_error("BCryptEncrypt: calculating result size");
|
vb@119
|
299 |
}
|
vb@117
|
300 |
|
vb@117
|
301 |
_result = new UCHAR[result_size];
|
vb@120
|
302 |
status = BCryptEncrypt(hUpdateKey, (PUCHAR) &_delivery_key, sizeof(aeskey_t), &pi, NULL, 0, _result, result_size, &copied, BCRYPT_PAD_OAEP);
|
vb@119
|
303 |
if (status) {
|
vb@119
|
304 |
BCryptDestroyKey(hUpdateKey);
|
vb@119
|
305 |
delete[] _result;
|
vb@118
|
306 |
throw runtime_error("BCryptEncrypt: encrypting using update_key");
|
vb@119
|
307 |
}
|
vb@117
|
308 |
|
vb@120
|
309 |
BCryptDestroyKey(hUpdateKey);
|
vb@120
|
310 |
|
vb@117
|
311 |
stringstream s;
|
vb@118
|
312 |
s << hex << setw(2) << setfill('0');
|
vb@118
|
313 |
for (ULONG i = 0; i < copied; i++)
|
vb@118
|
314 |
s << (int) _result[i];
|
vb@117
|
315 |
delete[] _result;
|
vb@117
|
316 |
s >> result;
|
vb@117
|
317 |
|
vb@113
|
318 |
return result;
|
vb@112
|
319 |
}
|
vb@112
|
320 |
|
vb@96
|
321 |
GateKeeper::product_list& GateKeeper::registered_products()
|
vb@96
|
322 |
{
|
vb@96
|
323 |
static product_list products;
|
vb@88
|
324 |
|
vb@96
|
325 |
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
|
vb@96
|
326 |
static TCHAR value_name[16384];
|
vb@96
|
327 |
DWORD value_name_size;
|
vb@96
|
328 |
static TCHAR value[L_MAX_URL_LENGTH + 1];
|
vb@96
|
329 |
DWORD value_size;
|
vb@88
|
330 |
|
vb@96
|
331 |
products.empty();
|
vb@96
|
332 |
|
vb@96
|
333 |
LONG lResult = ERROR_SUCCESS;
|
vb@96
|
334 |
for (DWORD i = 0; lResult == ERROR_SUCCESS; i++) {
|
vb@96
|
335 |
value_size = L_MAX_URL_LENGTH + 1;
|
vb@96
|
336 |
lResult = RegEnumValue(hkUpdater, 0, value_name, &value_name_size, NULL, NULL, (LPBYTE) value, &value_size);
|
vb@96
|
337 |
if (lResult == ERROR_SUCCESS)
|
vb@96
|
338 |
products.push_back({ value_name, value });
|
vb@88
|
339 |
}
|
vb@88
|
340 |
|
vb@96
|
341 |
return products;
|
vb@88
|
342 |
}
|
vb@88
|
343 |
|
vb@97
|
344 |
void GateKeeper::update_product(product p, DWORD context)
|
vb@96
|
345 |
{
|
vb@123
|
346 |
string delivery = wrapped_delivery_key(delivery_key());
|
vb@123
|
347 |
|
vb@97
|
348 |
HINTERNET hUrl = InternetOpenUrl(internet, p.second.c_str(), NULL, 0,
|
vb@97
|
349 |
INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
|
vb@97
|
350 |
if (hUrl == NULL)
|
vb@97
|
351 |
return;
|
vb@94
|
352 |
|
vb@97
|
353 |
// update
|
vb@107
|
354 |
PCTSTR rgpszAcceptTypes[] = { _T("text/plain"), NULL };
|
vb@107
|
355 |
HINTERNET hRequest = HttpOpenRequest(hUrl, NULL, _T("challenge"), NULL, NULL, rgpszAcceptTypes, INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, context);
|
vb@107
|
356 |
|
vb@97
|
357 |
|
vb@97
|
358 |
InternetCloseHandle(hUrl);
|
vb@96
|
359 |
}
|
vb@88
|
360 |
|
vb@96
|
361 |
void GateKeeper::keep_updated()
|
vb@96
|
362 |
{
|
vb@107
|
363 |
return; // disabled for now
|
vb@107
|
364 |
|
vb@116
|
365 |
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAES, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
|
vb@116
|
366 |
assert(status == 0);
|
vb@116
|
367 |
if (status)
|
vb@116
|
368 |
goto closing;
|
vb@116
|
369 |
|
vb@116
|
370 |
status = BCryptOpenAlgorithmProvider(&hRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
|
vb@116
|
371 |
assert(status == 0);
|
vb@116
|
372 |
if (status)
|
vb@116
|
373 |
goto closing;
|
vb@116
|
374 |
|
vb@97
|
375 |
internet = InternetOpen(_T("pEp"), INTERNET_OPEN_TYPE_PROXY, NULL, NULL, 0);
|
vb@123
|
376 |
//if (!internet)
|
vb@123
|
377 |
// goto closing;
|
vb@97
|
378 |
|
vb@96
|
379 |
product_list& products = registered_products();
|
vb@97
|
380 |
DWORD context = 0;
|
vb@96
|
381 |
for (auto i = products.begin(); i != products.end(); i++) {
|
vb@97
|
382 |
update_product(*i, context++);
|
vb@96
|
383 |
}
|
vb@97
|
384 |
|
vb@116
|
385 |
closing:
|
vb@116
|
386 |
if (internet)
|
vb@116
|
387 |
InternetCloseHandle(internet);
|
vb@116
|
388 |
if (hAES)
|
vb@116
|
389 |
BCryptCloseAlgorithmProvider(hAES, 0);
|
vb@116
|
390 |
if (hRSA)
|
vb@116
|
391 |
BCryptCloseAlgorithmProvider(hRSA, 0);
|
vb@97
|
392 |
internet = NULL;
|
vb@116
|
393 |
hAES = NULL;
|
vb@116
|
394 |
hRSA = NULL;
|
vb@96
|
395 |
}
|
vb@88
|
396 |
|
vb@96
|
397 |
} // namespace pEp
|