Merge pull request #7609 from IntelOrca/refactor/openssl-usage

Refactor OpenSSL usage
This commit is contained in:
Ted John 2018-06-02 22:22:04 +01:00 committed by GitHub
commit 3b8c0703f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 858 additions and 420 deletions

View File

@ -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 */,

View File

@ -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))

View File

@ -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;

View File

@ -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];

View File

@ -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

79
src/openrct2/core/Crypt.h Normal file
View File

@ -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();
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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)
{
}
};

View File

@ -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.";

View File

@ -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);
}
}
}

View File

@ -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;
}
_key = nullptr;
}
bool NetworkKey::Generate()
{
if (_ctx == nullptr)
try
{
log_error("Invalid OpenSSL context");
_key = Crypt::CreateRSAKey();
_key->Generate();
return true;
}
catch (const std::exception& e)
{
log_error("NetworkKey::Generate failed: %s", e.what());
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);
return true;
}
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;
_key = Crypt::CreateRSAKey();
_key->SetPrivate(pem);
return true;
}
catch (const std::exception& e)
{
log_error("NetworkKey::LoadPrivate failed: %s", e.what());
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;
return true;
}
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)
{
if (_key == nullptr)
try
{
log_error("No key loaded");
if (_key == nullptr)
{
throw std::runtime_error("No key loaded");
}
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;
}
#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
return true;
}
bool NetworkKey::SavePublic(IStream * stream)
{
if (_key == nullptr)
try
{
log_error("No key loaded");
if (_key == nullptr)
{
throw std::runtime_error("No key loaded");
}
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;
}
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;
return true;
}
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()
{
std::string key = PublicKeyString();
if (key.empty())
try
{
log_error("No key found");
return nullptr;
std::string key = PublicKeyString();
if (key.empty())
{
throw std::runtime_error("No key found");
}
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", b);
result.append(buf);
}
return result;
}
EVP_MD_CTX * ctx = EVP_MD_CTX_create();
if (EVP_DigestInit_ex(ctx, EVP_sha1(), nullptr) <= 0)
catch (const std::exception& e)
{
log_error("Failed to initialise digest context");
EVP_MD_CTX_destroy(ctx);
return nullptr;
log_error("Failed to create hash of public key: %s", e.what());
}
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++)
{
char buf[3];
snprintf(buf, 3, "%02x", digest[i]);
digest_out.append(buf);
}
return digest_out;
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;
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;
}
/* 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))
catch (const std::exception& e)
{
log_error("Failed to init digest sign");
EVP_MD_CTX_destroy(mdctx);
log_error("NetworkKey::Sign failed: %s", e.what());
*signature = nullptr;
*out_size = 0;
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);
return true;
}
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;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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())

View File

@ -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

View File

@ -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");
}
for (size_t i = 0; i < MAX_SPRITES; i++)
{
rct_sprite *sprite = get_sprite(i);
if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL && sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_MISC)
if (_spriteHashAlg == nullptr)
{
rct_sprite copy = *sprite;
copy.unknown.sprite_left = copy.unknown.sprite_right = copy.unknown.sprite_top = copy.unknown.sprite_bottom = 0;
_spriteHashAlg = CreateSHA1();
}
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, &copy, sizeof(rct_sprite)) <= 0)
_spriteHashAlg->Clear();
for (size_t i = 0; i < MAX_SPRITES; i++)
{
auto sprite = get_sprite(i);
if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL && sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_MISC)
{
openrct2_assert(false, "Failed to update digest");
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)
{
// 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;
}
_spriteHashAlg->Update(&copy, sizeof(copy));
}
}
auto hash = _spriteHashAlg->Finish();
result.reserve(hash.size() * 2);
for (auto b : hash)
{
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

View File

@ -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")

175
test/tests/CryptTests.cpp Normal file
View File

@ -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());
}

View File

@ -0,0 +1,5 @@
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAORGPJHpScRUYrjSu8Y5SCQW1UOefRUgQLDemcYD/DrMFBWnLYTMQmyW
QaJSt5zlacQucbfDV+tdxbQBO9eE1S+wxRVnSJpa40R9Ye7YTRsGUhwRyB0MwRBx
sxXKksWNDjsh3UujqW+Tq2Hhz4ohRr3K5fEkMS8Cgzs2TmiNgj1zAgMBAAE=
-----END RSA PUBLIC KEY-----

15
test/tests/testdata/keys/Player.privkey vendored Normal file
View File

@ -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-----

View File

@ -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" />