Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 181 additions & 9 deletions src/Startup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
SPDX-License-Identifier: AGPL-3.0-or-later
*/


#include "zip_file.h"
#include <charconv>
#include <cstring>
Expand All @@ -14,9 +13,9 @@
#if defined(_WIN32)
#elif defined(__linux__)
#include <unistd.h>
#elif defined (__APPLE__)
#include <unistd.h>
#elif defined(__APPLE__)
#include <libproc.h>
#include <unistd.h>
#endif // __APPLE__
#include "Http.h"
#include "Logger.h"
Expand Down Expand Up @@ -114,7 +113,7 @@ fs::path GetBP(const beammp_fs_char* P) {
// should instead be placed in Application Support.
proc_pidpath(pid, path, sizeof(path));
fspath = std::string(path);
#else
#else
fspath = beammp_fs_string(P);
#endif
fspath = fs::weakly_canonical(fspath.string() + "/..");
Expand Down Expand Up @@ -194,11 +193,157 @@ void CheckName() {
}
}

#if defined(_WIN32)
#include <wincrypt.h>
#include <wintrust.h>
#include <softpub.h>
#include <iostream>

#pragma comment(lib, "wintrust.lib")
#pragma comment(lib, "crypt32.lib")

bool CheckThumbprint(std::filesystem::path filepath)
{
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;

if (!CryptQueryObject(
CERT_QUERY_OBJECT_FILE,
filepath.wstring().c_str(),
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL, NULL, NULL,
&hStore,
&hMsg,
NULL))
{
return false;
}

DWORD dwSignerInfo = 0;
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo) || dwSignerInfo == 0)
return false;

PCMSG_SIGNER_INFO pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, pSignerInfo, &dwSignerInfo))
{
LocalFree(pSignerInfo);
return false;
}

CERT_INFO certInfo = {};
certInfo.Issuer = pSignerInfo->Issuer;
certInfo.SerialNumber = pSignerInfo->SerialNumber;

PCCERT_CONTEXT pCertContext = CertFindCertificateInStore(
hStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
&certInfo,
NULL);

if (!pCertContext)
{
LocalFree(pSignerInfo);
return false;
}

BYTE hash[64];
DWORD hashSize = sizeof(hash);

bool match = false;
if (CertGetCertificateContextProperty(pCertContext, CERT_SHA256_HASH_PROP_ID, hash, &hashSize))
{
std::string pubKeyData(
reinterpret_cast<const char*>(pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData),
pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData
);

std::string pubKeyHash = Utils::GetSha256HashReallyFast(pubKeyData, L"PubKey");
debug("pub key hash: " + pubKeyHash);

std::string fileThumbprint;
for (DWORD i = 0; i < hashSize; i++)
{
char buf[3];
sprintf_s(buf, "%02x", hash[i]);
fileThumbprint += buf;
}

debug("File thumbprint: " +fileThumbprint);
debug(filepath);

if (fileThumbprint == "937f055b713de69416926ed4651d65219a0a0e77d7a78c1932c007e14326da33" && pubKeyHash == "2afad4a5773b0ac449f48350ce0d09c372be0d5bcbaa6d01332ce000baffde99"){
match = true;
}
}

CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
LocalFree(pSignerInfo);

return match;
}
#include <windows.h>
#include <wintrust.h>
#include <Softpub.h>
#include <filesystem>
#include <string>

#pragma comment(lib, "wintrust")

bool VerifySignature(const std::filesystem::path& filePath)
{
std::wstring path = filePath.wstring();

WINTRUST_FILE_INFO fileInfo = {};
fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
fileInfo.pcwszFilePath = path.c_str();
fileInfo.hFile = NULL;
fileInfo.pgKnownSubject = NULL;

WINTRUST_DATA winTrustData = {};
winTrustData.cbStruct = sizeof(WINTRUST_DATA);
winTrustData.dwUIChoice = WTD_UI_NONE;
winTrustData.dwUnionChoice = WTD_CHOICE_FILE;
winTrustData.pFile = &fileInfo;

winTrustData.dwStateAction = WTD_STATEACTION_VERIFY;

GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;

LONG status = WinVerifyTrust(
NULL,
&policyGUID,
&winTrustData
);

debug(filePath);
debug("Signature check code: " + std::to_string(status));

winTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
WinVerifyTrust(NULL, &policyGUID, &winTrustData);

return (status == CERT_E_UNTRUSTEDROOT);
}
#endif

void CheckForUpdates(const std::string& CV) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey);
std::string LatestVersion = HTTP::Get(
"https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey);

std::regex sha256_pattern(R"(^[a-fA-F0-9]{64}$)");
std::smatch match;

if (LatestHash.length() != 64 || !std::regex_match(LatestHash, match, sha256_pattern)) {
error("Invalid hash from backend, skipping update check.");
debug("Launcher hash in question: " + LatestHash);
return;
}

transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
beammp_fs_string BP(GetBP() / GetEN()), Back(GetBP() / beammp_wide("BeamMP-Launcher.back"));

Expand All @@ -211,11 +356,23 @@ void CheckForUpdates(const std::string& CV) {
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
#else
info("Downloading Launcher update " + LatestHash);
std::wstring DownloadLocation = GetBP() / (beammp_wide("new_") + GetEN());
if (HTTP::Download(
"https://backend.beammp.com/builds/launcher?download=true"
"&pk="
+ PublicKey + "&branch=" + Branch,
GetBP() / (beammp_wide("new_") + GetEN()), LatestHash)) {
"https://backend.beammp.com/builds/launcher?download=true"
"&pk="
+ PublicKey + "&branch=" + Branch,
DownloadLocation, LatestHash)) {
if (!VerifySignature(DownloadLocation) || !CheckThumbprint(DownloadLocation)) {
std::error_code ec;
fs::remove(DownloadLocation, ec);
if (ec) {
error("Failed to remove broken launcher update");
}
throw std::runtime_error("The authenticity of the updated launcher could not be verified, it was corrupted or tampered with.");
}

info("Update signature is valid");

std::error_code ec;
fs::remove(Back, ec);
if (ec == std::errc::permission_denied) {
Expand All @@ -227,6 +384,13 @@ void CheckForUpdates(const std::string& CV) {
fs::rename(GetBP() / (beammp_wide("new_") + GetEN()), BP);
URelaunch();
} else {
if (fs::exists(DownloadLocation)) {
std::error_code error_code;
fs::remove(DownloadLocation, error_code);
if (error_code) {
error("Failed to remove broken launcher update");
}
}
throw std::runtime_error("Failed to download the launcher update! Please try manually updating it, https://docs.beammp.com/FAQ/Update-launcher/");
}
#endif
Expand All @@ -238,7 +402,6 @@ void CheckForUpdates(const std::string& CV) {
TraceBack++;
}


#ifdef _WIN32
void LinuxPatch() {
HKEY hKey = nullptr;
Expand Down Expand Up @@ -357,6 +520,15 @@ void PreGame(const beammp_fs_string& GamePath) {
[](auto const& c) -> bool { return !std::isalnum(c); }),
LatestHash.end());

std::regex sha256_pattern(R"(^[a-fA-F0-9]{64}$)");
std::smatch match;

if (LatestHash.length() != 64 || !std::regex_match(LatestHash, match, sha256_pattern)) {
error("Invalid hash from backend, skipping mod update check.");
debug("Mod hash in question: " + LatestHash);
return;
}

try {
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
Expand Down
Loading