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 &notes_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 &notes = 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