nix-update.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #include "nix-update.hpp"
  2. #include "crypto-helpers.hpp"
  3. #include "nix-update-helpers.hpp"
  4. #include "obs-app.hpp"
  5. #include "remote-text.hpp"
  6. #include "platform.hpp"
  7. #include <util/util.hpp>
  8. #include <blake2.h>
  9. #include <iostream>
  10. #include <fstream>
  11. #include <QRandomGenerator>
  12. #include <QByteArray>
  13. #include <QString>
  14. #include <browser-panel.hpp>
  15. struct QCef;
  16. extern QCef *cef;
  17. #ifndef MAC_WHATSNEW_URL
  18. #define MAC_WHATSNEW_URL "https://obsproject.com/update_studio/whatsnew.json"
  19. #endif
  20. #ifndef LINUX_WHATSNEW_URL
  21. #define LINUX_WHATSNEW_URL "https://obsproject.com/update_studio/whatsnew.json"
  22. #endif
  23. #ifdef __APPLE__
  24. #define WHATSNEW_URL MAC_WHATSNEW_URL
  25. #else
  26. #define WHATSNEW_URL LINUX_WHATSNEW_URL
  27. #endif
  28. #define HASH_READ_BUF_SIZE 65536
  29. #define BLAKE2_HASH_LENGTH 20
  30. /* ------------------------------------------------------------------------ */
  31. static bool QuickWriteFile(const char *file, std::string &data)
  32. try {
  33. std::ofstream fileStream(file, std::ios::binary);
  34. if (fileStream.fail())
  35. throw strprintf("Failed to open file '%s': %s", file,
  36. strerror(errno));
  37. fileStream.write(data.data(), data.size());
  38. if (fileStream.fail())
  39. throw strprintf("Failed to write file '%s': %s", file,
  40. strerror(errno));
  41. return true;
  42. } catch (std::string &text) {
  43. blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
  44. return false;
  45. }
  46. static bool QuickReadFile(const char *file, std::string &data)
  47. try {
  48. std::ifstream fileStream(file);
  49. if (!fileStream.is_open() || fileStream.fail())
  50. throw strprintf("Failed to open file '%s': %s", file,
  51. strerror(errno));
  52. fileStream.seekg(0, fileStream.end);
  53. size_t size = fileStream.tellg();
  54. fileStream.seekg(0);
  55. data.resize(size);
  56. fileStream.read(&data[0], size);
  57. if (fileStream.fail())
  58. throw strprintf("Failed to write file '%s': %s", file,
  59. strerror(errno));
  60. return true;
  61. } catch (std::string &text) {
  62. blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
  63. return false;
  64. }
  65. static bool CalculateFileHash(const char *path, uint8_t *hash)
  66. try {
  67. blake2b_state blake2;
  68. if (blake2b_init(&blake2, BLAKE2_HASH_LENGTH) != 0)
  69. return false;
  70. std::ifstream file(path, std::ios::binary);
  71. if (!file.is_open() || file.fail())
  72. return false;
  73. char buf[HASH_READ_BUF_SIZE];
  74. for (;;) {
  75. file.read(buf, HASH_READ_BUF_SIZE);
  76. size_t read = file.gcount();
  77. if (blake2b_update(&blake2, &buf, read) != 0)
  78. return false;
  79. if (file.eof())
  80. break;
  81. }
  82. if (blake2b_final(&blake2, hash, BLAKE2_HASH_LENGTH) != 0)
  83. return false;
  84. return true;
  85. } catch (std::string &text) {
  86. blog(LOG_DEBUG, "%s: %s", __FUNCTION__, text.c_str());
  87. return false;
  88. }
  89. /* ------------------------------------------------------------------------ */
  90. void GenerateGUID(std::string &guid)
  91. {
  92. const char alphabet[] = "0123456789abcdef";
  93. QRandomGenerator *rng = QRandomGenerator::system();
  94. guid.resize(40);
  95. for (size_t i = 0; i < 40; i++) {
  96. guid[i] = alphabet[rng->bounded(0, 16)];
  97. }
  98. }
  99. std::string GetProgramGUID()
  100. {
  101. static std::mutex m;
  102. std::lock_guard<std::mutex> lock(m);
  103. /* NOTE: this is an arbitrary random number that we use to count the
  104. * number of unique OBS installations and is not associated with any
  105. * kind of identifiable information */
  106. const char *pguid =
  107. config_get_string(GetGlobalConfig(), "General", "InstallGUID");
  108. std::string guid;
  109. if (pguid)
  110. guid = pguid;
  111. if (guid.empty()) {
  112. GenerateGUID(guid);
  113. if (!guid.empty())
  114. config_set_string(GetGlobalConfig(), "General",
  115. "InstallGUID", guid.c_str());
  116. }
  117. return guid;
  118. }
  119. /* ------------------------------------------------------------------------ */
  120. static void LoadPublicKey(std::string &pubkey)
  121. {
  122. std::string pemFilePath;
  123. if (!GetDataFilePath("OBSPublicRSAKey.pem", pemFilePath))
  124. throw std::string("Could not find OBS public key file!");
  125. if (!QuickReadFile(pemFilePath.c_str(), pubkey))
  126. throw std::string("Could not read OBS public key file!");
  127. }
  128. static bool CheckDataSignature(const char *name, const std::string &data,
  129. const std::string &hexSig)
  130. try {
  131. if (hexSig.empty() || hexSig.length() > 0xFFFF ||
  132. (hexSig.length() & 1) != 0)
  133. throw strprintf("Missing or invalid signature for %s: %s", name,
  134. hexSig.c_str());
  135. static std::string obsPubKey;
  136. if (obsPubKey.empty())
  137. LoadPublicKey(obsPubKey);
  138. // Convert hex string to bytes
  139. auto signature = QByteArray::fromHex(hexSig.data());
  140. if (!VerifySignature((uint8_t *)obsPubKey.data(), obsPubKey.size(),
  141. (uint8_t *)data.data(), data.size(),
  142. (uint8_t *)signature.data(), signature.size()))
  143. throw strprintf("Signature check failed for %s", name);
  144. return true;
  145. } catch (std::string &text) {
  146. blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
  147. return false;
  148. }
  149. /* ------------------------------------------------------------------------ */
  150. void WhatsNewInfoThread::run()
  151. try {
  152. long responseCode;
  153. std::vector<std::string> extraHeaders;
  154. std::string text;
  155. std::string error;
  156. std::string signature;
  157. uint8_t whatsnewHash[BLAKE2_HASH_LENGTH];
  158. bool success;
  159. BPtr<char> whatsnewPath =
  160. GetConfigPathPtr("obs-studio/updates/whatsnew.json");
  161. /* ----------------------------------- *
  162. * avoid downloading json again */
  163. if (CalculateFileHash(whatsnewPath, whatsnewHash)) {
  164. auto hash = QByteArray::fromRawData((const char *)whatsnewHash,
  165. BLAKE2_HASH_LENGTH);
  166. QString header = "If-None-Match: " + hash.toHex();
  167. extraHeaders.push_back(move(header.toStdString()));
  168. }
  169. /* ----------------------------------- *
  170. * get current install GUID */
  171. std::string guid = GetProgramGUID();
  172. if (!guid.empty()) {
  173. std::string header = "X-OBS2-GUID: " + guid;
  174. extraHeaders.push_back(move(header));
  175. }
  176. /* ----------------------------------- *
  177. * get json from server */
  178. success = GetRemoteFile(WHATSNEW_URL, text, error, &responseCode,
  179. nullptr, "", nullptr, extraHeaders, &signature);
  180. if (!success || (responseCode != 200 && responseCode != 304)) {
  181. if (responseCode == 404)
  182. return;
  183. throw strprintf("Failed to fetch whatsnew file: %s",
  184. error.c_str());
  185. }
  186. /* ----------------------------------- *
  187. * verify file signature */
  188. if (responseCode == 200) {
  189. success = CheckDataSignature("whatsnew", text, signature);
  190. if (!success)
  191. throw std::string("Invalid whatsnew signature");
  192. }
  193. /* ----------------------------------- *
  194. * write or load json */
  195. if (responseCode == 200) {
  196. if (!QuickWriteFile(whatsnewPath, text))
  197. throw strprintf("Could not write file '%s'",
  198. whatsnewPath.Get());
  199. } else {
  200. if (!QuickReadFile(whatsnewPath, text))
  201. throw strprintf("Could not read file '%s'",
  202. whatsnewPath.Get());
  203. }
  204. /* ----------------------------------- *
  205. * success */
  206. emit Result(QString::fromStdString(text));
  207. } catch (std::string &text) {
  208. blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
  209. }
  210. /* ------------------------------------------------------------------------ */
  211. void WhatsNewBrowserInitThread::run()
  212. {
  213. cef->wait_for_browser_init();
  214. emit Result(url);
  215. }