1 #include "win-update-helpers.hpp"
2 #include "update-window.hpp"
3 #include "remote-text.hpp"
4 #include "qt-wrappers.hpp"
5 #include "win-update.hpp"
6 #include "obs-app.hpp"
7
8 #include <QMessageBox>
9
10 #include <string>
11 #include <mutex>
12
13 #include <util/windows/WinHandle.hpp>
14 #include <util/util.hpp>
15 #include <json11.hpp>
16 #include <blake2.h>
17
18 #include <time.h>
19 #include <strsafe.h>
20 #include <winhttp.h>
21 #include <shellapi.h>
22
23 #ifdef BROWSER_AVAILABLE
24 #include <browser-panel.hpp>
25 #endif
26
27 using namespace std;
28 using namespace json11;
29
30 struct QCef;
31 extern QCef *cef;
32
33 /* ------------------------------------------------------------------------ */
34
35 #ifndef WIN_MANIFEST_URL
36 #define WIN_MANIFEST_URL "https://obsproject.com/update_studio/manifest.json"
37 #endif
38
39 #ifndef WIN_WHATSNEW_URL
40 #define WIN_WHATSNEW_URL "https://obsproject.com/update_studio/whatsnew.json"
41 #endif
42
43 #ifndef WIN_UPDATER_URL
44 #define WIN_UPDATER_URL "https://obsproject.com/update_studio/updater.exe"
45 #endif
46
47 static __declspec(thread) HCRYPTPROV provider = 0;
48
49 #pragma pack(push, r1, 1)
50
51 typedef struct {
52 BLOBHEADER blobheader;
53 RSAPUBKEY rsapubkey;
54 } PUBLICKEYHEADER;
55
56 #pragma pack(pop, r1)
57
58 #define BLAKE2_HASH_LENGTH 20
59 #define BLAKE2_HASH_STR_LENGTH ((BLAKE2_HASH_LENGTH * 2) + 1)
60
61 #define TEST_BUILD
62
63 // Hard coded 4096 bit RSA public key for obsproject.com in PEM format
64 static const unsigned char obs_pub[] = {
65 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
66 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
67 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x49, 0x6a, 0x41, 0x4e, 0x42,
68 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41,
69 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x38, 0x41, 0x4d,
70 0x49, 0x49, 0x43, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x67, 0x45, 0x41, 0x6c,
71 0x33, 0x73, 0x76, 0x65, 0x72, 0x77, 0x39, 0x48, 0x51, 0x2b, 0x72, 0x59,
72 0x51, 0x4e, 0x6e, 0x39, 0x43, 0x61, 0x37, 0x0a, 0x39, 0x4c, 0x55, 0x36,
73 0x32, 0x6e, 0x47, 0x36, 0x4e, 0x6f, 0x7a, 0x45, 0x2f, 0x46, 0x73, 0x49,
74 0x56, 0x4e, 0x65, 0x72, 0x2b, 0x57, 0x2f, 0x68, 0x75, 0x65, 0x45, 0x38,
75 0x57, 0x51, 0x31, 0x6d, 0x72, 0x46, 0x50, 0x2b, 0x32, 0x79, 0x41, 0x2b,
76 0x69, 0x59, 0x52, 0x75, 0x74, 0x59, 0x50, 0x65, 0x45, 0x67, 0x70, 0x78,
77 0x74, 0x6f, 0x64, 0x48, 0x68, 0x67, 0x6b, 0x52, 0x34, 0x70, 0x45, 0x4b,
78 0x0a, 0x56, 0x6e, 0x72, 0x72, 0x31, 0x38, 0x71, 0x34, 0x73, 0x7a, 0x6c,
79 0x76, 0x38, 0x39, 0x51, 0x49, 0x37, 0x74, 0x38, 0x6c, 0x4d, 0x6f, 0x4c,
80 0x54, 0x6c, 0x46, 0x2b, 0x74, 0x31, 0x49, 0x52, 0x30, 0x56, 0x34, 0x77,
81 0x4a, 0x56, 0x33, 0x34, 0x49, 0x33, 0x43, 0x2b, 0x33, 0x35, 0x39, 0x4b,
82 0x69, 0x78, 0x6e, 0x7a, 0x4c, 0x30, 0x42, 0x6c, 0x39, 0x61, 0x6a, 0x2f,
83 0x7a, 0x44, 0x63, 0x72, 0x58, 0x0a, 0x57, 0x6c, 0x35, 0x70, 0x48, 0x54,
84 0x69, 0x6f, 0x4a, 0x77, 0x59, 0x4f, 0x67, 0x4d, 0x69, 0x42, 0x47, 0x4c,
85 0x79, 0x50, 0x65, 0x69, 0x74, 0x4d, 0x46, 0x64, 0x6a, 0x6a, 0x54, 0x49,
86 0x70, 0x43, 0x4d, 0x2b, 0x6d, 0x78, 0x54, 0x57, 0x58, 0x43, 0x72, 0x5a,
87 0x39, 0x64, 0x50, 0x55, 0x4b, 0x76, 0x5a, 0x74, 0x67, 0x7a, 0x6a, 0x64,
88 0x2b, 0x49, 0x7a, 0x6c, 0x48, 0x69, 0x64, 0x48, 0x74, 0x4f, 0x0a, 0x4f,
89 0x52, 0x42, 0x4e, 0x35, 0x6d, 0x52, 0x73, 0x38, 0x4c, 0x4e, 0x4f, 0x35,
90 0x38, 0x6b, 0x37, 0x39, 0x72, 0x37, 0x37, 0x44, 0x63, 0x67, 0x51, 0x59,
91 0x50, 0x4e, 0x69, 0x69, 0x43, 0x74, 0x57, 0x67, 0x43, 0x2b, 0x59, 0x34,
92 0x4b, 0x37, 0x75, 0x53, 0x5a, 0x58, 0x33, 0x48, 0x76, 0x65, 0x6f, 0x6d,
93 0x32, 0x74, 0x48, 0x62, 0x56, 0x58, 0x79, 0x30, 0x4c, 0x2f, 0x43, 0x6c,
94 0x37, 0x66, 0x4d, 0x0a, 0x48, 0x4b, 0x71, 0x66, 0x63, 0x51, 0x47, 0x75,
95 0x79, 0x72, 0x76, 0x75, 0x64, 0x34, 0x32, 0x4f, 0x72, 0x57, 0x61, 0x72,
96 0x41, 0x73, 0x6e, 0x32, 0x70, 0x32, 0x45, 0x69, 0x36, 0x4b, 0x7a, 0x78,
97 0x62, 0x33, 0x47, 0x36, 0x45, 0x53, 0x43, 0x77, 0x31, 0x35, 0x6e, 0x48,
98 0x41, 0x67, 0x4c, 0x61, 0x6c, 0x38, 0x7a, 0x53, 0x71, 0x37, 0x2b, 0x72,
99 0x61, 0x45, 0x2f, 0x78, 0x6b, 0x4c, 0x70, 0x43, 0x0a, 0x62, 0x59, 0x67,
100 0x35, 0x67, 0x6d, 0x59, 0x36, 0x76, 0x62, 0x6d, 0x57, 0x6e, 0x71, 0x39,
101 0x64, 0x71, 0x57, 0x72, 0x55, 0x7a, 0x61, 0x71, 0x4f, 0x66, 0x72, 0x5a,
102 0x50, 0x67, 0x76, 0x67, 0x47, 0x30, 0x57, 0x76, 0x6b, 0x42, 0x53, 0x68,
103 0x66, 0x61, 0x45, 0x4f, 0x42, 0x61, 0x49, 0x55, 0x78, 0x41, 0x33, 0x51,
104 0x42, 0x67, 0x7a, 0x41, 0x5a, 0x68, 0x71, 0x65, 0x65, 0x64, 0x46, 0x39,
105 0x68, 0x0a, 0x61, 0x66, 0x4d, 0x47, 0x4d, 0x4d, 0x39, 0x71, 0x56, 0x62,
106 0x66, 0x77, 0x75, 0x75, 0x7a, 0x4a, 0x32, 0x75, 0x68, 0x2b, 0x49, 0x6e,
107 0x61, 0x47, 0x61, 0x65, 0x48, 0x32, 0x63, 0x30, 0x34, 0x6f, 0x56, 0x63,
108 0x44, 0x46, 0x66, 0x65, 0x4f, 0x61, 0x44, 0x75, 0x78, 0x52, 0x6a, 0x43,
109 0x43, 0x62, 0x71, 0x72, 0x35, 0x73, 0x4c, 0x53, 0x6f, 0x31, 0x43, 0x57,
110 0x6f, 0x6b, 0x79, 0x6e, 0x6a, 0x4e, 0x0a, 0x43, 0x42, 0x2b, 0x62, 0x32,
111 0x72, 0x51, 0x46, 0x37, 0x44, 0x50, 0x50, 0x62, 0x44, 0x34, 0x73, 0x2f,
112 0x6e, 0x54, 0x39, 0x4e, 0x73, 0x63, 0x6b, 0x2f, 0x4e, 0x46, 0x7a, 0x72,
113 0x42, 0x58, 0x52, 0x4f, 0x2b, 0x64, 0x71, 0x6b, 0x65, 0x42, 0x77, 0x44,
114 0x55, 0x43, 0x76, 0x37, 0x62, 0x5a, 0x67, 0x57, 0x37, 0x4f, 0x78, 0x75,
115 0x4f, 0x58, 0x30, 0x37, 0x4c, 0x54, 0x71, 0x66, 0x70, 0x35, 0x73, 0x0a,
116 0x4f, 0x65, 0x47, 0x67, 0x75, 0x62, 0x75, 0x62, 0x69, 0x77, 0x59, 0x33,
117 0x55, 0x64, 0x48, 0x59, 0x71, 0x2b, 0x4c, 0x39, 0x4a, 0x71, 0x49, 0x53,
118 0x47, 0x31, 0x74, 0x4d, 0x34, 0x48, 0x65, 0x4b, 0x6a, 0x61, 0x48, 0x6a,
119 0x75, 0x31, 0x4d, 0x44, 0x6a, 0x76, 0x48, 0x5a, 0x32, 0x44, 0x62, 0x6d,
120 0x4c, 0x77, 0x55, 0x78, 0x75, 0x59, 0x61, 0x36, 0x4a, 0x5a, 0x44, 0x4b,
121 0x57, 0x73, 0x37, 0x72, 0x0a, 0x49, 0x72, 0x64, 0x44, 0x77, 0x78, 0x33,
122 0x4a, 0x77, 0x61, 0x63, 0x46, 0x36, 0x36, 0x68, 0x33, 0x59, 0x55, 0x57,
123 0x36, 0x74, 0x7a, 0x55, 0x5a, 0x68, 0x7a, 0x74, 0x63, 0x6d, 0x51, 0x65,
124 0x70, 0x50, 0x2f, 0x75, 0x37, 0x42, 0x67, 0x47, 0x72, 0x6b, 0x4f, 0x50,
125 0x50, 0x70, 0x59, 0x41, 0x30, 0x4e, 0x45, 0x4a, 0x38, 0x30, 0x53, 0x65,
126 0x41, 0x78, 0x37, 0x68, 0x69, 0x4e, 0x34, 0x76, 0x61, 0x0a, 0x65, 0x45,
127 0x51, 0x4b, 0x6e, 0x52, 0x6e, 0x2b, 0x45, 0x70, 0x42, 0x4e, 0x36, 0x55,
128 0x42, 0x61, 0x35, 0x66, 0x37, 0x4c, 0x6f, 0x4b, 0x38, 0x43, 0x41, 0x77,
129 0x45, 0x41, 0x41, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
130 0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b,
131 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a};
132 static const unsigned int obs_pub_len = 800;
133
134 /* ------------------------------------------------------------------------ */
135
QuickWriteFile(const char * file,const void * data,size_t size)136 static bool QuickWriteFile(const char *file, const void *data, size_t size)
137 try {
138 BPtr<wchar_t> w_file;
139 if (os_utf8_to_wcs_ptr(file, 0, &w_file) == 0)
140 return false;
141
142 WinHandle handle = CreateFileW(w_file, GENERIC_WRITE, 0, nullptr,
143 CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH,
144 nullptr);
145
146 if (handle == INVALID_HANDLE_VALUE)
147 throw strprintf("Failed to open file '%s': %lu", file,
148 GetLastError());
149
150 DWORD written;
151 if (!WriteFile(handle, data, (DWORD)size, &written, nullptr))
152 throw strprintf("Failed to write file '%s': %lu", file,
153 GetLastError());
154
155 return true;
156
157 } catch (string &text) {
158 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
159 return false;
160 }
161
QuickReadFile(const char * file,string & data)162 static bool QuickReadFile(const char *file, string &data)
163 try {
164 BPtr<wchar_t> w_file;
165 if (os_utf8_to_wcs_ptr(file, 0, &w_file) == 0)
166 return false;
167
168 WinHandle handle = CreateFileW(w_file, GENERIC_READ, FILE_SHARE_READ,
169 nullptr, OPEN_EXISTING, 0, nullptr);
170
171 if (handle == INVALID_HANDLE_VALUE)
172 throw strprintf("Failed to open file '%s': %lu", file,
173 GetLastError());
174
175 DWORD size = GetFileSize(handle, nullptr);
176 data.resize(size);
177
178 DWORD read;
179 if (!ReadFile(handle, &data[0], size, &read, nullptr))
180 throw strprintf("Failed to write file '%s': %lu", file,
181 GetLastError());
182
183 return true;
184
185 } catch (string &text) {
186 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
187 return false;
188 }
189
HashToString(const uint8_t * in,char * out)190 static void HashToString(const uint8_t *in, char *out)
191 {
192 const char alphabet[] = "0123456789abcdef";
193
194 for (int i = 0; i != BLAKE2_HASH_LENGTH; ++i) {
195 out[2 * i] = alphabet[in[i] / 16];
196 out[2 * i + 1] = alphabet[in[i] % 16];
197 }
198
199 out[BLAKE2_HASH_LENGTH * 2] = 0;
200 }
201
CalculateFileHash(const char * path,uint8_t * hash)202 static bool CalculateFileHash(const char *path, uint8_t *hash)
203 try {
204 blake2b_state blake2;
205 if (blake2b_init(&blake2, BLAKE2_HASH_LENGTH) != 0)
206 return false;
207
208 BPtr<wchar_t> w_path;
209 if (os_utf8_to_wcs_ptr(path, 0, &w_path) == 0)
210 return false;
211
212 WinHandle handle = CreateFileW(w_path, GENERIC_READ, FILE_SHARE_READ,
213 nullptr, OPEN_EXISTING, 0, nullptr);
214 if (handle == INVALID_HANDLE_VALUE)
215 throw strprintf("Failed to open file '%s': %lu", path,
216 GetLastError());
217
218 vector<BYTE> buf;
219 buf.resize(65536);
220
221 for (;;) {
222 DWORD read = 0;
223 if (!ReadFile(handle, buf.data(), (DWORD)buf.size(), &read,
224 nullptr))
225 throw strprintf("Failed to read file '%s': %lu", path,
226 GetLastError());
227
228 if (!read)
229 break;
230
231 if (blake2b_update(&blake2, buf.data(), read) != 0)
232 return false;
233 }
234
235 if (blake2b_final(&blake2, hash, BLAKE2_HASH_LENGTH) != 0)
236 return false;
237
238 return true;
239
240 } catch (string &text) {
241 blog(LOG_DEBUG, "%s: %s", __FUNCTION__, text.c_str());
242 return false;
243 }
244
245 /* ------------------------------------------------------------------------ */
246
VerifyDigitalSignature(uint8_t * buf,size_t len,uint8_t * sig,size_t sigLen)247 static bool VerifyDigitalSignature(uint8_t *buf, size_t len, uint8_t *sig,
248 size_t sigLen)
249 {
250 /* ASN of PEM public key */
251 BYTE binaryKey[1024];
252 DWORD binaryKeyLen = sizeof(binaryKey);
253
254 /* Windows X509 public key info from ASN */
255 LocalPtr<CERT_PUBLIC_KEY_INFO> publicPBLOB;
256 DWORD iPBLOBSize;
257
258 /* RSA BLOB info from X509 public key */
259 LocalPtr<PUBLICKEYHEADER> rsaPublicBLOB;
260 DWORD rsaPublicBLOBSize;
261
262 /* Handle to public key */
263 CryptKey keyOut;
264
265 /* Handle to hash context */
266 CryptHash hash;
267
268 /* Signature in little-endian format */
269 vector<BYTE> reversedSig;
270
271 if (!CryptStringToBinaryA((LPCSTR)obs_pub, obs_pub_len,
272 CRYPT_STRING_BASE64HEADER, binaryKey,
273 &binaryKeyLen, nullptr, nullptr))
274 return false;
275
276 if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
277 binaryKey, binaryKeyLen,
278 CRYPT_DECODE_ALLOC_FLAG, nullptr, &publicPBLOB,
279 &iPBLOBSize))
280 return false;
281
282 if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
283 publicPBLOB->PublicKey.pbData,
284 publicPBLOB->PublicKey.cbData,
285 CRYPT_DECODE_ALLOC_FLAG, nullptr,
286 &rsaPublicBLOB, &rsaPublicBLOBSize))
287 return false;
288
289 if (!CryptImportKey(provider, (const BYTE *)rsaPublicBLOB.get(),
290 rsaPublicBLOBSize, 0, 0, &keyOut))
291 return false;
292
293 if (!CryptCreateHash(provider, CALG_SHA_512, 0, 0, &hash))
294 return false;
295
296 if (!CryptHashData(hash, buf, (DWORD)len, 0))
297 return false;
298
299 /* Windows requires signature in little-endian. Every other crypto
300 * provider is big-endian of course. */
301 reversedSig.resize(sigLen);
302 for (size_t i = 0; i < sigLen; i++)
303 reversedSig[i] = sig[sigLen - i - 1];
304
305 if (!CryptVerifySignature(hash, reversedSig.data(), (DWORD)sigLen,
306 keyOut, nullptr, 0))
307 return false;
308
309 return true;
310 }
311
HexToByteArray(const char * hexStr,size_t hexLen,vector<uint8_t> & out)312 static inline void HexToByteArray(const char *hexStr, size_t hexLen,
313 vector<uint8_t> &out)
314 {
315 char ptr[3];
316
317 ptr[2] = 0;
318
319 for (size_t i = 0; i < hexLen; i += 2) {
320 ptr[0] = hexStr[i];
321 ptr[1] = hexStr[i + 1];
322 out.push_back((uint8_t)strtoul(ptr, nullptr, 16));
323 }
324 }
325
CheckDataSignature(const string & data,const char * name,const char * hexSig,size_t sigLen)326 static bool CheckDataSignature(const string &data, const char *name,
327 const char *hexSig, size_t sigLen)
328 try {
329 if (sigLen == 0 || sigLen > 0xFFFF || (sigLen & 1) != 0)
330 throw strprintf("Missing or invalid signature for %s", name);
331
332 /* Convert TCHAR signature to byte array */
333 vector<uint8_t> signature;
334 signature.reserve(sigLen);
335 HexToByteArray(hexSig, sigLen, signature);
336
337 if (!VerifyDigitalSignature((uint8_t *)data.data(), data.size(),
338 signature.data(), signature.size()))
339 throw strprintf("Signature check failed for %s", name);
340
341 return true;
342
343 } catch (string &text) {
344 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
345 return false;
346 }
347
348 /* ------------------------------------------------------------------------ */
349
FetchUpdaterModule(const char * url)350 static bool FetchUpdaterModule(const char *url)
351 try {
352 long responseCode;
353 uint8_t updateFileHash[BLAKE2_HASH_LENGTH];
354 vector<string> extraHeaders;
355
356 BPtr<char> updateFilePath =
357 GetConfigPathPtr("obs-studio\\updates\\updater.exe");
358
359 if (CalculateFileHash(updateFilePath, updateFileHash)) {
360 char hashString[BLAKE2_HASH_STR_LENGTH];
361 HashToString(updateFileHash, hashString);
362
363 string header = "If-None-Match: ";
364 header += hashString;
365 extraHeaders.push_back(move(header));
366 }
367
368 string signature;
369 string error;
370 string data;
371
372 bool success = GetRemoteFile(url, data, error, &responseCode, nullptr,
373 "", nullptr, extraHeaders, &signature);
374
375 if (!success || (responseCode != 200 && responseCode != 304)) {
376 if (responseCode == 404)
377 return false;
378
379 throw strprintf("Could not fetch '%s': %s", url, error.c_str());
380 }
381
382 /* A new file must be digitally signed */
383 if (responseCode == 200) {
384 bool valid = CheckDataSignature(data, url, signature.data(),
385 signature.size());
386 if (!valid)
387 throw string("Invalid updater module signature");
388
389 if (!QuickWriteFile(updateFilePath, data.data(), data.size()))
390 return false;
391 }
392
393 return true;
394
395 } catch (string &text) {
396 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
397 return false;
398 }
399
400 /* ------------------------------------------------------------------------ */
401
ParseUpdateManifest(const char * manifest,bool * updatesAvailable,string & notes_str,int & updateVer)402 static bool ParseUpdateManifest(const char *manifest, bool *updatesAvailable,
403 string ¬es_str, int &updateVer)
404 try {
405
406 string error;
407 Json root = Json::parse(manifest, error);
408 if (!error.empty())
409 throw strprintf("Failed reading json string: %s",
410 error.c_str());
411
412 if (!root.is_object())
413 throw string("Root of manifest is not an object");
414
415 int major = root["version_major"].int_value();
416 int minor = root["version_minor"].int_value();
417 int patch = root["version_patch"].int_value();
418
419 if (major == 0)
420 throw strprintf("Invalid version number: %d.%d.%d", major,
421 minor, patch);
422
423 const Json ¬es = root["notes"];
424 if (!notes.is_string())
425 throw string("'notes' value invalid");
426
427 notes_str = notes.string_value();
428
429 const Json &packages = root["packages"];
430 if (!packages.is_array())
431 throw string("'packages' value invalid");
432
433 int cur_ver = LIBOBS_API_VER;
434 int new_ver = MAKE_SEMANTIC_VERSION(major, minor, patch);
435
436 updateVer = new_ver;
437 *updatesAvailable = new_ver > cur_ver;
438
439 return true;
440
441 } catch (string &text) {
442 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
443 return false;
444 }
445
446 /* ------------------------------------------------------------------------ */
447
GenerateGUID(string & guid)448 void GenerateGUID(string &guid)
449 {
450 BYTE junk[20];
451
452 if (!CryptGenRandom(provider, sizeof(junk), junk))
453 return;
454
455 guid.resize(41);
456 HashToString(junk, &guid[0]);
457 }
458
GetProgramGUID()459 string GetProgramGUID()
460 {
461 static mutex m;
462 lock_guard<mutex> lock(m);
463
464 /* NOTE: this is an arbitrary random number that we use to count the
465 * number of unique OBS installations and is not associated with any
466 * kind of identifiable information */
467 const char *pguid =
468 config_get_string(GetGlobalConfig(), "General", "InstallGUID");
469 string guid;
470 if (pguid)
471 guid = pguid;
472
473 if (guid.empty()) {
474 GenerateGUID(guid);
475
476 if (!guid.empty())
477 config_set_string(GetGlobalConfig(), "General",
478 "InstallGUID", guid.c_str());
479 }
480
481 return guid;
482 }
483
infoMsg(const QString & title,const QString & text)484 void AutoUpdateThread::infoMsg(const QString &title, const QString &text)
485 {
486 OBSMessageBox::information(App()->GetMainWindow(), title, text);
487 }
488
info(const QString & title,const QString & text)489 void AutoUpdateThread::info(const QString &title, const QString &text)
490 {
491 QMetaObject::invokeMethod(this, "infoMsg", Qt::BlockingQueuedConnection,
492 Q_ARG(QString, title), Q_ARG(QString, text));
493 }
494
queryUpdateSlot(bool localManualUpdate,const QString & text)495 int AutoUpdateThread::queryUpdateSlot(bool localManualUpdate,
496 const QString &text)
497 {
498 OBSUpdate updateDlg(App()->GetMainWindow(), localManualUpdate, text);
499 return updateDlg.exec();
500 }
501
queryUpdate(bool localManualUpdate,const char * text_utf8)502 int AutoUpdateThread::queryUpdate(bool localManualUpdate, const char *text_utf8)
503 {
504 int ret = OBSUpdate::No;
505 QString text = text_utf8;
506 QMetaObject::invokeMethod(this, "queryUpdateSlot",
507 Qt::BlockingQueuedConnection,
508 Q_RETURN_ARG(int, ret),
509 Q_ARG(bool, localManualUpdate),
510 Q_ARG(QString, text));
511 return ret;
512 }
513
run()514 void AutoUpdateThread::run()
515 try {
516 long responseCode;
517 vector<string> extraHeaders;
518 string text;
519 string error;
520 string signature;
521 CryptProvider localProvider;
522 BYTE manifestHash[BLAKE2_HASH_LENGTH];
523 bool updatesAvailable = false;
524 bool success;
525
526 struct FinishedTrigger {
527 inline ~FinishedTrigger()
528 {
529 QMetaObject::invokeMethod(App()->GetMainWindow(),
530 "updateCheckFinished");
531 }
532 } finishedTrigger;
533
534 BPtr<char> manifestPath =
535 GetConfigPathPtr("obs-studio\\updates\\manifest.json");
536
537 /* ----------------------------------- *
538 * create signature provider */
539
540 if (!CryptAcquireContext(&localProvider, nullptr, MS_ENH_RSA_AES_PROV,
541 PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
542 throw strprintf("CryptAcquireContext failed: %lu",
543 GetLastError());
544
545 provider = localProvider;
546
547 /* ----------------------------------- *
548 * avoid downloading manifest again */
549
550 if (CalculateFileHash(manifestPath, manifestHash)) {
551 char hashString[BLAKE2_HASH_STR_LENGTH];
552 HashToString(manifestHash, hashString);
553
554 string header = "If-None-Match: ";
555 header += hashString;
556 extraHeaders.push_back(move(header));
557 }
558
559 /* ----------------------------------- *
560 * get current install GUID */
561
562 string guid = GetProgramGUID();
563 if (!guid.empty()) {
564 string header = "X-OBS2-GUID: ";
565 header += guid;
566 extraHeaders.push_back(move(header));
567 }
568
569 /* ----------------------------------- *
570 * get manifest from server */
571
572 success = GetRemoteFile(WIN_MANIFEST_URL, text, error, &responseCode,
573 nullptr, "", nullptr, extraHeaders, &signature);
574
575 if (!success || (responseCode != 200 && responseCode != 304)) {
576 if (responseCode == 404)
577 return;
578
579 throw strprintf("Failed to fetch manifest file: %s",
580 error.c_str());
581 }
582
583 /* ----------------------------------- *
584 * verify file signature */
585
586 /* a new file must be digitally signed */
587 if (responseCode == 200) {
588 success = CheckDataSignature(text, "manifest", signature.data(),
589 signature.size());
590 if (!success)
591 throw string("Invalid manifest signature");
592 }
593
594 /* ----------------------------------- *
595 * write or load manifest */
596
597 if (responseCode == 200) {
598 if (!QuickWriteFile(manifestPath, text.data(), text.size()))
599 throw strprintf("Could not write file '%s'",
600 manifestPath.Get());
601 } else {
602 if (!QuickReadFile(manifestPath, text))
603 throw strprintf("Could not read file '%s'",
604 manifestPath.Get());
605 }
606
607 /* ----------------------------------- *
608 * check manifest for update */
609
610 string notes;
611 int updateVer = 0;
612
613 success = ParseUpdateManifest(text.c_str(), &updatesAvailable, notes,
614 updateVer);
615 if (!success)
616 throw string("Failed to parse manifest");
617
618 if (!updatesAvailable) {
619 if (manualUpdate)
620 info(QTStr("Updater.NoUpdatesAvailable.Title"),
621 QTStr("Updater.NoUpdatesAvailable.Text"));
622 return;
623 }
624
625 /* ----------------------------------- *
626 * skip this version if set to skip */
627
628 int skipUpdateVer = config_get_int(GetGlobalConfig(), "General",
629 "SkipUpdateVersion");
630 if (!manualUpdate && updateVer == skipUpdateVer)
631 return;
632
633 /* ----------------------------------- *
634 * fetch updater module */
635
636 if (!FetchUpdaterModule(WIN_UPDATER_URL))
637 return;
638
639 /* ----------------------------------- *
640 * query user for update */
641
642 int queryResult = queryUpdate(manualUpdate, notes.c_str());
643
644 if (queryResult == OBSUpdate::No) {
645 if (!manualUpdate) {
646 long long t = (long long)time(nullptr);
647 config_set_int(GetGlobalConfig(), "General",
648 "LastUpdateCheck", t);
649 }
650 return;
651
652 } else if (queryResult == OBSUpdate::Skip) {
653 config_set_int(GetGlobalConfig(), "General",
654 "SkipUpdateVersion", updateVer);
655 return;
656 }
657
658 /* ----------------------------------- *
659 * get working dir */
660
661 wchar_t cwd[MAX_PATH];
662 GetModuleFileNameW(nullptr, cwd, _countof(cwd) - 1);
663 wchar_t *p = wcsrchr(cwd, '\\');
664 if (p)
665 *p = 0;
666
667 /* ----------------------------------- *
668 * execute updater */
669
670 BPtr<char> updateFilePath =
671 GetConfigPathPtr("obs-studio\\updates\\updater.exe");
672 BPtr<wchar_t> wUpdateFilePath;
673
674 size_t size = os_utf8_to_wcs_ptr(updateFilePath, 0, &wUpdateFilePath);
675 if (!size)
676 throw string("Could not convert updateFilePath to wide");
677
678 /* note, can't use CreateProcess to launch as admin. */
679 SHELLEXECUTEINFO execInfo = {};
680
681 execInfo.cbSize = sizeof(execInfo);
682 execInfo.lpFile = wUpdateFilePath;
683 #ifndef UPDATE_CHANNEL
684 #define UPDATE_ARG_SUFFIX L""
685 #else
686 #define UPDATE_ARG_SUFFIX UPDATE_CHANNEL
687 #endif
688 if (App()->IsPortableMode())
689 execInfo.lpParameters = UPDATE_ARG_SUFFIX L" Portable";
690 else
691 execInfo.lpParameters = UPDATE_ARG_SUFFIX;
692
693 execInfo.lpDirectory = cwd;
694 execInfo.nShow = SW_SHOWNORMAL;
695
696 if (!ShellExecuteEx(&execInfo)) {
697 QString msg = QTStr("Updater.FailedToLaunch");
698 info(msg, msg);
699 throw strprintf("Can't launch updater '%s': %d",
700 updateFilePath.Get(), GetLastError());
701 }
702
703 /* force OBS to perform another update check immediately after updating
704 * in case of issues with the new version */
705 config_set_int(GetGlobalConfig(), "General", "LastUpdateCheck", 0);
706 config_set_int(GetGlobalConfig(), "General", "SkipUpdateVersion", 0);
707 config_set_string(GetGlobalConfig(), "General", "InstallGUID",
708 guid.c_str());
709
710 QMetaObject::invokeMethod(App()->GetMainWindow(), "close");
711
712 } catch (string &text) {
713 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
714 }
715
716 /* ------------------------------------------------------------------------ */
717
run()718 void WhatsNewInfoThread::run()
719 try {
720 long responseCode;
721 vector<string> extraHeaders;
722 string text;
723 string error;
724 string signature;
725 CryptProvider localProvider;
726 BYTE whatsnewHash[BLAKE2_HASH_LENGTH];
727 bool success;
728
729 BPtr<char> whatsnewPath =
730 GetConfigPathPtr("obs-studio\\updates\\whatsnew.json");
731
732 /* ----------------------------------- *
733 * create signature provider */
734
735 if (!CryptAcquireContext(&localProvider, nullptr, MS_ENH_RSA_AES_PROV,
736 PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
737 throw strprintf("CryptAcquireContext failed: %lu",
738 GetLastError());
739
740 provider = localProvider;
741
742 /* ----------------------------------- *
743 * avoid downloading json again */
744
745 if (CalculateFileHash(whatsnewPath, whatsnewHash)) {
746 char hashString[BLAKE2_HASH_STR_LENGTH];
747 HashToString(whatsnewHash, hashString);
748
749 string header = "If-None-Match: ";
750 header += hashString;
751 extraHeaders.push_back(move(header));
752 }
753
754 /* ----------------------------------- *
755 * get current install GUID */
756
757 string guid = GetProgramGUID();
758
759 if (!guid.empty()) {
760 string header = "X-OBS2-GUID: ";
761 header += guid;
762 extraHeaders.push_back(move(header));
763 }
764
765 /* ----------------------------------- *
766 * get json from server */
767
768 success = GetRemoteFile(WIN_WHATSNEW_URL, text, error, &responseCode,
769 nullptr, "", nullptr, extraHeaders, &signature);
770
771 if (!success || (responseCode != 200 && responseCode != 304)) {
772 if (responseCode == 404)
773 return;
774
775 throw strprintf("Failed to fetch whatsnew file: %s",
776 error.c_str());
777 }
778
779 /* ----------------------------------- *
780 * verify file signature */
781
782 if (responseCode == 200) {
783 success = CheckDataSignature(text, "whatsnew", signature.data(),
784 signature.size());
785 if (!success)
786 throw string("Invalid whatsnew signature");
787 }
788
789 /* ----------------------------------- *
790 * write or load json */
791
792 if (responseCode == 200) {
793 if (!QuickWriteFile(whatsnewPath, text.data(), text.size()))
794 throw strprintf("Could not write file '%s'",
795 whatsnewPath.Get());
796 } else {
797 if (!QuickReadFile(whatsnewPath, text))
798 throw strprintf("Could not read file '%s'",
799 whatsnewPath.Get());
800 }
801
802 /* ----------------------------------- *
803 * success */
804
805 emit Result(QString::fromUtf8(text.c_str()));
806
807 } catch (string &text) {
808 blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
809 }
810
811 /* ------------------------------------------------------------------------ */
812
run()813 void WhatsNewBrowserInitThread::run()
814 {
815 #ifdef BROWSER_AVAILABLE
816 cef->wait_for_browser_init();
817 #endif
818 emit Result(url);
819 }
820