Pyrogenesis  trunk
ModIo.h
Go to the documentation of this file.
1 /* Copyright (C) 2021 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 
27 #include "lib/os_path.h"
29 
30 #include <map>
31 #include <sodium.h>
32 #include <string>
33 #include <vector>
34 
35 // TODO: Allocate instance of the below two using sodium_malloc?
36 struct PKStruct
37 {
38  unsigned char sig_alg[2] = {}; // == "Ed"
39  unsigned char keynum[8] = {}; // should match the keynum in the sigstruct, else this is the wrong key
40  unsigned char pk[crypto_sign_PUBLICKEYBYTES] = {};
41 };
42 
43 struct SigStruct
44 {
45  unsigned char sig_alg[2] = {}; // "ED" (since we only support the hashed mode)
46  unsigned char keynum[8] = {}; // should match the keynum in the PKStruct
47  unsigned char sig[crypto_sign_BYTES] = {};
48 };
49 
51 {
52  std::map<std::string, std::string> properties;
53  std::vector<std::string> dependencies;
55 };
56 
58  NONE, // Default state
59  GAMEID, // The game ID is being downloaded
60  READY, // The game ID has been downloaded
61  LISTING, // The mod list is being downloaded
62  LISTED, // The mod list has been downloaded
63  DOWNLOADING, // A mod file is being downloaded
64  SUCCESS, // A mod file has been downloaded
65 
66  FAILED_GAMEID, // Game ID couldn't be retrieved
67  FAILED_LISTING, // Mod list couldn't be retrieved
68  FAILED_DOWNLOADING, // File couldn't be retrieved
69  FAILED_FILECHECK // The file is corrupted
70 };
71 
73 {
75  double progress;
76  std::string error;
77 };
78 
80 
81 /**
82  * mod.io API interfacing code.
83  *
84  * Overview
85  *
86  * This class interfaces with a remote API provider that returns a list of mod files.
87  * These can then be downloaded after some cursory checking of well-formedness of the returned
88  * metadata.
89  * Downloaded files are checked for well formedness by validating that they fit the size and hash
90  * indicated by the API, then we check if the file is actually signed by a trusted key, and only
91  * if all of that is success the file is actually possible to be loaded as a mod.
92  *
93  * Security considerations
94  *
95  * This both distrusts the loaded JS mods, and the API as much as possible.
96  * We do not want a malicious mod to use this to download arbitrary files, nor do we want the API
97  * to make us download something we have not verified.
98  * Therefore we only allow mods to download one of the mods returned by this class (using indices).
99  *
100  * This (mostly) necessitates parsing the API responses here, as opposed to in JS.
101  * One could alternatively parse the responses in a locked down JS context, but that would require
102  * storing that code in here, or making sure nobody can overwrite it. Also this would possibly make
103  * some of the needed accesses for downloading and verifying files a bit more complicated.
104  *
105  * Everything downloaded from the API has its signature verified against our public key.
106  * This is a requirement, as otherwise a compromise of the API would result in users installing
107  * possibly malicious files.
108  * So a compromised API can just serve old files that we signed, so in that case there would need
109  * to be an issue in that old file that was missed.
110  *
111  * To limit the extend to how old those files could be the signing key should be rotated
112  * regularly (e.g. every release). To allow old versions of the engine to still use the API
113  * files can be signed by both the old and the new key for some amount of time, that however
114  * only makes sense in case a mod is compatible with both engine versions.
115  *
116  * Note that this does not prevent all possible attacks a package manager/update system should
117  * defend against. This is intentionally not an update system since proper package managers already
118  * exist. However there is some possible overlap in attack vectors and these should be evalutated
119  * whether they apply and to what extend we can fix that on our side (or how to get the API provider
120  * to help us do so). For a list of some possible issues see:
121  * https://github.com/theupdateframework/specification/blob/master/tuf-spec.md
122  *
123  * The mod.io settings are also locked down such that only mods that have been authorized by us
124  * show up in API queries. This is both done so that all required information (dependencies)
125  * are stored for the files, and that only mods that have been checked for being ok are actually
126  * shown to users.
127  */
128 class ModIo
129 {
131 public:
132  ModIo();
133  ~ModIo();
134 
135  // Async requests
136  void StartGetGameId();
137  void StartListMods();
138  void StartDownloadMod(u32 idx);
139 
140  /**
141  * Advance the current async request and perform final steps if the download is complete.
142  *
143  * @param scriptInterface used for parsing the data and possibly install the mod.
144  * @return true if the download is complete (successful or not), false otherwise.
145  */
146  bool AdvanceRequest(const ScriptInterface& scriptInterface);
147 
148  /**
149  * Cancel the current async request and clean things up
150  */
151  void CancelRequest();
152 
153  const std::vector<ModIoModData>& GetMods() const
154  {
155  return m_ModData;
156  }
158  {
159  return m_DownloadProgressData;
160  }
161 
162 private:
163  static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp);
164  static size_t DownloadCallback(void* buffer, size_t size, size_t nmemb, void* userp);
165  static int DownloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
166 
167  CURLMcode SetupRequest(const std::string& url, bool fileDownload);
168  void TearDownRequest();
169 
170  bool ParseGameId(const ScriptInterface& scriptInterface, std::string& err);
171  bool ParseMods(const ScriptInterface& scriptInterface, std::string& err);
172 
173  void DeleteDownloadedFile();
174  bool VerifyDownloadedFile(std::string& err);
175 
176  // Utility methods for parsing mod.io responses and metadata
177  static bool ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err);
178  static bool ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err);
179  static bool ParseSignature(const std::vector<std::string>& minisigs, SigStruct& sig, const PKStruct& pk, std::string& err);
180 
181  // Url parts
182  std::string m_BaseUrl;
183  std::string m_GamesRequest;
184  std::string m_GameId;
185 
186  // Query parameters
187  std::string m_ApiKey;
188  std::string m_IdQuery;
189 
190  CURL* m_Curl;
191  CURLM* m_CurlMulti;
192  curl_slist* m_Headers;
193  char m_ErrorBuffer[CURL_ERROR_SIZE];
194  std::string m_ResponseData;
195 
196  // Current mod download
201 
203 
204  std::vector<ModIoModData> m_ModData;
205 
206  friend class TestModIo;
207 };
208 
209 extern ModIo* g_ModIo;
210 
211 #endif // INCLUDED_MODIO
#define NONCOPYABLE(className)
Indicates that a class is noncopyable (usually due to const or reference members, or because the clas...
Definition: code_annotation.h:227
void StartGetGameId()
Definition: JSInterface_ModIo.cpp:38
curl_slist * m_Headers
Definition: ModIo.h:192
std::string error
Definition: ModIo.h:76
std::string m_GameId
Definition: ModIo.h:184
mod.io API interfacing code.
Definition: ModIo.h:128
std::string m_ApiKey
Definition: ModIo.h:187
PKStruct m_pk
Definition: ModIo.h:202
Definition: ModIo.h:43
CURLM * m_CurlMulti
Definition: ModIo.h:191
Definition: ModIo.h:50
std::string m_GamesRequest
Definition: ModIo.h:183
SigStruct sig
Definition: ModIo.h:54
Definition: ModIo.cpp:49
Definition: ModIo.h:72
uint32_t u32
Definition: types.h:39
double progress
Definition: ModIo.h:75
OsPath m_DownloadFilePath
Definition: ModIo.h:198
Definition: path.h:79
std::vector< ModIoModData > m_ModData
Definition: ModIo.h:204
DownloadProgressData m_DownloadProgressData
Definition: ModIo.h:200
std::string m_BaseUrl
Definition: ModIo.h:182
std::vector< std::string > dependencies
Definition: ModIo.h:53
ModIo * g_ModIo
Definition: ModIo.cpp:47
DownloadProgressStatus status
Definition: ModIo.h:74
DownloadCallbackData * m_CallbackData
Definition: ModIo.h:199
const DownloadProgressData & GetDownloadProgress() const
Definition: ModIo.h:157
DownloadProgressStatus
Definition: ModIo.h:57
unsigned char pk[crypto_sign_PUBLICKEYBYTES]
Definition: ModIo.h:40
unsigned char sig_alg[2]
Definition: ModIo.h:38
int m_DownloadModID
Definition: ModIo.h:197
std::map< std::string, std::string > properties
Definition: ModIo.h:52
Definition: ModIo.h:36
Abstraction around a SpiderMonkey JS::Realm.
Definition: ScriptInterface.h:71
const std::vector< ModIoModData > & GetMods() const
Definition: ModIo.h:153
CURL * m_Curl
Definition: ModIo.h:190
std::string m_IdQuery
Definition: ModIo.h:188
std::string m_ResponseData
Definition: ModIo.h:194
unsigned char keynum[8]
Definition: ModIo.h:39