Skip to content

Commit cdd4bf6

Browse files
author
stgatilov
committed
#5349. Implemented SHA256 checksum verification for downloaded missions.
Of course, it is done only if XML file with list of mission contains "sha256" attribute on "downloadLocation" or "localisationPack" element. idlib/hashing/sha256.* Since SHA256 is computed on-the-fly during download, and download itself is much slower anyway, I took simple standalone but unoptimized implementation of SHA256: https://github.com/B-Con/crypto-algorithms LICENSE.txt: Since author wants acknowledgement, added it =) game/Http/HttpRequest.* Added EnableSha256 and GetSha256 methods. Basically, if you call the first method, then checksum will be computed. game/Missions/Download.* Added method VerifySha256Checksum, which receives expected sha256 and enables its validation when download is over. Added MALFORMED status for the case when checksum verification failed (want special error message). Also removed excessive constructor. game/Missions/MissionManager.* LoadModListFromXml now reads and checks "sha256" field. Validation is perhaps not necessary, but better let it be. For that reason, LoadModListFromXml now returns bool (false if malformed XML). Also added MALFORMED status for requests by external users. game/DownloadMenu.cpp: Enabled SHA256 verification for mission/localization packs in CDownloadMenu::StartDownload. Print custom error message if mission has wrong checksum. Print custom error message if missionlist XML is malformed. (the other requests just use error message for FAILED, since they don't return MALFORMED) git-svn-id: http://svn.thedarkmod.com/svn/darkmod_src/trunk@9022 49c82d7f-2e2a-0410-a16f-ae8f201b507f
1 parent cdf1ab1 commit cdd4bf6

12 files changed

+401
-27
lines changed

LICENSE.txt

+16
Original file line numberDiff line numberDiff line change
@@ -1138,3 +1138,19 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY
11381138
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
11391139
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
11401140
POSSIBILITY OF SUCH DAMAGE.
1141+
1142+
1143+
sha256 implementation
1144+
---------------------------------------------------------------------------
1145+
idlib/hashing/sha256.*
1146+
1147+
These are basic implementations of standard cryptography algorithms,
1148+
written by Brad Conte ([email protected]) from scratch and without any cross-licensing.
1149+
They exist to provide publically accessible, restriction-free implementations
1150+
of popular cryptographic algorithms, like AES and SHA-1.
1151+
1152+
This code is released into the public domain free of any restrictions.
1153+
The author requests acknowledgement if the code is used, but does not require it.
1154+
This code is provided free of any liability and without any quality claims by the author.
1155+
1156+
Source: https://github.com/B-Con/crypto-algorithms

game/DownloadMenu.cpp

+34-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ void CDownloadMenu::HandleCommands(const idStr& cmd, idUserInterface* gui)
6868
}
6969
break;
7070

71+
case CMissionManager::MALFORMED:
72+
{
73+
gui->HandleNamedEvent("onAvailableMissionsRefreshed"); // hide progress dialog
74+
75+
// Issue a failure message
76+
gameLocal.Printf("Server response is incorrect\n");
77+
78+
GuiMessage msg;
79+
msg.title = common->Translate( "#str_02147" ); // Malformed response
80+
msg.message = common->Translate( "#str_02138" ); // The server returned wrong information.\nPlease report to maintainers.
81+
msg.type = GuiMessage::MSG_OK;
82+
msg.okCmd = "close_msg_box";
83+
84+
gameLocal.AddMainMenuMessage(msg);
85+
}
86+
break;
87+
7188
case CMissionManager::SUCCESSFUL:
7289
{
7390
gui->HandleNamedEvent("onAvailableMissionsRefreshed"); // hide progress dialog
@@ -90,6 +107,7 @@ void CDownloadMenu::HandleCommands(const idStr& cmd, idUserInterface* gui)
90107

91108
switch (status)
92109
{
110+
case CMissionManager::MALFORMED:
93111
case CMissionManager::FAILED:
94112
{
95113
gui->HandleNamedEvent("onDownloadableMissionDetailsDownloadFailed"); // hide progress dialog
@@ -128,6 +146,7 @@ void CDownloadMenu::HandleCommands(const idStr& cmd, idUserInterface* gui)
128146

129147
switch (status)
130148
{
149+
case CMissionManager::MALFORMED:
131150
case CMissionManager::FAILED:
132151
{
133152
gui->HandleNamedEvent("onFailedToDownloadScreenshot");
@@ -421,6 +440,7 @@ void CDownloadMenu::StartDownload(idUserInterface* gui)
421440
}
422441

423442
CDownloadPtr download(new CDownload(mod.missionUrls, missionPath, true));
443+
download->VerifySha256Checksum(mod.missionSha256);
424444
// gnartsch: In case only the language pack needs to be downloaded, do not add the mission itself to the download list.
425445
// In that case we did not add any urls for the mission itself anyway.
426446
int id = -1;
@@ -446,6 +466,7 @@ void CDownloadMenu::StartDownload(idUserInterface* gui)
446466
DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Will download the l10n pack to %s.", l10nPackPath.c_str());
447467

448468
CDownloadPtr l10nDownload(new CDownload(mod.l10nPackUrls, l10nPackPath, true));
469+
download->VerifySha256Checksum(mod.l10nPackSha256);
449470

450471
l10nId = gameLocal.m_DownloadManager->AddDownload(l10nDownload);
451472

@@ -627,6 +648,8 @@ idStr CDownloadMenu::GetMissionDownloadProgressString(int modIndex)
627648
return common->Translate( "#str_02180" ); // "queued "
628649
case CDownload::FAILED:
629650
return common->Translate( "#str_02181" ); // "failed "
651+
case CDownload::MALFORMED:
652+
return common->Translate( "#str_02518" ); // "malformed "
630653
case CDownload::IN_PROGRESS:
631654
{
632655
double totalFraction = download->GetProgressFraction();
@@ -657,7 +680,7 @@ void CDownloadMenu::ShowDownloadResult(idUserInterface* gui)
657680

658681
int successfulDownloads = 0;
659682
int failedDownloads = 0;
660-
683+
int malformedDownloads = 0;
661684
int canceledDownloads = 0; //Agent Jones
662685

663686
const DownloadableModList& mods = gameLocal.m_MissionManager->GetDownloadableMods();
@@ -680,6 +703,9 @@ void CDownloadMenu::ShowDownloadResult(idUserInterface* gui)
680703
case CDownload::FAILED:
681704
failedDownloads++;
682705
break;
706+
case CDownload::MALFORMED:
707+
malformedDownloads++;
708+
break;
683709
case CDownload::CANCELED:
684710
canceledDownloads++;//Agent Jones Test
685711
break;
@@ -748,14 +774,19 @@ void CDownloadMenu::ShowDownloadResult(idUserInterface* gui)
748774
GetPlural(successfulDownloads, common->Translate("#str_02144"), common->Translate("#str_02145")),
749775
successfulDownloads);
750776
}
751-
752777
if (failedDownloads > 0)
753778
{
754779
// "\n%d mission(s) couldn't be downloaded. Please check your disk space (or maybe some file is write protected) and try again."
755780
msg.message += va(common->Translate("#str_02146"),
756781
failedDownloads);
757782
}
758-
if (failedDownloads > 0 || successfulDownloads > 0)//Agent Jones Test
783+
if (malformedDownloads > 0)
784+
{
785+
//"\n%d mission(s) are wrong on mirrors.\nPlease report to maintainers."
786+
msg.message += va(common->Translate("#str_02693"), malformedDownloads);
787+
}
788+
789+
if (failedDownloads > 0 || successfulDownloads > 0 || malformedDownloads > 0)//Agent Jones Test
759790
gameLocal.AddMainMenuMessage(msg);
760791
else{
761792
//AJ test

game/Http/HttpRequest.cpp

+40
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
#include "curl/curl.h"
3333
#define ExtLibs
34+
#include "hashing/sha256.h"
35+
3436

3537
CHttpRequest::CHttpRequest(CHttpConnection& conn, const std::string& url) :
3638
_conn(&conn),
@@ -51,6 +53,28 @@ CHttpRequest::CHttpRequest(CHttpConnection& conn, const std::string& url, const
5153
_progress(0)
5254
{}
5355

56+
CHttpRequest::~CHttpRequest() {}
57+
58+
void CHttpRequest::EnableSha256()
59+
{
60+
_computeSha256 = true;
61+
}
62+
63+
idStr CHttpRequest::GetSha256() const
64+
{
65+
if (!_sha256state)
66+
return "";
67+
uint8_t digest[32];
68+
sha256_final(_sha256state.get(), digest);
69+
idStr res;
70+
for (int i = 0; i < 32; i++) {
71+
char tmp[8];
72+
idStr::snPrintf(tmp, sizeof(tmp), "%02x", digest[i]);
73+
res += tmp;
74+
}
75+
return res;
76+
}
77+
5478
// Agent Jones #3766
5579
int CHttpRequest::TDMHttpProgressFunc(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
5680
{
@@ -154,6 +178,12 @@ void CHttpRequest::Perform()
154178
_destStream.open(_destFilename.c_str(), std::ofstream::out|std::ofstream::binary);
155179
}
156180

181+
if (_computeSha256)
182+
{
183+
_sha256state.reset(new SHA256_CTX());
184+
sha256_init(_sha256state.get());
185+
}
186+
157187
CURLcode result = ExtLibs::curl_easy_perform( _handle );
158188

159189
if (!_destFilename.empty())
@@ -271,6 +301,11 @@ size_t CHttpRequest::WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, C
271301

272302
self->UpdateProgress();
273303

304+
if (self->_computeSha256)
305+
{
306+
sha256_update(self->_sha256state.get(), (uint8_t*)ptr, bytesToCopy);
307+
}
308+
274309
return static_cast<size_t>(bytesToCopy);
275310
}
276311

@@ -288,6 +323,11 @@ size_t CHttpRequest::WriteFileCallback(void* ptr, size_t size, size_t nmemb, CHt
288323

289324
self->UpdateProgress();
290325

326+
if (self->_computeSha256)
327+
{
328+
sha256_update(self->_sha256state.get(), (uint8_t*)ptr, bytesToCopy);
329+
}
330+
291331
return static_cast<size_t>(bytesToCopy);
292332
}
293333

game/Http/HttpRequest.h

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ typedef void CURL;
2626
// Shared_ptr typedef
2727
typedef std::shared_ptr<pugi::xml_document> XmlDocumentPtr;
2828

29+
typedef struct SHA256_CTX SHA256_CTX;
30+
2931
/**
3032
* greebo: An object representing a single HttpRequest, holding
3133
* the result (string) and status information.
@@ -69,11 +71,18 @@ class CHttpRequest
6971

7072
double _progress;
7173

74+
bool _computeSha256 = false;
75+
std::unique_ptr<SHA256_CTX> _sha256state;
76+
7277
public:
7378
CHttpRequest(CHttpConnection& conn, const std::string& url);
7479

7580
CHttpRequest(CHttpConnection& conn, const std::string& url, const std::string& destFilename);
7681

82+
~CHttpRequest();
83+
84+
void EnableSha256();
85+
7786
// Callbacks for CURL
7887
static size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, CHttpRequest* self);
7988
static size_t WriteFileCallback(void* ptr, size_t size, size_t nmemb, CHttpRequest* self);
@@ -96,6 +105,7 @@ class CHttpRequest
96105
// Returns the result as XML document
97106
XmlDocumentPtr GetResultXml();
98107

108+
idStr GetSha256() const;
99109
private:
100110
void InitRequest();
101111

game/Missions/Download.cpp

+22-10
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@
2323
#include "../ZipLoader/ZipLoader.h"
2424
#include "MissionManager.h"
2525

26-
CDownload::CDownload(const idStr& url, const idStr& destFilename, bool enablePK4check) :
27-
_curUrl(0),
28-
_destFilename(destFilename),
29-
_status(NOT_STARTED_YET),
30-
_pk4CheckEnabled(enablePK4check),
31-
_relatedDownload(-1)
32-
{
33-
_urls.Append(url);
34-
}
3526

3627
CDownload::CDownload(const idStringList& urls, const idStr& destFilename, bool enablePK4check) :
3728
_urls(urls),
@@ -47,6 +38,11 @@ CDownload::~CDownload()
4738
Stop(false);
4839
}
4940

41+
void CDownload::VerifySha256Checksum(const idStr &sha256)
42+
{
43+
_expectedSha256 = sha256;
44+
}
45+
5046
CDownload::DownloadStatus CDownload::GetStatus()
5147
{
5248
return _status;
@@ -134,13 +130,29 @@ void CDownload::Perform()
134130

135131
// Create a new request
136132
_request = gameLocal.m_HttpConnection->CreateRequest(url.c_str(), _tempFilename.c_str());
133+
// stgatilov: if we have checksum to verify, enable its computation on-the-fly
134+
if (_expectedSha256.Length())
135+
_request->EnableSha256();
137136

138137
// Start the download, blocks until finished or aborted
139138
_request->Perform();
140139

141140
if (_request->GetStatus() == CHttpRequest::OK)
142141
{
143-
// Check the downloaded file
142+
// stgatilov: Verify checksum if specified
143+
if (_expectedSha256.Length())
144+
{
145+
idStr computedSha256 = _request->GetSha256();
146+
if (computedSha256 != _expectedSha256)
147+
{
148+
DM_LOG(LC_MAINMENU, LT_DEBUG)LOGSTRING("Wrong checksum at '%s': %s instead of %s", url.c_str(), computedSha256.c_str(), _expectedSha256.c_str());
149+
CMissionManager::DoRemoveFile(_tempFilename.c_str());
150+
_status = MALFORMED;
151+
break;
152+
}
153+
}
154+
155+
// Check that downloaded file is a zip indeed
144156
if (_pk4CheckEnabled)
145157
{
146158
bool valid = CheckValidPK4(_tempFilename);

game/Missions/Download.h

+7-6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class CDownload
6161
FAILED,
6262
SUCCESS,
6363
CANCELED,//Agent Jones
64+
MALFORMED, //stgatilov: checksum invalid
6465
};
6566

6667
// Some additional data which can be set by clients
@@ -87,19 +88,14 @@ class CDownload
8788
ThreadPtr _thread;
8889

8990
bool _pk4CheckEnabled;
91+
idStr _expectedSha256; //empty if check is ignored
9092

9193
UserData _userData;
9294

9395
// The ID of the related download (mission + l10n)
9496
int _relatedDownload;
9597

9698
public:
97-
/**
98-
* greebo: Construct a new Download using the given URL.
99-
* The download data will be saved to the given destFilename;
100-
*/
101-
CDownload(const idStr& url, const idStr& destFilename, bool enablePK4check = false);
102-
10399
/**
104100
* greebo: Construct a new Download using the given list of
105101
* alternative URLs. If downloading from the first URL fails
@@ -110,6 +106,11 @@ class CDownload
110106

111107
~CDownload();
112108

109+
// stgatilov: sha256 must be valid 64-digit hex number or empty string.
110+
// If empty, then sha256 check is disabled (default).
111+
// If not empty, then sha256 checksum of downloaded file will be computed and verified after download.
112+
void VerifySha256Checksum(const idStr &sha256);
113+
113114
// Start the download. This will spawn a new thread and execution
114115
// will return to the calling code.
115116
void Start();

0 commit comments

Comments
 (0)