diff --git a/.clang-format b/.clang-format index eea3a0b..927f9a0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,4 @@ - ---- +--- Language: Cpp BasedOnStyle: LLVM DerivePointerAlignment: false @@ -10,3 +9,5 @@ SortIncludes: false IncludeBlocks: Preserve --- +Language: Proto +BasedOnStyle: Google diff --git a/.gitmodules b/.gitmodules index 265ded9..060dcee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,13 @@ [submodule "deps/GSL"] path = deps/GSL url = https://github.com/microsoft/GSL.git +[submodule "deps/libtomcrypt"] + path = deps/libtomcrypt + url = https://github.com/libtom/libtomcrypt.git +[submodule "deps/libtommath"] + path = deps/libtommath + url = https://github.com/libtom/libtommath.git +[submodule "deps/protobuf"] + path = deps/protobuf + url = https://github.com/google/protobuf.git + branch = 3.20.x diff --git a/deps/libtomcrypt b/deps/libtomcrypt new file mode 160000 index 0000000..ddfe2e8 --- /dev/null +++ b/deps/libtomcrypt @@ -0,0 +1 @@ +Subproject commit ddfe2e8aa7c4239463a8a1d26724aef123333549 diff --git a/deps/libtommath b/deps/libtommath new file mode 160000 index 0000000..4b47368 --- /dev/null +++ b/deps/libtommath @@ -0,0 +1 @@ +Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940 diff --git a/deps/premake/libtomcrypt.lua b/deps/premake/libtomcrypt.lua new file mode 100644 index 0000000..6c6f28d --- /dev/null +++ b/deps/premake/libtomcrypt.lua @@ -0,0 +1,61 @@ +libtomcrypt = { + source = path.join(dependencies.basePath, "libtomcrypt"), +} + +function libtomcrypt.import() + links { + "libtomcrypt" + } + + libtomcrypt.includes() +end + +function libtomcrypt.includes() + includedirs { + path.join(libtomcrypt.source, "src/headers") + } + + defines { + "LTC_NO_FAST", + "LTC_NO_PROTOTYPES", + "LTC_NO_RSA_BLINDING", + } +end + +function libtomcrypt.project() + project "libtomcrypt" + language "C" + + libtomcrypt.includes() + libtommath.import() + + files { + path.join(libtomcrypt.source, "src/**.c"), + } + + removefiles { + path.join(libtomcrypt.source, "src/**/*tab.c"), + path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"), + } + + defines { + "_CRT_SECURE_NO_WARNINGS", + "LTC_SOURCE", + "_LIB", + "USE_LTM" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtomcrypt) diff --git a/deps/premake/libtommath.lua b/deps/premake/libtommath.lua new file mode 100644 index 0000000..ab4cdde --- /dev/null +++ b/deps/premake/libtommath.lua @@ -0,0 +1,52 @@ +libtommath = { + source = path.join(dependencies.basePath, "libtommath"), +} + +function libtommath.import() + links { + "libtommath" + } + + libtommath.includes() +end + +function libtommath.includes() + includedirs { + libtommath.source + } + + defines { + "LTM_DESC", + "__STDC_IEC_559__", + "MP_NO_DEV_URANDOM", + } +end + +function libtommath.project() + project "libtommath" + language "C" + + libtommath.includes() + + files { + path.join(libtommath.source, "*.c"), + } + + defines { + "_LIB" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtommath) diff --git a/deps/premake/protobuf.lua b/deps/premake/protobuf.lua new file mode 100644 index 0000000..1c58972 --- /dev/null +++ b/deps/premake/protobuf.lua @@ -0,0 +1,60 @@ +protobuf = { + source = path.join(dependencies.basePath, "protobuf"), +} + +function protobuf.import() + links { + "protobuf" + } + + protobuf.includes() +end + +function protobuf.includes() + includedirs { + path.join(protobuf.source, "src"), + } +end + +function protobuf.project() + project "protobuf" + language "C++" + + protobuf.includes() + + files { + path.join(protobuf.source, "src/**.cc"), + "./src/**.proto", + } + + removefiles { + path.join(protobuf.source, "src/**/*test.cc"), + path.join(protobuf.source, "src/google/protobuf/*test*.cc"), + + path.join(protobuf.source, "src/google/protobuf/testing/**.cc"), + path.join(protobuf.source, "src/google/protobuf/compiler/**.cc"), + + path.join(protobuf.source, "src/google/protobuf/arena_nc.cc"), + path.join(protobuf.source, "src/google/protobuf/util/internal/error_listener.cc"), + path.join(protobuf.source, "**/*_gcc.cc"), + } + + rules { + "ProtobufCompiler" + } + + defines { + "_SCL_SECURE_NO_WARNINGS", + "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS", + "_SILENCE_ALL_CXX20_DEPRECATION_WARNINGS", + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, protobuf) diff --git a/deps/protobuf b/deps/protobuf new file mode 160000 index 0000000..50bdb17 --- /dev/null +++ b/deps/protobuf @@ -0,0 +1 @@ +Subproject commit 50bdb17409d4133d51ab6cfa095700f4c816576e diff --git a/premake5.lua b/premake5.lua index 03e8047..69287ee 100644 --- a/premake5.lua +++ b/premake5.lua @@ -31,6 +31,7 @@ end dependencies.load() workspace "mw3-server-freezer" +startproject "client" location "./build" objdir "%{wks.location}/obj" targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" @@ -104,3 +105,13 @@ dependencies.imports() group "Dependencies" dependencies.projects() + +rule "ProtobufCompiler" +display "Protobuf compiler" +location "./build" +fileExtension ".proto" +buildmessage "Compiling %(Identity) with protoc..." +buildcommands {'@echo off', 'path "$(SolutionDir)\\..\\tools"', + 'if not exist "$(ProjectDir)\\src\\proto" mkdir "$(ProjectDir)\\src\\proto"', + 'protoc --error_format=msvs -I=%(RelativeDir) --cpp_out=src\\proto %(Identity)'} +buildoutputs {'$(ProjectDir)\\src\\proto\\%(Filename).pb.cc', '$(ProjectDir)\\src\\proto\\%(Filename).pb.h'} diff --git a/src/client/component/crypto_key.cpp b/src/client/component/crypto_key.cpp new file mode 100644 index 0000000..ea92294 --- /dev/null +++ b/src/client/component/crypto_key.cpp @@ -0,0 +1,62 @@ +#include + +#include "crypto_key.hpp" +#include "console.hpp" + +#include + +namespace crypto_key { +namespace { +bool load_key(utils::cryptography::ecc::key& key) { + std::string data{}; + if (!utils::io::read_file("./private.key", &data)) { + return false; + } + + key.deserialize(data); + if (!key.is_valid()) { + console::info("Loaded key is invalid!"); + return false; + } + + return true; +} + +utils::cryptography::ecc::key generate_key() { + auto key = utils::cryptography::ecc::generate_key(512); + if (!key.is_valid()) { + throw std::runtime_error("Failed to generate server key!"); + } + + if (!utils::io::write_file("./private.key", key.serialize())) { + throw std::runtime_error("Failed to write server key!"); + } + + console::info("Generated cryptographic key: {}", key.get_hash()); + return key; +} + +utils::cryptography::ecc::key load_or_generate_key() { + utils::cryptography::ecc::key key{}; + if (load_key(key)) { + console::info("Loaded cryptographic key: {}", key.get_hash()); + return key; + } + + return generate_key(); +} + +utils::cryptography::ecc::key get_key_internal() { + auto key = load_or_generate_key(); + if (!utils::io::write_file("./public.key", key.get_public_key())) { + console::info("Failed to write public key!"); + } + return key; +} +} // namespace + +const utils::cryptography::ecc::key& get() { + static auto key = get_key_internal(); + return key; +} +} // namespace crypto_key diff --git a/src/client/component/crypto_key.hpp b/src/client/component/crypto_key.hpp new file mode 100644 index 0000000..65c54c8 --- /dev/null +++ b/src/client/component/crypto_key.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include "utils/cryptography.hpp" + +namespace crypto_key { +const utils::cryptography::ecc::key& get(); +} diff --git a/src/client/component/network.hpp b/src/client/component/network.hpp deleted file mode 100644 index b5e3fe2..0000000 --- a/src/client/component/network.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -namespace network { -using callback = - std::function; - -void on_packet(const std::string& command, const callback& callback); -} // namespace network diff --git a/src/client/component/network.cpp b/src/client/component/network/network.cpp similarity index 50% rename from src/client/component/network.cpp rename to src/client/component/network/network.cpp index 6d68ced..cc65262 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network/network.cpp @@ -1,15 +1,17 @@ #include -#include "../loader/component_loader.hpp" +#include "../../loader/component_loader.hpp" #include #include +#include "../console.hpp" + #include "network.hpp" namespace network { namespace { -std::unordered_map& get_callbacks() { - static std::unordered_map network_callbacks{}; +std::unordered_map& get_callbacks() { + static std::unordered_map network_callbacks{}; return network_callbacks; } @@ -32,7 +34,6 @@ bool handle_command(game::netadr_s* address, const char* command, handler->second(*address, data); return true; } -} // namespace int packet_interception_handler(game::netadr_s* from, const char* command, game::msg_t* message) { @@ -42,12 +43,61 @@ int packet_interception_handler(game::netadr_s* from, const char* command, return TRUE; } +} // namespace + +void send(const game::netadr_s& address, const std::string& command, + const std::string& data, const char separator) { + std::string packet = "\xFF\xFF\xFF\xFF"; + packet.append(command); + packet.push_back(separator); + packet.append(data); + + send_data(address, packet); +} + +void send_data(const game::netadr_s& address, const std::string& data) { + auto size = static_cast(data.size()); + + if (address.type == game::NA_LOOPBACK) { + // TODO: Fix this for loopback + if (size > 1280) { + console::info("Packet was too long. Truncated!\n"); + size = 1280; + } + + game::NET_SendLoopPacket(game::NS_CLIENT1, size, data.data(), address); + } else { + game::Sys_SendPacket(size, data.data(), address); + } +} void on_packet(const std::string& command, const callback& callback) { get_callbacks()[utils::string::to_lower(command)] = callback; } -class component final : public component_interface { +const char* net_adr_to_string(const game::netadr_s& a) { + if (a.type == game::netadrtype_t::NA_LOOPBACK) { + return "loopback"; + } + + if (a.type == game::netadrtype_t::NA_BOT) { + return "bot"; + } + + if (a.type == game::netadrtype_t::NA_IP || + a.type == game::netadrtype_t::NA_BROADCAST) { + if (a.port) { + return utils::string::va("%u.%u.%u.%u:%u", a.ip[0], a.ip[1], a.ip[2], + a.ip[3], htons(a.port)); + } + + return utils::string::va("%u.%u.%u.%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3]); + } + + return "bad"; +} + +class network final : public component_interface { public: void post_unpack() override { add_network_commands(); @@ -67,4 +117,4 @@ private: }; } // namespace network -REGISTER_COMPONENT(network::component) +REGISTER_COMPONENT(network::network) diff --git a/src/client/component/network/network.hpp b/src/client/component/network/network.hpp new file mode 100644 index 0000000..dee8fd2 --- /dev/null +++ b/src/client/component/network/network.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace network { +void send(const game::netadr_s& address, const std::string& command, + const std::string& data = {}, char separator = ' '); +void send_data(const game::netadr_s& address, const std::string& data); + +using callback = + std::function; +void on_packet(const std::string& command, const callback& callback); + +const char* net_adr_to_string(const game::netadr_s& a); +} // namespace network diff --git a/src/client/component/rcon.cpp b/src/client/component/rcon.cpp new file mode 100644 index 0000000..c146ee8 --- /dev/null +++ b/src/client/component/rcon.cpp @@ -0,0 +1,54 @@ +#include +#include "../loader/component_loader.hpp" + +#include + +#include "network/network.hpp" + +#include "crypto_key.hpp" +#include "key_catcher.hpp" + +#include + +namespace rcon { +namespace { +utils::cryptography::ecc::key key; +std::string commands; +} // namespace + +class component final : public component_interface { +public: + void post_unpack() override { + key = crypto_key::get(); + add_key_hooks(); + add_commands(); + } + +private: + static void add_key_hooks() { + // Why commands don't work? I don't know! + key_catcher::on_key_press( + "7", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { + commands = "quit"; + network::send(game::localClientConnection->serverAddress, + "rcon_request"); + }); + } + + static void add_commands() { + network::on_packet("rcon_authorization", [](const game::netadr_s& adr, + const std::string_view& data) { + const auto signed_msg = + utils::cryptography::ecc::sign_message(key, std::string(data)); + + proto::rcon::command info; + info.set_commands(commands); + info.set_signature(signed_msg); + + network::send(adr, "rcon_execute", info.SerializeAsString()); + }); + } +}; +} // namespace rcon + +REGISTER_COMPONENT(rcon::component) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index dc29ad1..4dd97a4 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -69,7 +69,7 @@ struct netadr_s { unsigned int addrHandleIndex; }; -static_assert(sizeof(netadr_s) == 24); +static_assert(sizeof(netadr_s) == 0x18); typedef enum { ERR_FATAL = 0x0, diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 4dff855..2897110 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -54,7 +54,9 @@ WEAK symbol WEAK symbol NetadrToSockadr{0x48B460}; WEAK symbol NET_StringToAdr{ 0x4E09A0}; -WEAK symbol query_socket{0x5A861EC}; +WEAK symbol Sys_SendPacket{0x5145C0}; +WEAK symbol NET_SendLoopPacket{0x4B9DF0}; + WEAK symbol Com_Quit_f{0x556060}; WEAK symbol MSG_Init{0x40E030}; @@ -74,11 +76,6 @@ WEAK symbol WEAK symbol DB_FindXAssetHeader{0x4B25C0}; -WEAK symbol - R_AddCmdDrawText{0x42C970}; - // Variables WEAK symbol cmd_args{0x1C96850}; WEAK symbol playerKeys{0xB3A38C}; diff --git a/src/client/proto/rcon.proto b/src/client/proto/rcon.proto new file mode 100644 index 0000000..8c525dd --- /dev/null +++ b/src/client/proto/rcon.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package proto.rcon; + +message command { + bytes commands = 1; + bytes signature = 2; +} diff --git a/src/client/std_include.cpp b/src/client/std_include.cpp index e912508..5d10150 100644 --- a/src/client/std_include.cpp +++ b/src/client/std_include.cpp @@ -1 +1,11 @@ #include "std_include.hpp" + +extern "C" { +int s_read_arc4random(void*, std::size_t) { return -1; } + +int s_read_getrandom(void*, std::size_t) { return -1; } + +int s_read_urandom(void*, std::size_t) { return -1; } + +int s_read_ltm_rng(void*, std::size_t) { return -1; } +} diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index bb97dcd..28fdc6b 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -4,8 +4,10 @@ #define WIN32_LEAN_AND_MEAN -#include #include +#include +#include + #include #include @@ -15,10 +17,12 @@ #include #include #include +#include #include #include #pragma comment(lib, "ntdll.lib") +#pragma comment(lib, "ws2_32.lib") using namespace std::literals; diff --git a/src/common/utils/cryptography.cpp b/src/common/utils/cryptography.cpp new file mode 100644 index 0000000..8316752 --- /dev/null +++ b/src/common/utils/cryptography.cpp @@ -0,0 +1,556 @@ +#include "string.hpp" +#include "cryptography.hpp" +#include "nt.hpp" + +#include + +#undef max +using namespace std::string_literals; + +/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf + +namespace utils::cryptography { +namespace { +struct __ { + __() { + ltc_mp = ltm_desc; + + register_cipher(&aes_desc); + register_cipher(&des3_desc); + + register_prng(&sprng_desc); + register_prng(&fortuna_desc); + register_prng(&yarrow_desc); + + register_hash(&sha1_desc); + register_hash(&sha256_desc); + register_hash(&sha512_desc); + } +} ___; + +[[maybe_unused]] const char* cs(const std::uint8_t* data) { + return reinterpret_cast(data); +} + +[[maybe_unused]] char* cs(std::uint8_t* data) { + return reinterpret_cast(data); +} + +[[maybe_unused]] const std::uint8_t* cs(const char* data) { + return reinterpret_cast(data); +} + +[[maybe_unused]] std::uint8_t* cs(char* data) { + return reinterpret_cast(data); +} + +[[maybe_unused]] unsigned long ul(const std::size_t value) { + return static_cast(value); +} + +class prng { +public: + prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true) + : state_(std::make_unique()), descriptor_(descriptor) { + this->id_ = register_prng(&descriptor); + if (this->id_ == -1) { + throw std::runtime_error("PRNG "s + this->descriptor_.name + + " could not be registered!"); + } + + if (autoseed) { + this->auto_seed(); + } else { + this->descriptor_.start(this->state_.get()); + } + } + + ~prng() { this->descriptor_.done(this->state_.get()); } + + [[nodiscard]] prng_state* get_state() const { + this->descriptor_.ready(this->state_.get()); + return this->state_.get(); + } + + [[nodiscard]] int get_id() const { return this->id_; } + + void add_entropy(const void* data, const std::size_t length) const { + this->descriptor_.add_entropy(static_cast(data), + ul(length), this->state_.get()); + } + + void read(void* data, const std::size_t length) const { + this->descriptor_.read(static_cast(data), ul(length), + this->get_state()); + } + +private: + int id_; + std::unique_ptr state_; + const ltc_prng_descriptor& descriptor_; + + void auto_seed() const { + rng_make_prng(128, this->id_, this->state_.get(), nullptr); + + int i[4]; // uninitialized data + auto* i_ptr = &i; + this->add_entropy(reinterpret_cast(&i), sizeof(i)); + this->add_entropy(reinterpret_cast(&i_ptr), sizeof(i_ptr)); + + auto t = time(nullptr); + this->add_entropy(reinterpret_cast(&t), sizeof(t)); + } +}; + +const prng prng_(fortuna_desc); +} // namespace + +ecc::key::key() { ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); } + +ecc::key::~key() { this->free(); } + +ecc::key::key(key&& obj) noexcept : key() { this->operator=(std::move(obj)); } + +ecc::key::key(const key& obj) : key() { this->operator=(obj); } + +ecc::key& ecc::key::operator=(key&& obj) noexcept { + if (this != &obj) { + std::memmove(&this->key_storage_, &obj.key_storage_, + sizeof(this->key_storage_)); + ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_)); + } + + return *this; +} + +ecc::key& ecc::key::operator=(const key& obj) { + if (this != &obj && obj.is_valid()) { + this->deserialize(obj.serialize(obj.key_storage_.type)); + } + + return *this; +} + +bool ecc::key::operator==(key& key) const { + return (this->is_valid() && key.is_valid() && + this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC)); +} + +bool ecc::key::is_valid() const { + return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_))); +} + +ecc_key& ecc::key::get() { return this->key_storage_; } + +const ecc_key& ecc::key::get() const { return this->key_storage_; } + +std::string ecc::key::get_public_key() const { + std::uint8_t buffer[512] = {0}; + unsigned long length = sizeof(buffer); + + if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK) { + return {cs(buffer), length}; + } + + return {}; +} + +void ecc::key::set(const std::string& pub_key_buffer) { + this->free(); + + if (ecc_ansi_x963_import(cs(pub_key_buffer.data()), ul(pub_key_buffer.size()), + &this->key_storage_) != CRYPT_OK) { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } +} + +void ecc::key::deserialize(const std::string& key) { + this->free(); + + if (ecc_import(cs(key.data()), ul(key.size()), &this->key_storage_) != + CRYPT_OK) { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } +} + +std::string ecc::key::serialize(const int type) const { + std::uint8_t buffer[4096] = {0}; + unsigned long length = sizeof(buffer); + + if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK) { + return {cs(buffer), length}; + } + + return {}; +} + +void ecc::key::free() { + if (this->is_valid()) { + ecc_free(&this->key_storage_); + } + + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); +} + +std::uint64_t ecc::key::get_hash() const { + const auto hash = sha1::compute(this->get_public_key()); + if (hash.size() >= 8) { + return *reinterpret_cast(hash.data()); + } + + return 0; +} + +ecc::key ecc::generate_key(const int bits) { + key key; + ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get()); + + return key; +} + +ecc::key ecc::generate_key(const int bits, const std::string& entropy) { + key key{}; + const prng yarrow(yarrow_desc, false); + yarrow.add_entropy(entropy.data(), entropy.size()); + + ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get()); + + return key; +} + +std::string ecc::sign_message(const key& key, const std::string& message) { + if (!key.is_valid()) + return {}; + + std::uint8_t buffer[512]; + unsigned long length = sizeof(buffer); + + ecc_sign_hash(cs(message.data()), ul(message.size()), buffer, &length, + prng_.get_state(), prng_.get_id(), &key.get()); + + return {cs(buffer), length}; +} + +bool ecc::verify_message(const key& key, const std::string& message, + const std::string& signature) { + if (!key.is_valid()) + return false; + + auto result = 0; + return (ecc_verify_hash(cs(signature.data()), ul(signature.size()), + cs(message.data()), ul(message.size()), &result, + &key.get()) == CRYPT_OK && + result != 0); +} + +bool ecc::encrypt(const key& key, std::string& data) { + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() { + return ecc_encrypt_key(cs(data.data()), ul(data.size()), + cs(out_data.data()), &out_len, prng_.get_state(), + prng_.get_id(), find_hash("sha512"), &key.get()); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) { + out_data.resize(out_len); + res = crypt(); + } + + if (res != CRYPT_OK) { + return false; + } + + out_data.resize(out_len); + data = std::move(out_data); + return true; +} + +bool ecc::decrypt(const key& key, std::string& data) { + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() { + return ecc_decrypt_key(cs(data.data()), ul(data.size()), + cs(out_data.data()), &out_len, &key.get()); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) { + out_data.resize(out_len); + res = crypt(); + } + + if (res != CRYPT_OK) { + return false; + } + + out_data.resize(out_len); + data = std::move(out_data); + return true; +} + +std::string rsa::encrypt(const std::string& data, const std::string& hash, + const std::string& key) { + rsa_key new_key; + rsa_import(cs(key.data()), ul(key.size()), &new_key); + const auto _ = gsl::finally([&]() { rsa_free(&new_key); }); + + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() { + return rsa_encrypt_key(cs(data.data()), ul(data.size()), + cs(out_data.data()), &out_len, cs(hash.data()), + ul(hash.size()), prng_.get_state(), prng_.get_id(), + find_hash("sha512"), &new_key); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) { + out_data.resize(out_len); + res = crypt(); + } + + if (res == CRYPT_OK) { + out_data.resize(out_len); + return out_data; + } + + return {}; +} + +std::string des3::encrypt(const std::string& data, const std::string& iv, + const std::string& key) { + std::string enc_data; + enc_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), + 0, &cbc); + cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return enc_data; +} + +std::string des3::decrypt(const std::string& data, const std::string& iv, + const std::string& key) { + std::string dec_data; + dec_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), + 0, &cbc); + cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return dec_data; +} + +std::string tiger::compute(const std::string& data, const bool hex) { + return compute(cs(data.data()), data.size(), hex); +} + +std::string tiger::compute(const uint8_t* data, const size_t length, + const bool hex) { + std::uint8_t buffer[24] = {0}; + + hash_state state; + tiger_init(&state); + tiger_process(&state, data, ul(length)); + tiger_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) + return hash; + + return string::dump_hex(hash, ""); +} + +std::string aes::encrypt(const std::string& data, const std::string& iv, + const std::string& key) { + std::string enc_data; + enc_data.resize(data.size()); + + symmetric_CBC cbc; + const auto aes = find_cipher("aes"); + + cbc_start(aes, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, + &cbc); + cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return enc_data; +} + +std::string aes::decrypt(const std::string& data, const std::string& iv, + const std::string& key) { + std::string dec_data; + dec_data.resize(data.size()); + + symmetric_CBC cbc; + const auto aes = find_cipher("aes"); + + cbc_start(aes, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, + &cbc); + cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return dec_data; +} + +std::string hmac_sha1::compute(const std::string& data, + const std::string& key) { + std::string buffer; + buffer.resize(20); + + hmac_state state; + hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size())); + hmac_process(&state, cs(data.data()), static_cast(data.size())); + + auto out_len = ul(buffer.size()); + hmac_done(&state, cs(buffer.data()), &out_len); + + buffer.resize(out_len); + return buffer; +} + +std::string sha1::compute(const std::string& data, const bool hex) { + return compute(cs(data.data()), data.size(), hex); +} + +std::string sha1::compute(const std::uint8_t* data, const std::size_t length, + const bool hex) { + std::uint8_t buffer[20] = {0}; + + hash_state state; + sha1_init(&state); + sha1_process(&state, data, ul(length)); + sha1_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) + return hash; + + return string::dump_hex(hash, ""); +} + +std::string sha256::compute(const std::string& data, const bool hex) { + return compute(cs(data.data()), data.size(), hex); +} + +std::string sha256::compute(const std::uint8_t* data, const std::size_t length, + const bool hex) { + std::uint8_t buffer[32] = {0}; + + hash_state state; + sha256_init(&state); + sha256_process(&state, data, ul(length)); + sha256_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) + return hash; + + return string::dump_hex(hash, ""); +} + +std::string sha512::compute(const std::string& data, const bool hex) { + return compute(cs(data.data()), data.size(), hex); +} + +std::string sha512::compute(const std::uint8_t* data, const std::size_t length, + const bool hex) { + std::uint8_t buffer[64] = {0}; + + hash_state state; + sha512_init(&state); + sha512_process(&state, data, ul(length)); + sha512_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) + return hash; + + return string::dump_hex(hash, ""); +} + +std::string base64::encode(const std::uint8_t* data, const std::size_t len) { + std::string result; + result.resize((len + 2) * 2); + + auto out_len = ul(result.size()); + if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK) { + return {}; + } + + result.resize(out_len); + return result; +} + +std::string base64::encode(const std::string& data) { + return base64::encode(cs(data.data()), data.size()); +} + +std::string base64::decode(const std::string& data) { + std::string result; + result.resize((data.size() + 2) * 2); + + auto out_len = ul(result.size()); + if (base64_decode(data.data(), ul(data.size()), cs(result.data()), + &out_len) != CRYPT_OK) { + return {}; + } + + result.resize(out_len); + return result; +} + +unsigned int jenkins_one_at_a_time::compute(const std::string& data) { + return compute(data.data(), data.size()); +} + +unsigned int jenkins_one_at_a_time::compute(const char* key, + const std::size_t len) { + unsigned int hash, i; + for (hash = i = 0; i < len; ++i) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; +} + +std::uint32_t random::get_integer() { + std::uint32_t result; + random::get_data(&result, sizeof(result)); + return result; +} + +std::string random::get_challenge() { + std::string result; + result.resize(sizeof(std::uint32_t)); + random::get_data(result.data(), result.size()); + return string::dump_hex(result, ""); +} + +void random::get_data(void* data, const std::size_t size) { + prng_.read(data, size); +} +} // namespace utils::cryptography diff --git a/src/common/utils/cryptography.hpp b/src/common/utils/cryptography.hpp new file mode 100644 index 0000000..755cef3 --- /dev/null +++ b/src/common/utils/cryptography.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include +#include + +namespace utils::cryptography { +namespace ecc { +class key final { +public: + key(); + ~key(); + + key(key&& obj) noexcept; + key(const key& obj); + key& operator=(key&& obj) noexcept; + key& operator=(const key& obj); + bool operator==(key& key) const; + + [[nodiscard]] bool is_valid() const; + + ecc_key& get(); + [[nodiscard]] const ecc_key& get() const; + + [[nodiscard]] std::string get_public_key() const; + + void set(const std::string& pub_key_buffer); + + void deserialize(const std::string& key); + + [[nodiscard]] std::string serialize(int type = PK_PRIVATE) const; + + void free(); + + [[nodiscard]] std::uint64_t get_hash() const; + +private: + ecc_key key_storage_{}; +}; + +key generate_key(int bits); +key generate_key(int bits, const std::string& entropy); +std::string sign_message(const key& key, const std::string& message); +bool verify_message(const key& key, const std::string& message, + const std::string& signature); + +bool encrypt(const key& key, std::string& data); +bool decrypt(const key& key, std::string& data); +} // namespace ecc + +namespace rsa { +std::string encrypt(const std::string& data, const std::string& hash, + const std::string& key); +} + +namespace des3 { +std::string encrypt(const std::string& data, const std::string& iv, + const std::string& key); +std::string decrypt(const std::string& data, const std::string& iv, + const std::string& key); +} // namespace des3 + +namespace tiger { +std::string compute(const std::string& data, bool hex = false); +std::string compute(const std::uint8_t* data, std::size_t length, + bool hex = false); +} // namespace tiger + +namespace aes { +std::string encrypt(const std::string& data, const std::string& iv, + const std::string& key); +std::string decrypt(const std::string& data, const std::string& iv, + const std::string& key); +} // namespace aes + +namespace hmac_sha1 { +std::string compute(const std::string& data, const std::string& key); +} + +namespace sha1 { +std::string compute(const std::string& data, bool hex = false); +std::string compute(const std::uint8_t* data, std::size_t length, + bool hex = false); +} // namespace sha1 + +namespace sha256 { +std::string compute(const std::string& data, bool hex = false); +std::string compute(const std::uint8_t* data, std::size_t length, + bool hex = false); +} // namespace sha256 + +namespace sha512 { +std::string compute(const std::string& data, bool hex = false); +std::string compute(const std::uint8_t* data, std::size_t length, + bool hex = false); +} // namespace sha512 + +namespace base64 { +std::string encode(const std::uint8_t* data, std::size_t len); +std::string encode(const std::string& data); +std::string decode(const std::string& data); +} // namespace base64 + +namespace jenkins_one_at_a_time { +unsigned int compute(const std::string& data); +unsigned int compute(const char* key, std::size_t len); +}; // namespace jenkins_one_at_a_time + +namespace random { +std::uint32_t get_integer(); +std::string get_challenge(); +void get_data(void* data, std::size_t size); +} // namespace random +} // namespace utils::cryptography diff --git a/src/common/utils/io.cpp b/src/common/utils/io.cpp new file mode 100644 index 0000000..5e017f9 --- /dev/null +++ b/src/common/utils/io.cpp @@ -0,0 +1,109 @@ +#include "io.hpp" +#include "nt.hpp" +#include + +namespace utils::io { +bool remove_file(const std::string& file) { + return DeleteFileA(file.data()) == TRUE; +} + +bool move_file(const std::string& src, const std::string& target) { + return MoveFileA(src.data(), target.data()) == TRUE; +} + +bool file_exists(const std::string& file) { + return std::filesystem::exists(file); +} + +bool write_file(const std::string& file, const std::string& data, + const bool append) { + const auto pos = file.find_last_of("/\\"); + if (pos != std::string::npos) { + create_directory(file.substr(0, pos)); + } + + std::ofstream stream(file, std::ios::binary | std::ofstream::out | + (append ? std::ofstream::app : 0)); + + if (stream.is_open()) { + stream.write(data.data(), data.size()); + stream.close(); + return true; + } + + return false; +} + +std::string read_file(const std::string& file) { + std::string data; + read_file(file, &data); + return data; +} + +bool read_file(const std::string& file, std::string* data) { + if (!data) + return false; + data->clear(); + + if (file_exists(file)) { + std::ifstream stream(file, std::ios::binary); + if (!stream.is_open()) + return false; + + stream.seekg(0, std::ios::end); + const std::streamsize size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) { + data->resize(static_cast(size)); + stream.read(data->data(), size); + stream.close(); + return true; + } + } + + return false; +} + +size_t file_size(const std::string& file) { + if (file_exists(file)) { + std::ifstream stream(file, std::ios::binary); + + if (stream.good()) { + stream.seekg(0, std::ios::end); + return static_cast(stream.tellg()); + } + } + + return 0; +} + +bool create_directory(const std::string& directory) { + return std::filesystem::create_directories(directory); +} + +bool directory_exists(const std::string& directory) { + return std::filesystem::is_directory(directory); +} + +bool directory_is_empty(const std::string& directory) { + return std::filesystem::is_empty(directory); +} + +std::vector list_files(const std::string& directory) { + std::vector files; + + for (auto& file : std::filesystem::directory_iterator(directory)) { + files.push_back(file.path().generic_string()); + } + + return files; +} + +void copy_folder(const std::filesystem::path& src, + const std::filesystem::path& target) { + std::filesystem::copy(src, target, + std::filesystem::copy_options::overwrite_existing | + std::filesystem::copy_options::recursive); +} +} // namespace utils::io diff --git a/src/common/utils/io.hpp b/src/common/utils/io.hpp new file mode 100644 index 0000000..92da124 --- /dev/null +++ b/src/common/utils/io.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace utils::io { +bool remove_file(const std::string& file); +bool move_file(const std::string& src, const std::string& target); +bool file_exists(const std::string& file); +bool write_file(const std::string& file, const std::string& data, + bool append = false); +bool read_file(const std::string& file, std::string* data); +std::string read_file(const std::string& file); +size_t file_size(const std::string& file); +bool create_directory(const std::string& directory); +bool directory_exists(const std::string& directory); +bool directory_is_empty(const std::string& directory); +std::vector list_files(const std::string& directory); +void copy_folder(const std::filesystem::path& src, + const std::filesystem::path& target); +} // namespace utils::io diff --git a/tools/protoc.exe b/tools/protoc.exe new file mode 100644 index 0000000..cc258a5 Binary files /dev/null and b/tools/protoc.exe differ