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.

412 lines
13KB

  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 String encryptXML (const XmlElement& xml, RSAKey privateKey)
  24. {
  25. MemoryOutputStream text;
  26. text << xml.createDocument (StringRef(), true);
  27. BigInteger val;
  28. val.loadFromMemoryBlock (text.getMemoryBlock());
  29. privateKey.applyToValue (val);
  30. return val.toString (16);
  31. }
  32. static String createKeyFile (String comment,
  33. const XmlElement& xml,
  34. RSAKey rsaPrivateKey)
  35. {
  36. String asHex ("#" + encryptXML (xml, rsaPrivateKey));
  37. StringArray lines;
  38. lines.add (comment);
  39. lines.add (String());
  40. const int charsPerLine = 70;
  41. while (asHex.length() > 0)
  42. {
  43. lines.add (asHex.substring (0, charsPerLine));
  44. asHex = asHex.substring (charsPerLine);
  45. }
  46. lines.add (String());
  47. return lines.joinIntoString ("\r\n");
  48. }
  49. //==============================================================================
  50. static XmlElement decryptXML (String hexData, RSAKey rsaPublicKey)
  51. {
  52. BigInteger val;
  53. val.parseString (hexData, 16);
  54. RSAKey key (rsaPublicKey);
  55. jassert (key.isValid());
  56. ScopedPointer<XmlElement> xml;
  57. if (! val.isZero())
  58. {
  59. key.applyToValue (val);
  60. const MemoryBlock mb (val.toMemoryBlock());
  61. if (CharPointer_UTF8::isValidString (static_cast<const char*> (mb.getData()), (int) mb.getSize()))
  62. xml = XmlDocument::parse (mb.toString());
  63. }
  64. return xml != nullptr ? *xml : XmlElement("key");
  65. }
  66. static XmlElement getXmlFromKeyFile (String keyFileText, RSAKey rsaPublicKey)
  67. {
  68. return decryptXML (keyFileText.fromLastOccurrenceOf ("#", false, false).trim(), rsaPublicKey);
  69. }
  70. static StringArray getMachineNumbers (XmlElement xml)
  71. {
  72. StringArray numbers;
  73. numbers.addTokens (xml.getStringAttribute ("mach"), ",; ", StringRef());
  74. numbers.trim();
  75. numbers.removeEmptyStrings();
  76. return numbers;
  77. }
  78. static String getLicensee (const XmlElement& xml) { return xml.getStringAttribute ("user"); }
  79. static String getEmail (const XmlElement& xml) { return xml.getStringAttribute ("email"); }
  80. static String getAppID (const XmlElement& xml) { return xml.getStringAttribute ("app"); }
  81. struct KeyFileData
  82. {
  83. String licensee, email, appID;
  84. StringArray machineNumbers;
  85. };
  86. static KeyFileData getDataFromKeyFile (XmlElement xml)
  87. {
  88. KeyFileData data;
  89. data.licensee = getLicensee (xml);
  90. data.email = getEmail (xml);
  91. data.appID = getAppID (xml);
  92. data.machineNumbers.addArray (getMachineNumbers (xml));
  93. return data;
  94. }
  95. };
  96. //==============================================================================
  97. const char* OnlineUnlockStatus::unlockedProp = "u";
  98. static const char* stateTagName = "REG";
  99. static const char* userNameProp = "user";
  100. static const char* keyfileDataProp = "key";
  101. static var machineNumberAllowed (StringArray numbersFromKeyFile,
  102. StringArray localMachineNumbers)
  103. {
  104. var result;
  105. for (int i = 0; i < localMachineNumbers.size(); ++i)
  106. {
  107. String localNumber (localMachineNumbers[i].trim());
  108. if (localNumber.isNotEmpty())
  109. {
  110. for (int j = numbersFromKeyFile.size(); --j >= 0;)
  111. {
  112. var ok (localNumber.trim().equalsIgnoreCase (numbersFromKeyFile[j].trim()));
  113. result.swapWith (ok);
  114. if (result)
  115. break;
  116. }
  117. }
  118. }
  119. return result;
  120. }
  121. //==============================================================================
  122. OnlineUnlockStatus::OnlineUnlockStatus() : status (stateTagName)
  123. {
  124. }
  125. OnlineUnlockStatus::~OnlineUnlockStatus()
  126. {
  127. }
  128. void OnlineUnlockStatus::load()
  129. {
  130. MemoryBlock mb;
  131. mb.fromBase64Encoding (getState());
  132. if (mb.getSize() > 0)
  133. status = ValueTree::readFromGZIPData (mb.getData(), mb.getSize());
  134. else
  135. status = ValueTree (stateTagName);
  136. StringArray localMachineNums (getLocalMachineIDs());
  137. if (machineNumberAllowed (StringArray ("1234"), localMachineNums))
  138. status.removeProperty (unlockedProp, nullptr);
  139. KeyFileUtils::KeyFileData data;
  140. data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (status[keyfileDataProp], getPublicKey()));
  141. if (! doesProductIDMatch (data.appID))
  142. status.removeProperty (unlockedProp, nullptr);
  143. if (! machineNumberAllowed (data.machineNumbers, localMachineNums))
  144. status.removeProperty (unlockedProp, nullptr);
  145. }
  146. void OnlineUnlockStatus::save()
  147. {
  148. MemoryOutputStream mo;
  149. {
  150. GZIPCompressorOutputStream gzipStream (&mo, 9);
  151. status.writeToStream (gzipStream);
  152. }
  153. saveState (mo.getMemoryBlock().toBase64Encoding());
  154. }
  155. char OnlineUnlockStatus::MachineIDUtilities::getPlatformPrefix()
  156. {
  157. #if JUCE_MAC
  158. return 'M';
  159. #elif JUCE_WINDOWS
  160. return 'W';
  161. #elif JUCE_LINUX
  162. return 'L';
  163. #elif JUCE_IOS
  164. return 'I';
  165. #elif JUCE_ANDROID
  166. return 'A';
  167. #endif
  168. }
  169. String OnlineUnlockStatus::MachineIDUtilities::getEncodedIDString (const String& input)
  170. {
  171. const String platform (String::charToString (getPlatformPrefix()));
  172. return platform + MD5 ((input + "salt_1" + platform).toUTF8())
  173. .toHexString().substring (0, 9).toUpperCase();
  174. }
  175. bool OnlineUnlockStatus::MachineIDUtilities::addFileIDToList (StringArray& ids, const File& f)
  176. {
  177. if (uint64 num = f.getFileIdentifier())
  178. {
  179. ids.add (getEncodedIDString (String::toHexString ((int64) num)));
  180. return true;
  181. }
  182. return false;
  183. }
  184. void OnlineUnlockStatus::MachineIDUtilities::addMACAddressesToList (StringArray& ids)
  185. {
  186. Array<MACAddress> addresses;
  187. MACAddress::findAllAddresses (addresses);
  188. for (int i = 0; i < addresses.size(); ++i)
  189. ids.add (getEncodedIDString (addresses.getReference(i).toString()));
  190. }
  191. StringArray OnlineUnlockStatus::getLocalMachineIDs()
  192. {
  193. StringArray ids;
  194. // First choice for an ID number is a filesystem ID for the user's home
  195. // folder or windows directory.
  196. #if JUCE_WINDOWS
  197. MachineIDUtilities::addFileIDToList (ids, File::getSpecialLocation (File::windowsSystemDirectory));
  198. #else
  199. MachineIDUtilities::addFileIDToList (ids, File ("~"));
  200. #endif
  201. // ..if that fails, use the MAC addresses..
  202. if (ids.size() == 0)
  203. MachineIDUtilities::addMACAddressesToList (ids);
  204. jassert (ids.size() > 0); // failed to create any IDs!
  205. return ids;
  206. }
  207. void OnlineUnlockStatus::setUserEmail (const String& usernameOrEmail)
  208. {
  209. status.setProperty (userNameProp, usernameOrEmail, nullptr);
  210. }
  211. String OnlineUnlockStatus::getUserEmail() const
  212. {
  213. return status[userNameProp].toString();
  214. }
  215. bool OnlineUnlockStatus::applyKeyFile (String keyFileContent)
  216. {
  217. KeyFileUtils::KeyFileData data;
  218. data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey()));
  219. if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && doesProductIDMatch (data.appID))
  220. {
  221. setUserEmail (data.email);
  222. status.setProperty (keyfileDataProp, keyFileContent, nullptr);
  223. status.removeProperty (unlockedProp, nullptr);
  224. var actualResult (0), dummyResult (1.0);
  225. var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs()));
  226. actualResult.swapWith (v);
  227. v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs());
  228. dummyResult.swapWith (v);
  229. jassert (! dummyResult);
  230. if ((! dummyResult) && actualResult)
  231. status.setProperty (unlockedProp, actualResult, nullptr);
  232. return isUnlocked();
  233. }
  234. return false;
  235. }
  236. static bool canConnectToWebsite (const URL& url)
  237. {
  238. ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String(), 2000, nullptr));
  239. return in != nullptr;
  240. }
  241. static bool areMajorWebsitesAvailable()
  242. {
  243. const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", nullptr};
  244. for (const char** url = urlsToTry; *url != nullptr; ++url)
  245. if (canConnectToWebsite (URL (*url)))
  246. return true;
  247. return false;
  248. }
  249. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleXmlReply (XmlElement xml)
  250. {
  251. UnlockResult r;
  252. if (const XmlElement* keyNode = xml.getChildByName ("KEY"))
  253. {
  254. const String keyText (keyNode->getAllSubText().trim());
  255. r.succeeded = keyText.length() > 10 && applyKeyFile (keyText);
  256. }
  257. if (xml.hasTagName ("MESSAGE"))
  258. r.informativeMessage = xml.getStringAttribute ("message").trim();
  259. if (xml.hasTagName ("ERROR"))
  260. r.errorMessage = xml.getStringAttribute ("error").trim();
  261. if (xml.getStringAttribute ("url").isNotEmpty())
  262. r.urlToLaunch = xml.getStringAttribute ("url").trim();
  263. if (r.errorMessage.isEmpty() && r.informativeMessage.isEmpty() && r.urlToLaunch.isEmpty() && ! r.succeeded)
  264. r.errorMessage = TRANS ("Unexpected or corrupted reply from XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n"
  265. + TRANS("Please try again in a few minutes, and contact us for support if this message appears again.");
  266. return r;
  267. }
  268. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleFailedConnection()
  269. {
  270. UnlockResult r;
  271. r.errorMessage = TRANS("Couldn't connect to XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n";
  272. if (areMajorWebsitesAvailable())
  273. r.errorMessage << TRANS("Your internet connection seems to be OK, but our webserver "
  274. "didn't respond... This is most likely a temporary problem, so try "
  275. "again in a few minutes, but if it persists, please contact us for support!");
  276. else
  277. r.errorMessage << TRANS("No internet sites seem to be accessible from your computer.. Before trying again, "
  278. "please check that your network is working correctly, and make sure "
  279. "that any firewall/security software installed on your machine isn't "
  280. "blocking your web connection.");
  281. return r;
  282. }
  283. OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::attemptWebserverUnlock (const String& email,
  284. const String& password)
  285. {
  286. // This method will block while it contacts the server, so you must run it on a background thread!
  287. jassert (! MessageManager::getInstance()->isThisTheMessageThread());
  288. String reply (readReplyFromWebserver (email, password));
  289. DBG ("Reply from server: " << reply);
  290. ScopedPointer<XmlElement> xml (XmlDocument::parse (reply));
  291. if (xml != nullptr)
  292. return handleXmlReply (*xml);
  293. return handleFailedConnection();
  294. }
  295. //==============================================================================
  296. String KeyGeneration::generateKeyFile (const String& appName,
  297. const String& userEmail,
  298. const String& userName,
  299. const String& machineNumbers,
  300. const RSAKey& privateKey)
  301. {
  302. XmlElement xml ("key");
  303. xml.setAttribute ("user", userName);
  304. xml.setAttribute ("email", userEmail);
  305. xml.setAttribute ("mach", machineNumbers);
  306. xml.setAttribute ("app", appName);
  307. xml.setAttribute ("date", String::toHexString (Time::getCurrentTime().toMilliseconds()));
  308. String comment;
  309. comment << "Keyfile for " << appName << newLine;
  310. if (userName.isNotEmpty())
  311. comment << "User: " << userName << newLine;
  312. comment << "Email: " << userEmail << newLine
  313. << "Machine numbers: " << machineNumbers << newLine
  314. << "Created: " << Time::getCurrentTime().toString (true, true);
  315. return KeyFileUtils::createKeyFile (comment, xml, privateKey);
  316. }