1 /* Copyright (C) 2018 Wildfire Games. 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining 4 * a copy of this software and associated documentation files (the 5 * "Software"), to deal in the Software without restriction, including 6 * without limitation the rights to use, copy, modify, merge, publish, 7 * distribute, sublicense, and/or sell copies of the Software, and to 8 * permit persons to whom the Software is furnished to do so, subject to 9 * the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included 12 * in all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 */ 22 23 #ifndef INCLUDED_MODIO 24 #define INCLUDED_MODIO 25 26 #include "lib/external_libraries/curl.h" 27 #include "scriptinterface/ScriptInterface.h" 28 29 #include <sodium.h> 30 #include <string> 31 32 // TODO: Allocate instance of the below two using sodium_malloc? 33 struct PKStruct 34 { 35 unsigned char sig_alg[2] = {}; // == "Ed" 36 unsigned char keynum[8] = {}; // should match the keynum in the sigstruct, else this is the wrong key 37 unsigned char pk[crypto_sign_PUBLICKEYBYTES] = {}; 38 }; 39 40 struct SigStruct 41 { 42 unsigned char sig_alg[2] = {}; // "ED" (since we only support the hashed mode) 43 unsigned char keynum[8] = {}; // should match the keynum in the PKStruct 44 unsigned char sig[crypto_sign_BYTES] = {}; 45 }; 46 47 struct ModIoModData 48 { 49 std::map<std::string, std::string> properties; 50 std::vector<std::string> dependencies; 51 SigStruct sig; 52 }; 53 54 enum class DownloadProgressStatus { 55 NONE, // Default state 56 GAMEID, // The game ID is being downloaded 57 READY, // The game ID has been downloaded 58 LISTING, // The mod list is being downloaded 59 LISTED, // The mod list has been downloaded 60 DOWNLOADING, // A mod file is being downloaded 61 SUCCESS, // A mod file has been downloaded 62 63 FAILED_GAMEID, // Game ID couldn't be retrieved 64 FAILED_LISTING, // Mod list couldn't be retrieved 65 FAILED_DOWNLOADING, // File couldn't be retrieved 66 FAILED_FILECHECK // The file is corrupted 67 }; 68 69 struct DownloadProgressData 70 { 71 DownloadProgressStatus status; 72 double progress; 73 std::string error; 74 }; 75 76 struct DownloadCallbackData; 77 78 /** 79 * mod.io API interfacing code. 80 * 81 * Overview 82 * 83 * This class interfaces with a remote API provider that returns a list of mod files. 84 * These can then be downloaded after some cursory checking of well-formedness of the returned 85 * metadata. 86 * Downloaded files are checked for well formedness by validating that they fit the size and hash 87 * indicated by the API, then we check if the file is actually signed by a trusted key, and only 88 * if all of that is success the file is actually possible to be loaded as a mod. 89 * 90 * Security considerations 91 * 92 * This both distrusts the loaded JS mods, and the API as much as possible. 93 * We do not want a malicious mod to use this to download arbitrary files, nor do we want the API 94 * to make us download something we have not verified. 95 * Therefore we only allow mods to download one of the mods returned by this class (using indices). 96 * 97 * This (mostly) necessitates parsing the API responses here, as opposed to in JS. 98 * One could alternatively parse the responses in a locked down JS context, but that would require 99 * storing that code in here, or making sure nobody can overwrite it. Also this would possibly make 100 * some of the needed accesses for downloading and verifying files a bit more complicated. 101 * 102 * Everything downloaded from the API has its signature verified against our public key. 103 * This is a requirement, as otherwise a compromise of the API would result in users installing 104 * possibly malicious files. 105 * So a compromised API can just serve old files that we signed, so in that case there would need 106 * to be an issue in that old file that was missed. 107 * 108 * To limit the extend to how old those files could be the signing key should be rotated 109 * regularly (e.g. every release). To allow old versions of the engine to still use the API 110 * files can be signed by both the old and the new key for some amount of time, that however 111 * only makes sense in case a mod is compatible with both engine versions. 112 * 113 * Note that this does not prevent all possible attacks a package manager/update system should 114 * defend against. This is intentionally not an update system since proper package managers already 115 * exist. However there is some possible overlap in attack vectors and these should be evalutated 116 * whether they apply and to what extend we can fix that on our side (or how to get the API provider 117 * to help us do so). For a list of some possible issues see: 118 * https://github.com/theupdateframework/specification/blob/master/tuf-spec.md 119 * 120 * The mod.io settings are also locked down such that only mods that have been authorized by us 121 * show up in API queries. This is both done so that all required information (dependencies) 122 * are stored for the files, and that only mods that have been checked for being ok are actually 123 * shown to users. 124 */ 125 class ModIo 126 { 127 NONCOPYABLE(ModIo); 128 public: 129 ModIo(); 130 ~ModIo(); 131 132 // Async requests 133 void StartGetGameId(); 134 void StartListMods(); 135 void StartDownloadMod(size_t idx); 136 137 /** 138 * Advance the current async request and perform final steps if the download is complete. 139 * 140 * @param scriptInterface used for parsing the data and possibly install the mod. 141 * @return true if the download is complete (successful or not), false otherwise. 142 */ 143 bool AdvanceRequest(const ScriptInterface& scriptInterface); 144 145 /** 146 * Cancel the current async request and clean things up 147 */ 148 void CancelRequest(); 149 GetMods()150 const std::vector<ModIoModData>& GetMods() const 151 { 152 return m_ModData; 153 } GetDownloadProgress()154 const DownloadProgressData& GetDownloadProgress() const 155 { 156 return m_DownloadProgressData; 157 } 158 159 private: 160 static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp); 161 static size_t DownloadCallback(void* buffer, size_t size, size_t nmemb, void* userp); 162 static int DownloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); 163 164 CURLMcode SetupRequest(const std::string& url, bool fileDownload); 165 void TearDownRequest(); 166 167 bool ParseGameId(const ScriptInterface& scriptInterface, std::string& err); 168 bool ParseMods(const ScriptInterface& scriptInterface, std::string& err); 169 170 void DeleteDownloadedFile(); 171 bool VerifyDownloadedFile(std::string& err); 172 173 // Utility methods for parsing mod.io responses and metadata 174 static bool ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err); 175 static bool ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err); 176 static bool ParseSignature(const std::vector<std::string>& minisigs, SigStruct& sig, const PKStruct& pk, std::string& err); 177 178 // Url parts 179 std::string m_BaseUrl; 180 std::string m_GamesRequest; 181 std::string m_GameId; 182 183 // Query parameters 184 std::string m_ApiKey; 185 std::string m_IdQuery; 186 187 CURL* m_Curl; 188 CURLM* m_CurlMulti; 189 curl_slist* m_Headers; 190 char m_ErrorBuffer[CURL_ERROR_SIZE]; 191 std::string m_ResponseData; 192 193 // Current mod download 194 int m_DownloadModID; 195 OsPath m_DownloadFilePath; 196 DownloadCallbackData* m_CallbackData; 197 DownloadProgressData m_DownloadProgressData; 198 199 PKStruct m_pk; 200 201 std::vector<ModIoModData> m_ModData; 202 203 friend class TestModIo; 204 }; 205 206 extern ModIo* g_ModIo; 207 208 #endif // INCLUDED_MODIO 209