mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #7609 from IntelOrca/refactor/openssl-usage
Refactor OpenSSL usage
This commit is contained in:
commit
3b8c0703f4
|
@ -37,6 +37,8 @@
|
|||
933F2CB820935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; };
|
||||
933F2CB920935653001B33FD /* LocalisationService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933F2CB620935653001B33FD /* LocalisationService.cpp */; };
|
||||
933F2CBB20935668001B33FD /* LocalisationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 933F2CBA20935668001B33FD /* LocalisationService.h */; };
|
||||
9344BEF920C1E6180047D165 /* Crypt.h in Headers */ = {isa = PBXBuildFile; fileRef = 9344BEF720C1E6180047D165 /* Crypt.h */; };
|
||||
9344BEFA20C1E6180047D165 /* Crypt.OpenSSL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9344BEF820C1E6180047D165 /* Crypt.OpenSSL.cpp */; };
|
||||
9346F9D8208A191900C77D91 /* Guest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9346F9D6208A191900C77D91 /* Guest.cpp */; };
|
||||
9346F9D9208A191900C77D91 /* Guest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9346F9D6208A191900C77D91 /* Guest.cpp */; };
|
||||
9346F9DA208A191900C77D91 /* Guest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9346F9D6208A191900C77D91 /* Guest.cpp */; };
|
||||
|
@ -868,6 +870,8 @@
|
|||
9308D9FD209908090079EE96 /* Surface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Surface.h; sourceTree = "<group>"; };
|
||||
933F2CB620935653001B33FD /* LocalisationService.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LocalisationService.cpp; sourceTree = "<group>"; };
|
||||
933F2CBA20935668001B33FD /* LocalisationService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalisationService.h; sourceTree = "<group>"; };
|
||||
9344BEF720C1E6180047D165 /* Crypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crypt.h; sourceTree = "<group>"; };
|
||||
9344BEF820C1E6180047D165 /* Crypt.OpenSSL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Crypt.OpenSSL.cpp; sourceTree = "<group>"; };
|
||||
9346F9D6208A191900C77D91 /* Guest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Guest.cpp; sourceTree = "<group>"; };
|
||||
9346F9D7208A191900C77D91 /* GuestPathfinding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GuestPathfinding.cpp; sourceTree = "<group>"; };
|
||||
9350B44420B46E0800897BC5 /* translit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = translit.h; sourceTree = "<group>"; };
|
||||
|
@ -2405,6 +2409,8 @@
|
|||
children = (
|
||||
F76C83791EC4E7CC00FA49E2 /* Collections.hpp */,
|
||||
F76C837A1EC4E7CC00FA49E2 /* Console.cpp */,
|
||||
9344BEF720C1E6180047D165 /* Crypt.h */,
|
||||
9344BEF820C1E6180047D165 /* Crypt.OpenSSL.cpp */,
|
||||
93CBA4C220A7502E00867D56 /* Imaging.cpp */,
|
||||
93CBA4C120A7502D00867D56 /* Imaging.h */,
|
||||
F76C837B1EC4E7CC00FA49E2 /* Console.hpp */,
|
||||
|
@ -3253,6 +3259,7 @@
|
|||
C6352B941F477032006CCEE3 /* PlaceParkEntranceAction.hpp in Headers */,
|
||||
C6352B911F477032006CCEE3 /* GameAction.h in Headers */,
|
||||
C62D838B1FD36D6F008C04F1 /* EditorObjectSelectionSession.h in Headers */,
|
||||
9344BEF920C1E6180047D165 /* Crypt.h in Headers */,
|
||||
939A35A220C12FFD00630B3F /* InteractiveConsole.h in Headers */,
|
||||
93CBA4C320A7502E00867D56 /* Imaging.h in Headers */,
|
||||
9308DA05209908090079EE96 /* Surface.h in Headers */,
|
||||
|
@ -3883,6 +3890,7 @@
|
|||
C688786720289A4A0084B384 /* SawyerCoding.cpp in Sources */,
|
||||
F76C86A61EC4E88400FA49E2 /* macos.mm in Sources */,
|
||||
93F9DA3B20B4701100D1BE92 /* StdInOutConsole.cpp in Sources */,
|
||||
9344BEFA20C1E6180047D165 /* Crypt.OpenSSL.cpp in Sources */,
|
||||
93F76F0520BFF77B00D4512C /* Paint.TileElement.cpp in Sources */,
|
||||
C68878FE20289B9B0084B384 /* MiniSuspendedCoaster.cpp in Sources */,
|
||||
F76C86AD1EC4E88400FA49E2 /* PlatformEnvironment.cpp in Sources */,
|
||||
|
|
|
@ -150,9 +150,6 @@ namespace OpenRCT2
|
|||
gfx_unload_g2();
|
||||
gfx_unload_g1();
|
||||
config_release();
|
||||
#ifndef DISABLE_NETWORK
|
||||
EVP_MD_CTX_destroy(gHashCTX);
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
Instance = nullptr;
|
||||
}
|
||||
|
@ -331,11 +328,6 @@ namespace OpenRCT2
|
|||
}
|
||||
_initialised = true;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
gHashCTX = EVP_MD_CTX_create();
|
||||
Guard::Assert(gHashCTX != nullptr, "EVP_MD_CTX_create failed");
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
crash_init();
|
||||
|
||||
if (gConfigGeneral.last_run_version != nullptr && String::Equals(gConfigGeneral.last_run_version, OPENRCT2_VERSION))
|
||||
|
|
|
@ -31,11 +31,6 @@ bool gOpenRCT2NoGraphics = false;
|
|||
bool gOpenRCT2ShowChangelog;
|
||||
bool gOpenRCT2SilentBreakpad;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
// OpenSSL's message digest context used for calculating sprite checksums
|
||||
EVP_MD_CTX * gHashCTX = nullptr;
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
uint32 gCurrentDrawCount = 0;
|
||||
uint8 gScreenFlags;
|
||||
uint32 gScreenAge;
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
#include "common.h"
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
#include <openssl/evp.h>
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
enum STARTUP_ACTION
|
||||
{
|
||||
STARTUP_ACTION_NONE,
|
||||
|
@ -58,10 +54,6 @@ extern bool gOpenRCT2NoGraphics;
|
|||
extern bool gOpenRCT2ShowChangelog;
|
||||
extern bool gOpenRCT2SilentBreakpad;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
extern EVP_MD_CTX * gHashCTX;
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
extern sint32 gNetworkStart;
|
||||
extern char gNetworkStartHost[128];
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
#pragma region Copyright (c) 2018 OpenRCT2 Developers
|
||||
/*****************************************************************************
|
||||
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
||||
*
|
||||
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
||||
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* A full copy of the GNU General Public License can be found in licence.txt
|
||||
*****************************************************************************/
|
||||
#pragma endregion
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
|
||||
#include "Crypt.h"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
using namespace Crypt;
|
||||
|
||||
static void OpenSSLThrowOnBadStatus(const std::string_view& name, int status)
|
||||
{
|
||||
if (status != 1)
|
||||
{
|
||||
throw std::runtime_error(std::string(name) + " failed: " + std::to_string(status));
|
||||
}
|
||||
}
|
||||
|
||||
static void OpenSSLInitialise()
|
||||
{
|
||||
static bool _opensslInitialised = false;
|
||||
if (!_opensslInitialised)
|
||||
{
|
||||
_opensslInitialised = true;
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
class OpenSSLHashAlgorithm final : public TBase
|
||||
{
|
||||
private:
|
||||
const EVP_MD * _type;
|
||||
EVP_MD_CTX * _ctx{};
|
||||
bool _initialised{};
|
||||
|
||||
public:
|
||||
OpenSSLHashAlgorithm(const EVP_MD * type)
|
||||
{
|
||||
_type = type;
|
||||
_ctx = EVP_MD_CTX_create();
|
||||
if (_ctx == nullptr)
|
||||
{
|
||||
throw std::runtime_error("EVP_MD_CTX_create failed");
|
||||
}
|
||||
}
|
||||
|
||||
~OpenSSLHashAlgorithm()
|
||||
{
|
||||
EVP_MD_CTX_destroy(_ctx);
|
||||
}
|
||||
|
||||
TBase * Clear() override
|
||||
{
|
||||
if (EVP_DigestInit_ex(_ctx, _type, nullptr) <= 0)
|
||||
{
|
||||
throw std::runtime_error("EVP_DigestInit_ex failed");
|
||||
}
|
||||
_initialised = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
TBase * Update(const void * data, size_t dataLen) override
|
||||
{
|
||||
// Auto initialise
|
||||
if (!_initialised)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
if (EVP_DigestUpdate(_ctx, data, dataLen) <= 0)
|
||||
{
|
||||
throw std::runtime_error("EVP_DigestUpdate failed");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
typename TBase::Result Finish() override
|
||||
{
|
||||
if (!_initialised)
|
||||
{
|
||||
throw std::runtime_error("No data to hash.");
|
||||
}
|
||||
_initialised = false;
|
||||
|
||||
typename TBase::Result result;
|
||||
unsigned int digestSize{};
|
||||
if (EVP_DigestFinal(_ctx, result.data(), &digestSize) <= 0)
|
||||
{
|
||||
EVP_MD_CTX_destroy(_ctx);
|
||||
throw std::runtime_error("EVP_DigestFinal failed");
|
||||
}
|
||||
if (digestSize != result.size())
|
||||
{
|
||||
throw std::runtime_error("Expected digest size to equal " + std::to_string(result.size()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class OpenSSLRsaKey final : public RsaKey
|
||||
{
|
||||
public:
|
||||
EVP_PKEY * GetEvpKey() const { return _evpKey; }
|
||||
|
||||
~OpenSSLRsaKey()
|
||||
{
|
||||
EVP_PKEY_free(_evpKey);
|
||||
}
|
||||
|
||||
void Generate() override
|
||||
{
|
||||
auto ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
throw std::runtime_error("EVP_PKEY_CTX_new_id failed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto status = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
|
||||
if (status == 0)
|
||||
{
|
||||
throw std::runtime_error("EVP_PKEY_CTX_set_rsa_keygen_bits failed");
|
||||
}
|
||||
|
||||
status = EVP_PKEY_keygen_init(ctx);
|
||||
OpenSSLThrowOnBadStatus("EVP_PKEY_keygen_init", status);
|
||||
|
||||
EVP_PKEY * key{};
|
||||
status = EVP_PKEY_keygen(ctx, &key);
|
||||
OpenSSLThrowOnBadStatus("EVP_PKEY_keygen", status);
|
||||
|
||||
EVP_PKEY_free(_evpKey);
|
||||
_evpKey = key;
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPrivate(const std::string_view& pem) override
|
||||
{
|
||||
SetKey(pem, true);
|
||||
}
|
||||
|
||||
void SetPublic(const std::string_view& pem) override
|
||||
{
|
||||
SetKey(pem, false);
|
||||
}
|
||||
|
||||
std::string GetPrivate() override { return GetKey(true); }
|
||||
|
||||
std::string GetPublic() override { return GetKey(false); }
|
||||
|
||||
private:
|
||||
EVP_PKEY * _evpKey{};
|
||||
|
||||
void SetKey(const std::string_view& pem, bool isPrivate)
|
||||
{
|
||||
// Read PEM data via BIO buffer
|
||||
// HACK first parameter is not const on MINGW for some reason
|
||||
auto bio = BIO_new_mem_buf((void *)pem.data(), (int)pem.size());
|
||||
if (bio == nullptr)
|
||||
{
|
||||
throw std::runtime_error("BIO_new_mem_buf failed");
|
||||
}
|
||||
auto rsa = isPrivate ?
|
||||
PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr) :
|
||||
PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr);
|
||||
if (rsa == nullptr)
|
||||
{
|
||||
BIO_free_all(bio);
|
||||
auto msg = isPrivate ?
|
||||
"PEM_read_bio_RSAPrivateKey failed" :
|
||||
"PEM_read_bio_RSAPublicKey failed";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
BIO_free_all(bio);
|
||||
|
||||
if (isPrivate && !RSA_check_key(rsa))
|
||||
{
|
||||
RSA_free(rsa);
|
||||
throw std::runtime_error("PEM key was invalid");
|
||||
}
|
||||
|
||||
// Assign new key
|
||||
EVP_PKEY_free(_evpKey);
|
||||
_evpKey = EVP_PKEY_new();
|
||||
EVP_PKEY_set1_RSA(_evpKey, rsa);
|
||||
RSA_free(rsa);
|
||||
}
|
||||
|
||||
std::string GetKey(bool isPrivate)
|
||||
{
|
||||
if (_evpKey == nullptr)
|
||||
{
|
||||
throw std::runtime_error("No key has been assigned");
|
||||
}
|
||||
|
||||
auto rsa = EVP_PKEY_get1_RSA(_evpKey);
|
||||
if (rsa == nullptr)
|
||||
{
|
||||
throw std::runtime_error("EVP_PKEY_get1_RSA failed");
|
||||
}
|
||||
|
||||
auto bio = BIO_new(BIO_s_mem());
|
||||
if (bio == nullptr)
|
||||
{
|
||||
throw std::runtime_error("BIO_new failed");
|
||||
}
|
||||
|
||||
auto status = isPrivate ?
|
||||
PEM_write_bio_RSAPrivateKey(bio, rsa, nullptr, nullptr, 0, nullptr, nullptr) :
|
||||
PEM_write_bio_RSAPublicKey(bio, rsa);
|
||||
if (status != 1)
|
||||
{
|
||||
BIO_free_all(bio);
|
||||
RSA_free(rsa);
|
||||
throw std::runtime_error("PEM_write_bio_RSAPrivateKey failed");
|
||||
}
|
||||
RSA_free(rsa);
|
||||
|
||||
auto keylen = BIO_pending(bio);
|
||||
std::string result(keylen, 0);
|
||||
BIO_read(bio, result.data(), keylen);
|
||||
BIO_free_all(bio);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class OpenSSLRsaAlgorithm final : public RsaAlgorithm
|
||||
{
|
||||
public:
|
||||
std::vector<uint8_t> SignData(const RsaKey& key, const void * data, size_t dataLen) override
|
||||
{
|
||||
auto evpKey = static_cast<const OpenSSLRsaKey&>(key).GetEvpKey();
|
||||
EVP_MD_CTX * mdctx{};
|
||||
try
|
||||
{
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
if (mdctx == nullptr)
|
||||
{
|
||||
throw std::runtime_error("EVP_MD_CTX_create failed");
|
||||
}
|
||||
|
||||
auto status = EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, evpKey);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestSignInit failed", status);
|
||||
|
||||
status = EVP_DigestSignUpdate(mdctx, data, dataLen);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestSignUpdate failed", status);
|
||||
|
||||
// Get required length of signature
|
||||
size_t sigLen{};
|
||||
status = EVP_DigestSignFinal(mdctx, nullptr, &sigLen);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestSignFinal failed", status);
|
||||
|
||||
// Get signature
|
||||
std::vector<uint8_t> signature(sigLen);
|
||||
status = EVP_DigestSignFinal(mdctx, signature.data(), &sigLen);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestSignFinal failed", status);
|
||||
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return signature;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
bool VerifyData(const RsaKey& key, const void * data, size_t dataLen, const void * sig, size_t sigLen) override
|
||||
{
|
||||
auto evpKey = static_cast<const OpenSSLRsaKey&>(key).GetEvpKey();
|
||||
EVP_MD_CTX * mdctx{};
|
||||
try
|
||||
{
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
if (mdctx == nullptr)
|
||||
{
|
||||
throw std::runtime_error("EVP_MD_CTX_create failed");
|
||||
}
|
||||
|
||||
auto status = EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, evpKey);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestVerifyInit", status);
|
||||
|
||||
status = EVP_DigestVerifyUpdate(mdctx, data, dataLen);
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestVerifyUpdate", status);
|
||||
|
||||
status = EVP_DigestVerifyFinal(mdctx, (uint8_t*)sig, sigLen);
|
||||
if (status != 0 && status != 1)
|
||||
{
|
||||
OpenSSLThrowOnBadStatus("EVP_DigestVerifyUpdate", status);
|
||||
}
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return status == 1;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace Crypt
|
||||
{
|
||||
std::unique_ptr<Sha1Algorithm> CreateSHA1()
|
||||
{
|
||||
OpenSSLInitialise();
|
||||
return std::make_unique<OpenSSLHashAlgorithm<Sha1Algorithm>>(EVP_sha1());
|
||||
}
|
||||
|
||||
std::unique_ptr<Sha256Algorithm> CreateSHA256()
|
||||
{
|
||||
OpenSSLInitialise();
|
||||
return std::make_unique<OpenSSLHashAlgorithm<Sha256Algorithm>>(EVP_sha256());
|
||||
}
|
||||
|
||||
std::unique_ptr<RsaAlgorithm> CreateRSA()
|
||||
{
|
||||
OpenSSLInitialise();
|
||||
return std::make_unique<OpenSSLRsaAlgorithm>();
|
||||
}
|
||||
|
||||
std::unique_ptr<RsaKey> CreateRSAKey()
|
||||
{
|
||||
OpenSSLInitialise();
|
||||
return std::make_unique<OpenSSLRsaKey>();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DISABLE_NETWORK
|
|
@ -0,0 +1,79 @@
|
|||
#pragma region Copyright (c) 2018 OpenRCT2 Developers
|
||||
/*****************************************************************************
|
||||
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
||||
*
|
||||
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
||||
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* A full copy of the GNU General Public License can be found in licence.txt
|
||||
*****************************************************************************/
|
||||
#pragma endregion
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Crypt
|
||||
{
|
||||
template<size_t TLength>
|
||||
class HashAlgorithm
|
||||
{
|
||||
public:
|
||||
typedef std::array<uint8_t, TLength> Result;
|
||||
|
||||
virtual ~HashAlgorithm() = default;
|
||||
virtual HashAlgorithm * Clear() = 0;
|
||||
virtual HashAlgorithm * Update(const void * data, size_t dataLen) = 0;
|
||||
virtual Result Finish() = 0;
|
||||
};
|
||||
|
||||
class RsaKey
|
||||
{
|
||||
public:
|
||||
virtual ~RsaKey() = default;
|
||||
virtual void Generate() = 0;
|
||||
virtual void SetPrivate(const std::string_view& pem) = 0;
|
||||
virtual void SetPublic(const std::string_view& pem) = 0;
|
||||
virtual std::string GetPrivate() = 0;
|
||||
virtual std::string GetPublic() = 0;
|
||||
};
|
||||
|
||||
class RsaAlgorithm
|
||||
{
|
||||
public:
|
||||
virtual ~RsaAlgorithm() = default;
|
||||
virtual std::vector<uint8_t> SignData(const RsaKey& key, const void * data, size_t dataLen) = 0;
|
||||
virtual bool VerifyData(const RsaKey& key, const void * data, size_t dataLen, const void * sig, size_t sigLen) = 0;
|
||||
};
|
||||
|
||||
using Sha1Algorithm = HashAlgorithm<20>;
|
||||
using Sha256Algorithm = HashAlgorithm<32>;
|
||||
|
||||
// Factories
|
||||
std::unique_ptr<Sha1Algorithm> CreateSHA1();
|
||||
std::unique_ptr<Sha256Algorithm> CreateSHA256();
|
||||
std::unique_ptr<RsaAlgorithm> CreateRSA();
|
||||
std::unique_ptr<RsaKey> CreateRSAKey();
|
||||
|
||||
inline Sha1Algorithm::Result SHA1(const void * data, size_t dataLen)
|
||||
{
|
||||
return CreateSHA1()
|
||||
->Update(data, dataLen)
|
||||
->Finish();
|
||||
}
|
||||
|
||||
inline Sha256Algorithm::Result SHA256(const void * data, size_t dataLen)
|
||||
{
|
||||
return CreateSHA256()
|
||||
->Update(data, dataLen)
|
||||
->Finish();
|
||||
}
|
||||
}
|
|
@ -56,10 +56,10 @@ namespace File
|
|||
std::vector<uint8> result;
|
||||
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
auto pathW = String::ToUtf16(path.data());
|
||||
auto pathW = String::ToUtf16(std::string(path));
|
||||
std::ifstream fs(pathW, std::ios::in | std::ios::binary);
|
||||
#else
|
||||
std::ifstream fs(path.data(), std::ios::in | std::ios::binary);
|
||||
std::ifstream fs(std::string(path), std::ios::in | std::ios::binary);
|
||||
#endif
|
||||
if (!fs.is_open())
|
||||
{
|
||||
|
@ -83,6 +83,15 @@ namespace File
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string ReadAllText(const std::string_view& path)
|
||||
{
|
||||
auto bytes = ReadAllBytes(path);
|
||||
// TODO skip BOM
|
||||
std::string result(bytes.size(), 0);
|
||||
std::copy(bytes.begin(), bytes.end(), result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteAllBytes(const std::string &path, const void * buffer, size_t length)
|
||||
{
|
||||
auto fs = FileStream(path, FILE_MODE_WRITE);
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace File
|
|||
bool Delete(const std::string &path);
|
||||
bool Move(const std::string &srcPath, const std::string &dstPath);
|
||||
std::vector<uint8> ReadAllBytes(const std::string_view& path);
|
||||
std::string ReadAllText(const std::string_view& path);
|
||||
void WriteAllBytes(const std::string &path, const void * buffer, size_t length);
|
||||
std::vector<std::string> ReadAllLines(const std::string &path);
|
||||
uint64 GetLastModified(const std::string &path);
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <istream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../common.h"
|
||||
#include "Memory.hpp"
|
||||
|
||||
|
@ -117,3 +119,26 @@ class IOException : public std::runtime_error
|
|||
public:
|
||||
explicit IOException(const std::string &message) : std::runtime_error(message) { }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ivstream : public std::istream
|
||||
{
|
||||
private:
|
||||
class vector_streambuf : public std::basic_streambuf<char, std::char_traits<char>>
|
||||
{
|
||||
public:
|
||||
explicit vector_streambuf(const std::vector<T>& vec)
|
||||
{
|
||||
this->setg((char *)vec.data(), (char *)vec.data(), (char *)(vec.data() + vec.size()));
|
||||
}
|
||||
};
|
||||
|
||||
vector_streambuf _streambuf;
|
||||
|
||||
public:
|
||||
ivstream(const std::vector<T>& vec)
|
||||
: std::istream(&_streambuf),
|
||||
_streambuf(vec)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,39 +19,15 @@
|
|||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <streambuf>
|
||||
#include <unordered_map>
|
||||
#include <png.h>
|
||||
#include "FileStream.hpp"
|
||||
#include "IStream.hpp"
|
||||
#include "Guard.hpp"
|
||||
#include "Imaging.h"
|
||||
#include "Memory.hpp"
|
||||
#include "String.hpp"
|
||||
#include "../drawing/Drawing.h"
|
||||
|
||||
template<typename T>
|
||||
class ivstream : public std::istream
|
||||
{
|
||||
private:
|
||||
class vector_streambuf : public std::basic_streambuf<char, std::char_traits<char>>
|
||||
{
|
||||
public:
|
||||
explicit vector_streambuf(const std::vector<T>& vec)
|
||||
{
|
||||
this->setg((char *)vec.data(), (char *)vec.data(), (char *)(vec.data() + vec.size()));
|
||||
}
|
||||
};
|
||||
|
||||
vector_streambuf _streambuf;
|
||||
|
||||
public:
|
||||
ivstream(const std::vector<T>& vec)
|
||||
: std::istream(&_streambuf),
|
||||
_streambuf(vec)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
namespace Imaging
|
||||
{
|
||||
constexpr auto EXCEPTION_IMAGE_FORMAT_UNKNOWN = "Unknown image format.";
|
||||
|
|
|
@ -75,8 +75,6 @@ static sint32 _pickup_peep_old_x = LOCATION_NULL;
|
|||
|
||||
#include "NetworkAction.h"
|
||||
|
||||
#include <openssl/evp.h> // just for OpenSSL_add_all_algorithms()
|
||||
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
@ -129,7 +127,6 @@ Network::Network()
|
|||
server_command_handlers[NETWORK_COMMAND_GAMEINFO] = &Network::Server_Handle_GAMEINFO;
|
||||
server_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Server_Handle_TOKEN;
|
||||
server_command_handlers[NETWORK_COMMAND_OBJECTS] = &Network::Server_Handle_OBJECTS;
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
_chat_log_fs << std::unitbuf;
|
||||
_server_log_fs << std::unitbuf;
|
||||
|
@ -668,11 +665,11 @@ bool Network::CheckSRAND(uint32 tick, uint32 srand0)
|
|||
server_srand0_tick = 0;
|
||||
// Check that the server and client sprite hashes match
|
||||
const char *client_sprite_hash = sprite_checksum();
|
||||
const bool sprites_mismatch = server_sprite_hash[0] != '\0' && strcmp(client_sprite_hash, server_sprite_hash) != 0;
|
||||
const bool sprites_mismatch = server_sprite_hash[0] != '\0' && strcmp(client_sprite_hash, server_sprite_hash.c_str()) != 0;
|
||||
// Check PRNG values and sprite hashes, if exist
|
||||
if ((srand0 != server_srand0) || sprites_mismatch) {
|
||||
#ifdef DEBUG_DESYNC
|
||||
dbg_report_desync(tick, srand0, server_srand0, client_sprite_hash, server_sprite_hash);
|
||||
dbg_report_desync(tick, srand0, server_srand0, client_sprite_hash, server_sprite_hash.c_str());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
@ -2370,13 +2367,15 @@ void Network::Client_Handle_TICK(NetworkConnection& connection, NetworkPacket& p
|
|||
if (server_srand0_tick == 0) {
|
||||
server_srand0 = srand0;
|
||||
server_srand0_tick = server_tick;
|
||||
server_sprite_hash[0] = '\0';
|
||||
server_sprite_hash.resize(0);
|
||||
if (flags & NETWORK_TICK_FLAG_CHECKSUMS)
|
||||
{
|
||||
const char* text = packet.ReadString();
|
||||
if (text != nullptr)
|
||||
{
|
||||
safe_strcpy(server_sprite_hash, text, sizeof(server_sprite_hash));
|
||||
auto textLen = std::strlen(text);
|
||||
server_sprite_hash.resize(textLen);
|
||||
std::memcpy(server_sprite_hash.data(), text, textLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,78 +17,32 @@
|
|||
#ifndef DISABLE_NETWORK
|
||||
|
||||
#include <vector>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include "../core/Crypt.h"
|
||||
#include "../core/IStream.hpp"
|
||||
#include "../Diagnostic.h"
|
||||
#include "NetworkKey.h"
|
||||
|
||||
#define KEY_TYPE EVP_PKEY_RSA
|
||||
|
||||
constexpr sint32 KEY_LENGTH_BITS = 2048;
|
||||
|
||||
NetworkKey::NetworkKey()
|
||||
{
|
||||
_ctx = EVP_PKEY_CTX_new_id(KEY_TYPE, nullptr);
|
||||
if (_ctx == nullptr)
|
||||
{
|
||||
log_error("Failed to create OpenSSL context");
|
||||
}
|
||||
}
|
||||
|
||||
NetworkKey::~NetworkKey()
|
||||
{
|
||||
Unload();
|
||||
if (_ctx != nullptr)
|
||||
{
|
||||
EVP_PKEY_CTX_free(_ctx);
|
||||
_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
NetworkKey::NetworkKey() { }
|
||||
NetworkKey::~NetworkKey() { }
|
||||
|
||||
void NetworkKey::Unload()
|
||||
{
|
||||
if (_key != nullptr)
|
||||
{
|
||||
EVP_PKEY_free(_key);
|
||||
_key = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkKey::Generate()
|
||||
{
|
||||
if (_ctx == nullptr)
|
||||
try
|
||||
{
|
||||
log_error("Invalid OpenSSL context");
|
||||
return false;
|
||||
}
|
||||
#if KEY_TYPE == EVP_PKEY_RSA
|
||||
if (!EVP_PKEY_CTX_set_rsa_keygen_bits(_ctx, KEY_LENGTH_BITS))
|
||||
{
|
||||
log_error("Failed to set keygen params");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
#error Only RSA is supported!
|
||||
#endif
|
||||
if (EVP_PKEY_keygen_init(_ctx) <= 0)
|
||||
{
|
||||
log_error("Failed to initialise keygen algorithm");
|
||||
return false;
|
||||
}
|
||||
if (EVP_PKEY_keygen(_ctx, &_key) <= 0)
|
||||
{
|
||||
log_error("Failed to generate new key!");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_verbose("Key successfully generated");
|
||||
}
|
||||
log_verbose("New key of type %d, length %d generated successfully.", KEY_TYPE, KEY_LENGTH_BITS);
|
||||
_key = Crypt::CreateRSAKey();
|
||||
_key->Generate();
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::Generate failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkKey::LoadPrivate(IStream * stream)
|
||||
|
@ -106,34 +60,21 @@ bool NetworkKey::LoadPrivate(IStream * stream)
|
|||
log_error("Key file suspiciously large, refusing to load it");
|
||||
return false;
|
||||
}
|
||||
char * priv_key = new char[size];
|
||||
stream->Read(priv_key, size);
|
||||
BIO * bio = BIO_new_mem_buf(priv_key, (sint32)size);
|
||||
if (bio == nullptr)
|
||||
|
||||
std::string pem(size, '\0');
|
||||
stream->Read(pem.data(), pem.size());
|
||||
|
||||
try
|
||||
{
|
||||
log_error("Failed to initialise OpenSSL's BIO!");
|
||||
delete [] priv_key;
|
||||
return false;
|
||||
}
|
||||
RSA * rsa;
|
||||
rsa = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr);
|
||||
if (rsa == nullptr || !RSA_check_key(rsa))
|
||||
{
|
||||
log_error("Loaded RSA key is invalid");
|
||||
BIO_free_all(bio);
|
||||
delete [] priv_key;
|
||||
return false;
|
||||
}
|
||||
if (_key != nullptr)
|
||||
{
|
||||
EVP_PKEY_free(_key);
|
||||
}
|
||||
_key = EVP_PKEY_new();
|
||||
EVP_PKEY_set1_RSA(_key, rsa);
|
||||
BIO_free_all(bio);
|
||||
RSA_free(rsa);
|
||||
delete [] priv_key;
|
||||
_key = Crypt::CreateRSAKey();
|
||||
_key->SetPrivate(pem);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::LoadPrivate failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkKey::LoadPublic(IStream * stream)
|
||||
|
@ -151,152 +92,68 @@ bool NetworkKey::LoadPublic(IStream * stream)
|
|||
log_error("Key file suspiciously large, refusing to load it");
|
||||
return false;
|
||||
}
|
||||
char * pub_key = new char[size];
|
||||
stream->Read(pub_key, size);
|
||||
BIO * bio = BIO_new_mem_buf(pub_key, (sint32)size);
|
||||
if (bio == nullptr)
|
||||
|
||||
std::string pem(size, '\0');
|
||||
stream->Read(pem.data(), pem.size());
|
||||
|
||||
try
|
||||
{
|
||||
log_error("Failed to initialise OpenSSL's BIO!");
|
||||
delete [] pub_key;
|
||||
_key = Crypt::CreateRSAKey();
|
||||
_key->SetPublic(pem);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::LoadPublic failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
RSA * rsa;
|
||||
rsa = PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr);
|
||||
if (_key != nullptr)
|
||||
{
|
||||
EVP_PKEY_free(_key);
|
||||
}
|
||||
_key = EVP_PKEY_new();
|
||||
EVP_PKEY_set1_RSA(_key, rsa);
|
||||
BIO_free_all(bio);
|
||||
RSA_free(rsa);
|
||||
delete [] pub_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkKey::SavePrivate(IStream * stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_key == nullptr)
|
||||
{
|
||||
log_error("No key loaded");
|
||||
return false;
|
||||
throw std::runtime_error("No key loaded");
|
||||
}
|
||||
#if KEY_TYPE == EVP_PKEY_RSA
|
||||
RSA * rsa = EVP_PKEY_get1_RSA(_key);
|
||||
if (rsa == nullptr)
|
||||
{
|
||||
log_error("Failed to get RSA key handle!");
|
||||
return false;
|
||||
}
|
||||
if (!RSA_check_key(rsa))
|
||||
{
|
||||
log_error("Loaded RSA key is invalid");
|
||||
return false;
|
||||
}
|
||||
BIO * bio = BIO_new(BIO_s_mem());
|
||||
if (bio == nullptr)
|
||||
{
|
||||
log_error("Failed to initialise OpenSSL's BIO!");
|
||||
return false;
|
||||
}
|
||||
sint32 result = PEM_write_bio_RSAPrivateKey(bio, rsa, nullptr, nullptr, 0, nullptr, nullptr);
|
||||
if (result != 1)
|
||||
{
|
||||
log_error("failed to write private key!");
|
||||
BIO_free_all(bio);
|
||||
return false;
|
||||
}
|
||||
RSA_free(rsa);
|
||||
|
||||
sint32 keylen = BIO_pending(bio);
|
||||
char * pem_key = new char[keylen];
|
||||
BIO_read(bio, pem_key, keylen);
|
||||
stream->Write(pem_key, keylen);
|
||||
log_verbose("saving key of length %u", keylen);
|
||||
BIO_free_all(bio);
|
||||
delete [] pem_key;
|
||||
#else
|
||||
#error Only RSA is supported!
|
||||
#endif
|
||||
|
||||
auto pem = _key->GetPrivate();
|
||||
stream->Write(pem.data(), pem.size());
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::SavePrivate failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkKey::SavePublic(IStream * stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_key == nullptr)
|
||||
{
|
||||
log_error("No key loaded");
|
||||
return false;
|
||||
throw std::runtime_error("No key loaded");
|
||||
}
|
||||
RSA * rsa = EVP_PKEY_get1_RSA(_key);
|
||||
if (rsa == nullptr)
|
||||
{
|
||||
log_error("Failed to get RSA key handle!");
|
||||
return false;
|
||||
}
|
||||
BIO * bio = BIO_new(BIO_s_mem());
|
||||
if (bio == nullptr)
|
||||
{
|
||||
log_error("Failed to initialise OpenSSL's BIO!");
|
||||
return false;
|
||||
}
|
||||
sint32 result = PEM_write_bio_RSAPublicKey(bio, rsa);
|
||||
if (result != 1)
|
||||
{
|
||||
log_error("failed to write private key!");
|
||||
BIO_free_all(bio);
|
||||
return false;
|
||||
}
|
||||
RSA_free(rsa);
|
||||
|
||||
sint32 keylen = BIO_pending(bio);
|
||||
char * pem_key = new char[keylen];
|
||||
BIO_read(bio, pem_key, keylen);
|
||||
stream->Write(pem_key, keylen);
|
||||
BIO_free_all(bio);
|
||||
delete [] pem_key;
|
||||
|
||||
auto pem = _key->GetPrivate();
|
||||
stream->Write(pem.data(), pem.size());
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::SavePublic failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string NetworkKey::PublicKeyString()
|
||||
{
|
||||
if (_key == nullptr)
|
||||
{
|
||||
log_error("No key loaded");
|
||||
return nullptr;
|
||||
throw std::runtime_error("No key loaded");
|
||||
}
|
||||
RSA * rsa = EVP_PKEY_get1_RSA(_key);
|
||||
if (rsa == nullptr)
|
||||
{
|
||||
log_error("Failed to get RSA key handle!");
|
||||
return nullptr;
|
||||
}
|
||||
BIO * bio = BIO_new(BIO_s_mem());
|
||||
if (bio == nullptr)
|
||||
{
|
||||
log_error("Failed to initialise OpenSSL's BIO!");
|
||||
return nullptr;
|
||||
}
|
||||
sint32 result = PEM_write_bio_RSAPublicKey(bio, rsa);
|
||||
if (result != 1)
|
||||
{
|
||||
log_error("failed to write private key!");
|
||||
BIO_free_all(bio);
|
||||
return nullptr;
|
||||
}
|
||||
RSA_free(rsa);
|
||||
|
||||
sint32 keylen = BIO_pending(bio);
|
||||
char * pem_key = new char[keylen + 1];
|
||||
BIO_read(bio, pem_key, keylen);
|
||||
BIO_free_all(bio);
|
||||
pem_key[keylen] = '\0';
|
||||
std::string pem_key_out(pem_key);
|
||||
delete [] pem_key;
|
||||
|
||||
return pem_key_out;
|
||||
return _key->GetPublic();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -312,136 +169,62 @@ std::string NetworkKey::PublicKeyString()
|
|||
*/
|
||||
std::string NetworkKey::PublicKeyHash()
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string key = PublicKeyString();
|
||||
if (key.empty())
|
||||
{
|
||||
log_error("No key found");
|
||||
return nullptr;
|
||||
throw std::runtime_error("No key found");
|
||||
}
|
||||
EVP_MD_CTX * ctx = EVP_MD_CTX_create();
|
||||
if (EVP_DigestInit_ex(ctx, EVP_sha1(), nullptr) <= 0)
|
||||
{
|
||||
log_error("Failed to initialise digest context");
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
return nullptr;
|
||||
}
|
||||
if (EVP_DigestUpdate(ctx, key.c_str(), key.size()) <= 0)
|
||||
{
|
||||
log_error("Failed to update digset");
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
return nullptr;
|
||||
}
|
||||
uint32 digest_size = EVP_MAX_MD_SIZE;
|
||||
std::vector<uint8> digest(EVP_MAX_MD_SIZE);
|
||||
// Cleans up `ctx` automatically.
|
||||
EVP_DigestFinal(ctx, digest.data(), &digest_size);
|
||||
std::string digest_out;
|
||||
digest_out.reserve(EVP_MAX_MD_SIZE * 2 + 1);
|
||||
for (uint32 i = 0; i < digest_size; i++)
|
||||
auto hash = Crypt::SHA1(key.c_str(), key.size());
|
||||
|
||||
std::string result;
|
||||
result.reserve(hash.size() * 2);
|
||||
for (auto b : hash)
|
||||
{
|
||||
char buf[3];
|
||||
snprintf(buf, 3, "%02x", digest[i]);
|
||||
digest_out.append(buf);
|
||||
snprintf(buf, 3, "%02x", b);
|
||||
result.append(buf);
|
||||
}
|
||||
return digest_out;
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("Failed to create hash of public key: %s", e.what());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NetworkKey::Sign(const uint8 * md, const size_t len, char ** signature, size_t * out_size)
|
||||
{
|
||||
EVP_MD_CTX * mdctx = nullptr;
|
||||
|
||||
*signature = nullptr;
|
||||
|
||||
/* Create the Message Digest Context */
|
||||
if ((mdctx = EVP_MD_CTX_create()) == nullptr)
|
||||
try
|
||||
{
|
||||
log_error("Failed to create MD context");
|
||||
return false;
|
||||
}
|
||||
/* Initialise the DigestSign operation - SHA-256 has been selected as the message digest function in this example */
|
||||
if (1 != EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, _key))
|
||||
{
|
||||
log_error("Failed to init digest sign");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
/* Call update with the message */
|
||||
if (1 != EVP_DigestSignUpdate(mdctx, md, len))
|
||||
{
|
||||
log_error("Failed to goto update digest");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Finalise the DigestSign operation */
|
||||
/* First call EVP_DigestSignFinal with a nullptr sig parameter to obtain the length of the
|
||||
* signature. Length is returned in slen */
|
||||
if (1 != EVP_DigestSignFinal(mdctx, nullptr, out_size))
|
||||
{
|
||||
log_error("failed to finalise signature");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 * sig;
|
||||
/* Allocate memory for the signature based on size in slen */
|
||||
if ((sig = (unsigned char*)malloc((sint32)(sizeof(unsigned char) * (*out_size)))) == nullptr)
|
||||
{
|
||||
log_error("Failed to crypto-allocate space for signature");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
/* Obtain the signature */
|
||||
if (1 != EVP_DigestSignFinal(mdctx, sig, out_size)) {
|
||||
log_error("Failed to finalise signature");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
free(sig);
|
||||
return false;
|
||||
}
|
||||
*signature = new char[*out_size];
|
||||
memcpy(*signature, sig, *out_size);
|
||||
free(sig);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
auto rsa = Crypt::CreateRSA();
|
||||
auto sig = rsa->SignData(*_key, md, len);
|
||||
*out_size = sig.size();
|
||||
*signature = new char[sig.size()];
|
||||
std::memcpy(*signature, sig.data(), sig.size());
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("NetworkKey::Sign failed: %s", e.what());
|
||||
*signature = nullptr;
|
||||
*out_size = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkKey::Verify(const uint8 * md, const size_t len, const char * sig, const size_t siglen)
|
||||
{
|
||||
EVP_MD_CTX * mdctx = nullptr;
|
||||
|
||||
/* Create the Message Digest Context */
|
||||
if ((mdctx = EVP_MD_CTX_create()) == nullptr)
|
||||
try
|
||||
{
|
||||
log_error("Failed to create MD context");
|
||||
return false;
|
||||
auto rsa = Crypt::CreateRSA();
|
||||
return rsa->VerifyData(*_key, md, len, sig, siglen);
|
||||
}
|
||||
|
||||
if (1 != EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, _key))
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_error("Failed to initialise verification routine");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Initialize `key` with a public key */
|
||||
if (1 != EVP_DigestVerifyUpdate(mdctx, md, len))
|
||||
{
|
||||
log_error("Failed to update verification");
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (1 == EVP_DigestVerifyFinal(mdctx, (uint8 *)sig, siglen))
|
||||
{
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
log_verbose("Successfully verified signature");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
log_error("Signature is invalid");
|
||||
log_error("NetworkKey::Verify failed: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,16 @@
|
|||
#ifndef DISABLE_NETWORK
|
||||
|
||||
#include "../common.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
using EVP_PKEY = evp_pkey_st;
|
||||
using EVP_PKEY_CTX = evp_pkey_ctx_st;
|
||||
|
||||
interface IStream;
|
||||
|
||||
namespace Crypt
|
||||
{
|
||||
class RsaKey;
|
||||
}
|
||||
|
||||
class NetworkKey final
|
||||
{
|
||||
public:
|
||||
|
@ -45,8 +47,7 @@ public:
|
|||
bool Verify(const uint8 * md, const size_t len, const char * sig, const size_t siglen);
|
||||
private:
|
||||
NetworkKey (const NetworkKey &) = delete;
|
||||
EVP_PKEY_CTX * _ctx = nullptr;
|
||||
EVP_PKEY * _key = nullptr;
|
||||
std::unique_ptr<Crypt::RsaKey> _key;
|
||||
};
|
||||
|
||||
#endif // DISABLE_NETWORK
|
||||
|
|
|
@ -64,7 +64,6 @@ namespace OpenRCT2
|
|||
#include <functional>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <openssl/evp.h>
|
||||
#include "../actions/GameAction.h"
|
||||
#include "../core/Json.hpp"
|
||||
#include "../core/Nullable.hpp"
|
||||
|
@ -242,7 +241,7 @@ private:
|
|||
uint32 server_tick = 0;
|
||||
uint32 server_srand0 = 0;
|
||||
uint32 server_srand0_tick = 0;
|
||||
char server_sprite_hash[EVP_MAX_MD_SIZE + 1]{};
|
||||
std::string server_sprite_hash;
|
||||
uint8 player_id = 0;
|
||||
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
||||
std::multiset<GameCommand> game_command_queue;
|
||||
|
|
|
@ -197,15 +197,10 @@ namespace Platform
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current version of Windows supports ANSI colour codes.
|
||||
* From Windows 10, build 10586 ANSI escape colour codes can be used on stdout.
|
||||
*/
|
||||
static bool HasANSIColourSupport()
|
||||
bool IsOSVersionAtLeast(uint32 major, uint32 minor, uint32 build)
|
||||
{
|
||||
const DWORD MINV_MAJOR = 10, MINV_MINOR = 0, MINV_BUILD = 10586;
|
||||
bool result = false;
|
||||
HMODULE hModule = GetModuleHandleA("ntdll.dll");
|
||||
auto hModule = GetModuleHandleA("ntdll.dll");
|
||||
if (hModule != nullptr)
|
||||
{
|
||||
using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW);
|
||||
|
@ -216,11 +211,11 @@ namespace Platform
|
|||
rovi.dwOSVersionInfoSize = sizeof(rovi);
|
||||
if (fn(&rovi) == 0)
|
||||
{
|
||||
if (rovi.dwMajorVersion > MINV_MAJOR ||
|
||||
(rovi.dwMajorVersion == MINV_MAJOR &&
|
||||
(rovi.dwMinorVersion > MINV_MINOR ||
|
||||
(rovi.dwMinorVersion == MINV_MINOR &&
|
||||
rovi.dwBuildNumber >= MINV_BUILD))))
|
||||
if (rovi.dwMajorVersion > major ||
|
||||
(rovi.dwMajorVersion == major &&
|
||||
(rovi.dwMinorVersion > minor ||
|
||||
(rovi.dwMinorVersion == minor &&
|
||||
rovi.dwBuildNumber >= build))))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
@ -230,6 +225,15 @@ namespace Platform
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current version of Windows supports ANSI colour codes.
|
||||
* From Windows 10, build 10586 ANSI escape colour codes can be used on stdout.
|
||||
*/
|
||||
static bool HasANSIColourSupport()
|
||||
{
|
||||
return IsOSVersionAtLeast(10, 0, 10586);
|
||||
}
|
||||
|
||||
static void EnableANSIConsole()
|
||||
{
|
||||
if (HasANSIColourSupport())
|
||||
|
|
|
@ -45,5 +45,9 @@ namespace Platform
|
|||
std::string FormatShortDate(std::time_t timestamp);
|
||||
std::string FormatTime(std::time_t timestamp);
|
||||
|
||||
#ifdef _WIN32
|
||||
bool IsOSVersionAtLeast(uint32 major, uint32 minor, uint32 build);
|
||||
#endif
|
||||
|
||||
bool IsColourTerminalSupported();
|
||||
} // namespace Platform
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <cmath>
|
||||
#include "../audio/audio.h"
|
||||
#include "../Cheats.h"
|
||||
#include "../core/Crypt.h"
|
||||
#include "../core/Guard.hpp"
|
||||
#include "../core/Math.hpp"
|
||||
#include "../core/Util.hpp"
|
||||
|
@ -208,47 +209,58 @@ static size_t GetSpatialIndexOffset(sint32 x, sint32 y)
|
|||
|
||||
#ifndef DISABLE_NETWORK
|
||||
|
||||
static uint8 _spriteChecksum[EVP_MAX_MD_SIZE + 1];
|
||||
|
||||
const char * sprite_checksum()
|
||||
{
|
||||
if (EVP_DigestInit_ex(gHashCTX, EVP_sha1(), NULL) <= 0)
|
||||
using namespace Crypt;
|
||||
|
||||
// TODO Remove statics, should be one of these per sprite manager / OpenRCT2 context.
|
||||
// Alternatively, make a new class for this functionality.
|
||||
static std::unique_ptr<HashAlgorithm<20>> _spriteHashAlg;
|
||||
static std::string result;
|
||||
|
||||
try
|
||||
{
|
||||
openrct2_assert(false, "Failed to initialise SHA1 engine");
|
||||
if (_spriteHashAlg == nullptr)
|
||||
{
|
||||
_spriteHashAlg = CreateSHA1();
|
||||
}
|
||||
|
||||
_spriteHashAlg->Clear();
|
||||
for (size_t i = 0; i < MAX_SPRITES; i++)
|
||||
{
|
||||
rct_sprite *sprite = get_sprite(i);
|
||||
auto sprite = get_sprite(i);
|
||||
if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL && sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_MISC)
|
||||
{
|
||||
rct_sprite copy = *sprite;
|
||||
auto copy = *sprite;
|
||||
copy.unknown.sprite_left = copy.unknown.sprite_right = copy.unknown.sprite_top = copy.unknown.sprite_bottom = 0;
|
||||
|
||||
if (copy.unknown.sprite_identifier == SPRITE_IDENTIFIER_PEEP) {
|
||||
if (copy.unknown.sprite_identifier == SPRITE_IDENTIFIER_PEEP)
|
||||
{
|
||||
// We set this to 0 because as soon the client selects a guest the window will remove the
|
||||
// invalidation flags causing the sprite checksum to be different than on server, the flag does not affect game state.
|
||||
copy.peep.window_invalidate_flags = 0;
|
||||
}
|
||||
|
||||
if (EVP_DigestUpdate(gHashCTX, ©, sizeof(rct_sprite)) <= 0)
|
||||
_spriteHashAlg->Update(©, sizeof(copy));
|
||||
}
|
||||
}
|
||||
|
||||
auto hash = _spriteHashAlg->Finish();
|
||||
|
||||
result.reserve(hash.size() * 2);
|
||||
for (auto b : hash)
|
||||
{
|
||||
openrct2_assert(false, "Failed to update digest");
|
||||
char buf[3];
|
||||
snprintf(buf, 3, "%02x", b);
|
||||
result.append(buf);
|
||||
}
|
||||
return result.c_str();
|
||||
}
|
||||
}
|
||||
uint8 localhash[EVP_MAX_MD_SIZE + 1];
|
||||
uint32 size = sizeof(localhash);
|
||||
EVP_DigestFinal(gHashCTX, localhash, &size);
|
||||
assert(size <= sizeof(localhash));
|
||||
localhash[sizeof(localhash) - 1] = '\0';
|
||||
char *x = (char *)_spriteChecksum;
|
||||
for (uint32 i = 0; i < size; i++)
|
||||
catch (std::exception& e)
|
||||
{
|
||||
snprintf(x, EVP_MAX_MD_SIZE + 1, "%02x", localhash[i]);
|
||||
x += 2;
|
||||
log_error("sprite_checksum failed: %s", e.what());
|
||||
throw;
|
||||
}
|
||||
*x = '\0';
|
||||
return (char *)_spriteChecksum;
|
||||
}
|
||||
#else
|
||||
|
||||
|
|
|
@ -153,6 +153,14 @@ add_executable(test_localisation ${STRING_TEST_SOURCES})
|
|||
target_link_libraries(test_localisation ${GTEST_LIBRARIES} test-common ${LDL} z)
|
||||
add_test(NAME localisation COMMAND test_localisation)
|
||||
|
||||
if (NOT DISABLE_NETWORK)
|
||||
# Crypt tests
|
||||
add_executable(test_crypt "${CMAKE_CURRENT_LIST_DIR}/CryptTests.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/TestData.cpp")
|
||||
target_link_libraries(test_crypt ${GTEST_LIBRARIES} libopenrct2)
|
||||
add_test(NAME Crypt COMMAND test_crypt)
|
||||
endif ()
|
||||
|
||||
# ImageImporter tests
|
||||
add_executable(test_imageimporter "${CMAKE_CURRENT_LIST_DIR}/ImageImporterTests.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/TestData.cpp")
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
#include <string>
|
||||
#include <openrct2/core/Crypt.h>
|
||||
#include <openrct2/core/File.h>
|
||||
#include <openrct2/core/Path.hpp>
|
||||
#include <openrct2/network/NetworkKey.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "TestData.h"
|
||||
|
||||
class CryptTests : public testing::Test
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
void AssertHash(std::string expected, T hash)
|
||||
{
|
||||
auto actual = StringToHex(hash);
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string StringToHex(T input)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(input.size() * 2);
|
||||
for (auto b : input)
|
||||
{
|
||||
static_assert(sizeof(b) == 1);
|
||||
|
||||
char buf[3];
|
||||
snprintf(buf, 3, "%02x", b);
|
||||
result.append(buf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetTestPrivateKeyPath()
|
||||
{
|
||||
return Path::Combine(TestData::GetBasePath(), "keys", "Player.privkey");
|
||||
}
|
||||
|
||||
std::string GetTestPublicKeyPath()
|
||||
{
|
||||
return Path::Combine(TestData::GetBasePath(), "keys", "Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CryptTests, SHA1_Basic)
|
||||
{
|
||||
std::string input = "The quick brown fox jumped over the lazy dog.";
|
||||
auto result = Crypt::SHA1(input.data(), input.size());
|
||||
AssertHash("c0854fb9fb03c41cce3802cb0d220529e6eef94e", result);
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, SHA1_Multiple)
|
||||
{
|
||||
std::string input[] = {
|
||||
"Merry-go-round 2 looks too intense for me",
|
||||
"This park is really clean and tidy",
|
||||
"This balloon from Balloon Stall 1 is really good value"
|
||||
};
|
||||
|
||||
auto alg = Crypt::CreateSHA1();
|
||||
for (auto s : input)
|
||||
{
|
||||
alg->Update(s.data(), s.size());
|
||||
}
|
||||
auto result = alg->Finish();
|
||||
|
||||
AssertHash("758a238d9a4748f80cc81f12be3885d5e45d34c2", result);
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, SHA1_WithClear)
|
||||
{
|
||||
std::string inputA = "Merry-go-round 2 looks too intense for me";
|
||||
std::string inputB = "This park is really clean and tidy";
|
||||
|
||||
auto alg = Crypt::CreateSHA1();
|
||||
alg->Update(inputA.data(), inputA.size());
|
||||
alg->Clear();
|
||||
alg->Update(inputB.data(), inputB.size());
|
||||
AssertHash("203986d57ebdbcdc8a45b7ee74c665960fd7ff48", alg->Finish());
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, SHA1_Many)
|
||||
{
|
||||
auto alg = Crypt::CreateSHA1();
|
||||
|
||||
// First digest
|
||||
std::string inputA[] = {
|
||||
"Merry-go-round 2 looks too intense for me",
|
||||
"This park is really clean and tidy",
|
||||
"This balloon from Balloon Stall 1 is really good value"
|
||||
};
|
||||
for (auto s : inputA)
|
||||
{
|
||||
alg->Update(s.data(), s.size());
|
||||
}
|
||||
AssertHash("758a238d9a4748f80cc81f12be3885d5e45d34c2", alg->Finish());
|
||||
|
||||
// No need to clear after a finish
|
||||
|
||||
// Second digest
|
||||
std::string inputB[] = {
|
||||
"This balloon from Balloon Stall 1 is really good value"
|
||||
"This park is really clean and tidy",
|
||||
"Merry-go-round 2 looks too intense for me",
|
||||
};
|
||||
for (auto s : inputB)
|
||||
{
|
||||
alg->Update(s.data(), s.size());
|
||||
}
|
||||
AssertHash("ac46948f97d69fa766706e932ce82562b4f73aa7", alg->Finish());
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, RSA_Basic)
|
||||
{
|
||||
std::vector<uint8> data = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
auto file = File::ReadAllText(GetTestPrivateKeyPath());
|
||||
auto key = Crypt::CreateRSAKey();
|
||||
key->SetPrivate(std::string_view(file.data(), file.size()));
|
||||
|
||||
auto rsa = Crypt::CreateRSA();
|
||||
auto signature = rsa->SignData(*key, data.data(), data.size());
|
||||
bool verified = rsa->VerifyData(*key, data.data(), data.size(), signature.data(), signature.size());
|
||||
ASSERT_TRUE(verified);
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, RSA_VerifyWithPublic)
|
||||
{
|
||||
std::vector<uint8> data = { 7, 6, 5, 4, 3, 2, 1, 0 };
|
||||
|
||||
auto privateFile = File::ReadAllText(GetTestPrivateKeyPath());
|
||||
auto privateKey = Crypt::CreateRSAKey();
|
||||
privateKey->SetPrivate(std::string_view(privateFile.data(), privateFile.size()));
|
||||
|
||||
auto publicFile = File::ReadAllText(GetTestPublicKeyPath());
|
||||
auto publicKey = Crypt::CreateRSAKey();
|
||||
publicKey->SetPublic(std::string_view(publicFile.data(), publicFile.size()));
|
||||
|
||||
auto rsa = Crypt::CreateRSA();
|
||||
auto signature = rsa->SignData(*privateKey, data.data(), data.size());
|
||||
bool verified = rsa->VerifyData(*publicKey, data.data(), data.size(), signature.data(), signature.size());
|
||||
ASSERT_TRUE(verified);
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, RSAKey_GetPublic)
|
||||
{
|
||||
auto inPem = File::ReadAllText(GetTestPublicKeyPath());
|
||||
auto publicKey = Crypt::CreateRSAKey();
|
||||
publicKey->SetPublic(inPem);
|
||||
auto outPem = publicKey->GetPublic();
|
||||
ASSERT_EQ(inPem, outPem);
|
||||
}
|
||||
|
||||
TEST_F(CryptTests, RSAKey_Generate)
|
||||
{
|
||||
auto key = Crypt::CreateRSAKey();
|
||||
|
||||
// Test generate twice, first checking if the PEMs contain expected strings
|
||||
key->Generate();
|
||||
auto privatePem1 = key->GetPrivate();
|
||||
auto publicPem1 = key->GetPublic();
|
||||
ASSERT_NE(privatePem1.find("RSA PRIVATE KEY"), std::string::npos);
|
||||
ASSERT_NE(publicPem1.find("RSA PUBLIC KEY"), std::string::npos);
|
||||
|
||||
key->Generate();
|
||||
auto privatePem2 = key->GetPrivate();
|
||||
auto publicPem2 = key->GetPublic();
|
||||
ASSERT_NE(privatePem2.find("RSA PRIVATE KEY"), std::string::npos);
|
||||
ASSERT_NE(publicPem2.find("RSA PUBLIC KEY"), std::string::npos);
|
||||
|
||||
// Now check that generate gives a different key each time
|
||||
ASSERT_STRNE(privatePem1.c_str(), privatePem2.c_str());
|
||||
ASSERT_STRNE(publicPem1.c_str(), publicPem2.c_str());
|
||||
}
|
5
test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey
vendored
Normal file
5
test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBAORGPJHpScRUYrjSu8Y5SCQW1UOefRUgQLDemcYD/DrMFBWnLYTMQmyW
|
||||
QaJSt5zlacQucbfDV+tdxbQBO9eE1S+wxRVnSJpa40R9Ye7YTRsGUhwRyB0MwRBx
|
||||
sxXKksWNDjsh3UujqW+Tq2Hhz4ohRr3K5fEkMS8Cgzs2TmiNgj1zAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDkRjyR6UnEVGK40rvGOUgkFtVDnn0VIECw3pnGA/w6zBQVpy2E
|
||||
zEJslkGiUrec5WnELnG3w1frXcW0ATvXhNUvsMUVZ0iaWuNEfWHu2E0bBlIcEcgd
|
||||
DMEQcbMVypLFjQ47Id1Lo6lvk6th4c+KIUa9yuXxJDEvAoM7Nk5ojYI9cwIDAQAB
|
||||
AoGBAN6Ivil8ZGZZ4XfOMDH4y6QrAjKOQeAjdg02pHAOmIh1RKsrM8u/GI4lGMz2
|
||||
mHsChs4yfLepXn9cBg0KGt1qaCvjaNGDmZL8uLJiyhE0cB8eObUFZkWzsJlBdKRV
|
||||
4aTXiWLZGQjDDLCdCz57MjSNspyuq4rsonKfTHlvvFHrN6J5AkEA9NSd05LnR90Y
|
||||
kF1DtUyvhBF3ubCLMYbwjLQWhuI+kFDe1aoijaYye9x8ol/ZO17xYr5IJ+uFO1U+
|
||||
or5MVqma5wJBAO6wQpJGYU2lpMwr5H6C0/HNfDnh65idQ8bKx4aQzSb41pFaLT2o
|
||||
2/cyOcXLaTz4yEtqiq4fgxJp6mZp4ZxGo5UCQQCMnDvUkk9AR6ve5aGIU3WOLRYM
|
||||
0Gbw1+X5eUhiyTdTXQ7NubvEjIn79wKhoti5L2sE4fHA85P+IpQplY90Sk5LAkBw
|
||||
FIhOE4phYaTO0tWKqnhHlQv+Sh3NHhvTXyjuAVS0NijbPBL+XypbG1SRkoCqRtAW
|
||||
ycKxHM75eI6+5H0yWuE5AkAmZutrDHoZn64E2uUU3aRENxu/1uddepKSlWkzYj7N
|
||||
sSuv6a9j5p89HAuE5tamnt83SnLh0s0s1L3zAciY8fqO
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -56,6 +56,7 @@
|
|||
<ClInclude Include="TestData.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CryptTests.cpp" />
|
||||
<ClCompile Include="LanguagePackTest.cpp" />
|
||||
<ClCompile Include="ImageImporterTests.cpp" />
|
||||
<ClCompile Include="IniReaderTest.cpp" />
|
||||
|
|
Loading…
Reference in New Issue