The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

489 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. /* Note: there's a bit of light obfuscation in this code, just to make things
  18. a bit more annoying for crackers who try to reverse-engineer your binaries, but
  19. nothing particularly foolproof.
  20. */
  21. struct KeyFileUtils
  22. {
  23. static XmlElement createKeyFileContent (const String& appName,
  24. const String& userEmail,
  25. const String& userName,
  26. const String& machineNumbers,
  27. const String& machineNumbersAttributeName)
  28. {
  29. XmlElement xml ("key");
  30. xml.setAttribute ("user", userName);
  31. xml.setAttribute ("email", userEmail);
  32. xml.setAttribute (machineNumbersAttributeName, machineNumbers);
  33. xml.setAttribute ("app", appName);
  34. xml.setAttribute ("date", String::toHexString (Time::getCurrentTime().toMilliseconds()));
  35. return xml;
  36. }
  37. static String createKeyFileComment (const String& appName,
  38. const String& userEmail,
  39. const String& userName,
  40. const String& machineNumbers)
  41. {
  42. String comment;
  43. comment << "Keyfile for " << appName << newLine;
  44. if (userName.isNotEmpty())
  45. comment << "User: " << userName << newLine;
  46. comment << "Email: " << userEmail << newLine
  47. << "Machine numbers: " << machineNumbers << newLine
  48. << "Created: " << Time::getCurrentTime().toString (true, true);
  49. return comment;
  50. }
  51. //==============================================================================
  52. static String encryptXML (const XmlElement& xml, RSAKey privateKey)
  53. {
  54. MemoryOutputStream text;
  55. text << xml.createDocument (StringRef(), true);
  56. BigInteger val;
  57. val.loadFromMemoryBlock (text.getMemoryBlock());
  58. privateKey.applyToValue (val);
  59. return val.toString (16);
  60. }
  61. static String createKeyFile (String comment,
  62. const XmlElement& xml,
  63. RSAKey rsaPrivateKey)
  64. {
  65. String asHex ("#" + encryptXML (xml, rsaPrivateKey));
  66. StringArray lines;
  67. lines.add (comment);
  68. lines.add (String());
  69. const int charsPerLine = 70;
  70. while (asHex.length() > 0)
  71. {
  72. lines.add (asHex.substring (0, charsPerLine));
  73. asHex = asHex.substring (charsPerLine);
  74. }
  75. lines.add (String());
  76. return lines.joinIntoString ("\r\n");
  77. }
  78. //==============================================================================
  79. static XmlElement decryptXML (String hexData, RSAKey rsaPublicKey)
  80. {
  81. BigInteger val;
  82. val.parseString (hexData, 16);
  83. RSAKey key (rsaPublicKey);
  84. jassert (key.isValid());
  85. ScopedPointer<XmlElement> xml;
  86. if (! val.isZero())
  87. {
  88. key.applyToValue (val);
  89. const MemoryBlock mb (val.toMemoryBlock());
  90. if (CharPointer_UTF8::isValidString (static_cast<const char*> (mb.getData()), (int) mb.getSize()))
  91. xml = XmlDocument::parse (mb.toString());
  92. }
  93. return xml != nullptr ? *xml : XmlElement("key");
  94. }
  95. static XmlElement getXmlFromKeyFile (String keyFileText, RSAKey rsaPublicKey)
  96. {
  97. return decryptXML (keyFileText.fromLastOccurrenceOf ("#", false, false).trim(), rsaPublicKey);
  98. }
  99. static StringArray getMachineNumbers (XmlElement xml, StringRef attributeName)
  100. {
  101. StringArray numbers;
  102. numbers.addTokens (xml.getStringAttribute (attributeName), ",; ", StringRef());
  103. numbers.trim();
  104. numbers.removeEmptyStrings();
  105. return numbers;
  106. }
  107. static String getLicensee (const XmlElement& xml) { return xml.getStringAttribute ("user"); }
  108. static String getEmail (const XmlElement& xml) { return xml.getStringAttribute ("email"); }
  109. static String getAppID (const XmlElement& xml) { return xml.getStringAttribute ("app"); }
  110. struct KeyFileData
  111. {
  112. String licensee, email, appID;
  113. StringArray machineNumbers;
  114. bool keyFileExpires;
  115. Time expiryTime;
  116. };
  117. static KeyFileData getDataFromKeyFile (XmlElement xml)
  118. {
  119. KeyFileData data;
  120. data.licensee = getLicensee (xml);
  121. data.email = getEmail (xml);
  122. data.appID = getAppID (xml);
  123. if (xml.hasAttribute ("expiryTime") && xml.hasAttribute ("expiring_mach"))
  124. {
  125. data.keyFileExpires = true;
  126. data.machineNumbers.addArray (getMachineNumbers (xml, "expiring_mach"));
  127. data.expiryTime = Time (xml.getStringAttribute ("expiryTime").getHexValue64());
  128. }
  129. else
  130. {
  131. data.keyFileExpires = false;
  132. data.machineNumbers.addArray (getMachineNumbers (xml, "mach"));
  133. }
  134. return data;
  135. }
  136. };
  137. //==============================================================================
  138. const char* OnlineUnlockStatus::unlockedProp = "u";
  139. const char* OnlineUnlockStatus::expiryTimeProp = "t";
  140. static const char* stateTagName = "REG";
  141. static const char* userNameProp = "user";
  142. static const char* keyfileDataProp = "key";
  143. static var machineNumberAllowed (StringArray numbersFromKeyFile,
  144. StringArray localMachineNumbers)
  145. {
  146. var result;
  147. for (int i = 0; i < localMachineNumbers.size(); ++i)
  148. {
  149. String localNumber (localMachineNumbers[i].trim());
  150. if (localNumber.isNotEmpty())
  151. {
  152. for (int j = numbersFromKeyFile.size(); --j >= 0;)
  153. {
  154. var ok (localNumber.trim().equalsIgnoreCase (numbersFromKeyFile[j].trim()));
  155. result.swapWith (ok);
  156. if (result)
  157. break;
  158. }
  159. }
  160. }
  161. return result;
  162. }
  163. //==============================================================================
  164. OnlineUnlockStatus::OnlineUnlockStatus() : status (stateTagName)
  165. {
  166. }
  167. OnlineUnlockStatus::~OnlineUnlockStatus()
  168. {
  169. }
  170. void OnlineUnlockStatus::load()
  171. {
  172. MemoryBlock mb;
  173. mb.fromBase64Encoding (getState());
  174. if (mb.getSize() > 0)
  175. status = ValueTree::readFromGZIPData (mb.getData(), mb.getSize());
  176. else
  177. status = ValueTree (stateTagName);
  178. StringArray localMachineNums (getLocalMachineIDs());
  179. if (machineNumberAllowed (StringArray ("1234"), localMachineNums))
  180. status.removeProperty (unlockedProp, nullptr);
  181. KeyFileUtils::KeyFileData data;
  182. data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (status[keyfileDataProp], getPublicKey()));
  183. if (data.keyFileExpires)
  184. {
  185. if (! doesProductIDMatch (data.appID))
  186. status.removeProperty (expiryTimeProp, nullptr);
  187. if (! machineNumberAllowed (data.machineNumbers, localMachineNums))
  188. status.removeProperty (expiryTimeProp, nullptr);
  189. }
  190. else
  191. {
  192. if (! doesProductIDMatch (data.appID))
  193. status.removeProperty (unlockedProp, nullptr);
  194. if (! machineNumberAllowed (data.machineNumbers, localMachineNums))
  195. status.removeProperty (unlockedProp, nullptr);
  196. }
  197. }
  198. void OnlineUnlockStatus::save()
  199. {
  200. MemoryOutputStream mo;
  201. {
  202. GZIPCompressorOutputStream gzipStream (&mo, 9);
  203. status.writeToStream (gzipStream);
  204. }
  205. saveState (mo.getMemoryBlock().toBase64Encoding());
  206. }
  207. char OnlineUnlockStatus::MachineIDUtilities::getPlatformPrefix()
  208. {
  209. #if JUCE_MAC
  210. return 'M';
  211. #elif JUCE_WINDOWS
  212. return 'W';
  213. #elif JUCE_LINUX
  214. return 'L';
  215. #elif JUCE_IOS
  216. return 'I';
  217. #elif JUCE_ANDROID
  218. return 'A';
  219. #endif
  220. }
  221. String OnlineUnlockStatus::MachineIDUtilities::getEncodedIDString (const String& input)
  222. {
  223. const String platform (String::charToString (getPlatformPrefix()));
  224. return platform + MD5 ((input + "salt_1" + platform).toUTF8())
  225. .toHexString().substring (0, 9).toUpperCase();
  226. }
  227. bool OnlineUnlockStatus::MachineIDUtilities::addFileIDToList (StringArray& ids, const File& f)
  228. {
  229. if (uint64 num = f.getFileIdentifier())
  230. {
  231. ids.add (getEncodedIDString (String::toHexString ((int64) num)));
  232. return true;
  233. }
  234. return false;
  235. }
  236. void OnlineUnlockStatus::MachineIDUtilities::addMACAddressesToList (StringArray& ids)
  237. {
  238. Array<MACAddress> addresses;
  239. MACAddress::findAllAddresses (addresses);
  240. for (int i = 0; i < addresses.size(); ++i)
  241. ids.add (getEncodedIDString (addresses.getReference(i).toString()));
  242. }
  243. StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs()
  244. {
  245. StringArray ids;
  246. // First choice for an ID number is a filesystem ID for the user's home
  247. // folder or windows directory.
  248. #if JUCE_WINDOWS
  249. MachineIDUtilities::addFileIDToList (ids, File::getSpecialLocation (File::windowsSystemDirectory));
  250. #else
  251. MachineIDUtilities::addFileIDToList (ids, File ("~"));
  252. #endif
  253. // ..if that fails, use the MAC addresses..
  254. if (ids.size() == 0)
  255. MachineIDUtilities::addMACAddressesToList (ids);
  256. jassert (ids.size() > 0); // failed to create any IDs!
  257. return ids;
  258. }
  259. StringArray OnlineUnlockStatus::getLocalMachineIDs()
  260. {
  261. return MachineIDUtilities::getLocalMachineIDs();
  262. }
  263. void OnlineUnlockStatus::setUserEmail (const String& usernameOrEmail)
  264. {
  265. status.setProperty (userNameProp, usernameOrEmail, nullptr);
  266. }
  267. String OnlineUnlockStatus::getUserEmail() const
  268. {
  269. return status[userNameProp].toString();
  270. }
  271. bool OnlineUnlockStatus::applyKeyFile (String keyFileContent)
  272. {
  273. KeyFileUtils::KeyFileData data;
  274. data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey()));
  275. if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && doesProductIDMatch (data.appID))
  276. {
  277. setUserEmail (data.email);
  278. status.setProperty (keyfileDataProp, keyFileContent, nullptr);
  279. status.removeProperty (data.keyFileExpires ? expiryTimeProp : unlockedProp, nullptr);
  280. var actualResult (0), dummyResult (1.0);
  281. var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs()));
  282. actualResult.swapWith (v);
  283. v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs());
  284. dummyResult.swapWith (v);
  285. jassert (! dummyResult);
  286. if (data.keyFileExpires)
  287. {
  288. if ((! dummyResult) && actualResult)
  289. status.setProperty (expiryTimeProp, data.expiryTime.toMilliseconds(), nullptr);
  290. return getExpiryTime().toMilliseconds() > 0;
  291. }
  292. if ((! dummyResult) && actualResult)
  293. status.setProperty (unlockedProp, actualResult, nullptr);
  294. return isUnlocked();
  295. }
  296. return false;
  297. }
  298. static bool canConnectToWebsite (const URL& url)
  299. {
  300. ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String(), 2000, nullptr));
  301. return in != nullptr;
  302. }
  303. static bool areMajorWebsitesAvailable()
  304. {
  305. const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com",
  306. "https://google.com", "https://bing.com", "https://amazon.com", nullptr};
  307. for (const char** url = urlsToTry; *url != nullptr; ++url)
  308. if (canConnectToWebsite (URL (*url)))
  309. return true;
  310. return false;
  311. }
  312. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleXmlReply (XmlElement xml)
  313. {
  314. UnlockResult r;
  315. if (const XmlElement* keyNode = xml.getChildByName ("KEY"))
  316. {
  317. const String keyText (keyNode->getAllSubText().trim());
  318. r.succeeded = keyText.length() > 10 && applyKeyFile (keyText);
  319. }
  320. if (xml.hasTagName ("MESSAGE"))
  321. r.informativeMessage = xml.getStringAttribute ("message").trim();
  322. if (xml.hasTagName ("ERROR"))
  323. r.errorMessage = xml.getStringAttribute ("error").trim();
  324. if (xml.getStringAttribute ("url").isNotEmpty())
  325. r.urlToLaunch = xml.getStringAttribute ("url").trim();
  326. if (r.errorMessage.isEmpty() && r.informativeMessage.isEmpty() && r.urlToLaunch.isEmpty() && ! r.succeeded)
  327. r.errorMessage = TRANS ("Unexpected or corrupted reply from XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n"
  328. + TRANS("Please try again in a few minutes, and contact us for support if this message appears again.");
  329. return r;
  330. }
  331. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleFailedConnection()
  332. {
  333. UnlockResult r;
  334. r.errorMessage = TRANS("Couldn't connect to XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n";
  335. if (areMajorWebsitesAvailable())
  336. r.errorMessage << TRANS("Your internet connection seems to be OK, but our webserver "
  337. "didn't respond... This is most likely a temporary problem, so try "
  338. "again in a few minutes, but if it persists, please contact us for support!");
  339. else
  340. r.errorMessage << TRANS("No internet sites seem to be accessible from your computer.. Before trying again, "
  341. "please check that your network is working correctly, and make sure "
  342. "that any firewall/security software installed on your machine isn't "
  343. "blocking your web connection.");
  344. return r;
  345. }
  346. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::attemptWebserverUnlock (const String& email,
  347. const String& password)
  348. {
  349. // This method will block while it contacts the server, so you must run it on a background thread!
  350. jassert (! MessageManager::getInstance()->isThisTheMessageThread());
  351. String reply (readReplyFromWebserver (email, password));
  352. DBG ("Reply from server: " << reply);
  353. ScopedPointer<XmlElement> xml (XmlDocument::parse (reply));
  354. if (xml != nullptr)
  355. return handleXmlReply (*xml);
  356. return handleFailedConnection();
  357. }
  358. //==============================================================================
  359. String KeyGeneration::generateKeyFile (const String& appName,
  360. const String& userEmail,
  361. const String& userName,
  362. const String& machineNumbers,
  363. const RSAKey& privateKey)
  364. {
  365. XmlElement xml (KeyFileUtils::createKeyFileContent (appName, userEmail, userName, machineNumbers, "mach"));
  366. const String comment (KeyFileUtils::createKeyFileComment (appName, userEmail, userName, machineNumbers));
  367. return KeyFileUtils::createKeyFile (comment, xml, privateKey);
  368. }
  369. String KeyGeneration::generateExpiringKeyFile (const String& appName,
  370. const String& userEmail,
  371. const String& userName,
  372. const String& machineNumbers,
  373. const Time expiryTime,
  374. const RSAKey& privateKey)
  375. {
  376. XmlElement xml (KeyFileUtils::createKeyFileContent (appName, userEmail, userName, machineNumbers, "expiring_mach"));
  377. xml.setAttribute ("expiryTime", String::toHexString (expiryTime.toMilliseconds()));
  378. String comment (KeyFileUtils::createKeyFileComment (appName, userEmail, userName, machineNumbers));
  379. comment << newLine << "Expires: " << expiryTime.toString (true, true);
  380. return KeyFileUtils::createKeyFile (comment, xml, privateKey);
  381. }