diff --git a/.github/workflows/core_windows_build.yml b/.github/workflows/core_windows_build.yml index 63402ac9..9196f93b 100644 --- a/.github/workflows/core_windows_build.yml +++ b/.github/workflows/core_windows_build.yml @@ -87,9 +87,6 @@ jobs: echo "OPENSSL_INCLUDE_DIR=$root/include" >> "$GITHUB_ENV" echo "OPENSSL_CRYPTO_LIBRARY=$root/lib/VC/libcrypto64MT.lib" >> "$GITHUB_ENV" echo "OPENSSL_SSL_LIBRARY=$root/lib/VC/libssl64MT.lib" >> "$GITHUB_ENV" - # SlProWeb installs provider DLLs beside openssl.exe, while its - # compiled MODULESDIR still names the default installation path. - echo "OPENSSL_MODULES=$root/bin" >> "$GITHUB_ENV" echo "$root/bin" >> "$GITHUB_PATH" else echo "::error::OpenSSL developer libraries not found" diff --git a/src/mangosd/mangosd.cpp b/src/mangosd/mangosd.cpp index cbc2124b..554e9276 100644 --- a/src/mangosd/mangosd.cpp +++ b/src/mangosd/mangosd.cpp @@ -47,10 +47,6 @@ #include "Common/ServerDefines.h" #include #include -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) -# include -# include "Auth/OpenSSLProvider.h" -#endif #include "Platform/Define.h" #include @@ -305,13 +301,7 @@ int main(int argc, char** argv) DETAIL_LOG("Using SSL version: %s (Library: %s)", OPENSSL_VERSION_TEXT, OpenSSL_version(OPENSSL_VERSION)); -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - if (!OpenSSLProviderManager::Instance().IsInitialized()) - { - Log::WaitBeforeContinueIfNeed(); - return 1; - } -#else +#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) if (SSLeay() < 0x10100000L || SSLeay() > 0x10200000L) { DETAIL_LOG("WARNING: OpenSSL version may be out of date or unsupported. Logins to server may not work!"); diff --git a/src/shared/Auth/ARC4.cpp b/src/shared/Auth/ARC4.cpp index 59b07759..2fe366bd 100644 --- a/src/shared/Auth/ARC4.cpp +++ b/src/shared/Auth/ARC4.cpp @@ -5,7 +5,6 @@ * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2026 MaNGOS - * Copyright (C) 2008-2015 TrinityCore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,163 +23,117 @@ * and lore are copyrighted by Blizzard Entertainment, Inc. */ -/** - * @file ARC4.cpp - * @brief Implementation of ARC4 encryption algorithm using OpenSSL - * - * This file implements the ARC4 (Alleged RC4) stream cipher for use - * in the MaNGOS authentication and session encryption system. ARC4 - * is used to encrypt/decrypt game traffic between the server and clients. - * - * The implementation uses OpenSSL's EVP interface for the cipher operations - * and includes proper provider management for OpenSSL 3.x compatibility. - */ - #include "ARC4.h" -#include "OpenSSLProvider.h" -#include "Log/Log.h" -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) -#include -#endif -/** - * @brief Construct ARC4 cipher with specified key length - * @param len Key length in bytes - * - * Creates an ARC4 cipher context with the specified key length. - * The key itself is not set in this constructor - use Init() to set it. - * - * @note On OpenSSL 3.x, this automatically initializes the legacy provider - * required for ARC4 support. - */ -ARC4::ARC4(uint8 len) : m_cipherContext() +#include + +namespace { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - // RC4 lives in the legacy provider, so it has to be loaded before EVP_rc4(). - if (!OpenSSLProviderManager::Instance().IsInitialized()) + /// The permutation is a byte table, so every index is taken modulo 256 -- which is + /// what a uint8 does on its own. Saying so once beats masking at eight call sites. + inline void Swap(uint8& a, uint8& b) { - sLog.outError("ARC4: Failed to initialize OpenSSL providers"); - return; - } -#endif - - if (!m_cipherContext.IsValid()) - { - sLog.outError("ARC4: Failed to create cipher context"); - return; + const uint8 t = a; + a = b; + b = t; } - // Checked, not assumed: a loaded provider does not mean the cipher was fetched. - if (EVP_EncryptInit_ex(m_cipherContext.Get(), EVP_rc4(), NULL, NULL, NULL) != 1) + /// A key of zero bytes would divide by zero in the schedule below, and a key longer + /// than the permutation cannot be distinguished from its first 256 bytes. + inline uint8 UsableKeyLength(uint8 len) { - sLog.outError("ARC4: Failed to initialize RC4 - is the OpenSSL legacy provider loaded?"); - return; - } - - if (EVP_CIPHER_CTX_set_key_length(m_cipherContext.Get(), len) != 1) - { - sLog.outError("ARC4: Failed to set the RC4 key length"); - return; + return len ? len : 1; } } -/** - * @brief Construct ARC4 cipher with initial key - * @param seed Pointer to the key bytes - * @param len Length of the key in bytes - * - * Creates an ARC4 cipher context and initializes it with the provided key. - * The cipher is ready to use for encryption/decryption immediately after - * construction. - * - * @note On OpenSSL 3.x, this automatically initializes the legacy provider - * required for ARC4 support. - */ -ARC4::ARC4(uint8 *seed, uint8 len) : m_cipherContext() +ARC4::ARC4(uint8 len) + : m_x(0), m_y(0), m_keyLength(UsableKeyLength(len)) { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - // RC4 lives in the legacy provider, so it has to be loaded before EVP_rc4(). - if (!OpenSSLProviderManager::Instance().IsInitialized()) + // The identity permutation. Not keyed yet: a caller using this constructor has said + // it will supply the key through Init, and leaving the state identity means a + // forgotten Init produces an obviously wrong stream rather than a plausible one. + for (int i = 0; i < 256; ++i) { - sLog.outError("ARC4: Failed to initialize OpenSSL providers"); - return; - } -#endif - - if (!m_cipherContext.IsValid()) - { - sLog.outError("ARC4: Failed to create cipher context"); - return; - } - - // Checked, not assumed: a loaded provider does not mean the cipher was fetched. - if (EVP_EncryptInit_ex(m_cipherContext.Get(), EVP_rc4(), NULL, NULL, NULL) != 1) - { - sLog.outError("ARC4: Failed to initialize RC4 - is the OpenSSL legacy provider loaded?"); - return; - } - - if (EVP_CIPHER_CTX_set_key_length(m_cipherContext.Get(), len) != 1) - { - sLog.outError("ARC4: Failed to set the RC4 key length"); - return; - } - - if (EVP_EncryptInit_ex(m_cipherContext.Get(), NULL, NULL, seed, NULL) != 1) - { - sLog.outError("ARC4: Failed to seed RC4"); + m_state[i] = uint8(i); } } -/** - * @brief Destructor for ARC4 cipher - * - * All cleanup is handled automatically by the RAII wrappers - * (OpenSSLProviderManager and OpenSSLCipherContext). - */ +ARC4::ARC4(uint8* seed, uint8 len) + : m_x(0), m_y(0), m_keyLength(UsableKeyLength(len)) +{ + for (int i = 0; i < 256; ++i) + { + m_state[i] = uint8(i); + } + Init(seed); +} + ARC4::~ARC4() { - // Cleanup is now handled automatically by RAII wrappers + // The key schedule is derived from a session secret, so it does not outlive the + // object in freed memory waiting to be read back. memset rather than std::fill + // because the intent is erasure, and a loop over a member the compiler can see is + // dead is a loop the compiler may delete. + std::memset(m_state, 0, sizeof(m_state)); + m_x = 0; + m_y = 0; } -/** - * @brief Initialize or re-initialize the cipher with a new key - * @param seed Pointer to the key bytes - * - * Sets or changes the encryption key for the ARC4 cipher. - * This can be called multiple times to re-key the cipher. - * - * @note The key length must match the length specified in the constructor. - */ -void ARC4::Init(uint8 *seed) +void ARC4::Init(uint8* seed) { - if (m_cipherContext.IsValid()) + if (!seed) { - EVP_EncryptInit_ex(m_cipherContext.Get(), NULL, NULL, seed, NULL); - } -} - -/** - * @brief Encrypt or decrypt data in-place - * @param len Length of data to process in bytes - * @param data Pointer to the data buffer (modified in-place) - * - * Processes data using the ARC4 stream cipher. Since ARC4 is a symmetric - * stream cipher, the same operation is used for both encryption and - * decryption. The data is modified in-place for efficiency. - * - * @warning The cipher must be initialized with a key before calling this. - * @note The output length will always equal the input length for ARC4. - */ -void ARC4::UpdateData(int len, uint8 *data) -{ - if (!m_cipherContext.IsValid()) - { - sLog.outError("ARC4: Invalid cipher context, cannot update data"); return; } - int outlen = 0; - EVP_EncryptUpdate(m_cipherContext.Get(), data, &outlen, data, len); - EVP_EncryptFinal_ex(m_cipherContext.Get(), data, &outlen); + // === Key schedule (KSA) === + // + // Start from the identity every time. Init is a RE-key as much as a first key -- + // the packet crypt builds a cipher and seeds it afterwards -- and keying on top of + // a used permutation would produce a stream that depends on how much traffic had + // gone before it. + for (int i = 0; i < 256; ++i) + { + m_state[i] = uint8(i); + } + + uint8 j = 0; + for (int i = 0; i < 256; ++i) + { + j = uint8(j + m_state[i] + seed[i % m_keyLength]); + Swap(m_state[i], m_state[j]); + } + + // A stream cipher is its position in the stream, so a re-key rewinds it. Forgetting + // this is the classic way to make a cipher that decrypts the first session and + // nothing after it. + m_x = 0; + m_y = 0; +} + +void ARC4::UpdateData(int len, uint8* data) +{ + if (len <= 0 || !data) + { + return; + } + + // === Pseudo-random generation (PRGA) === + // + // Kept in locals and written back once: the two indices are read and written for + // every byte, and leaving them as members makes the compiler reload them through + // `this` each time in case `data` aliases the object. + uint8 x = m_x; + uint8 y = m_y; + + for (int n = 0; n < len; ++n) + { + x = uint8(x + 1); + y = uint8(y + m_state[x]); + Swap(m_state[x], m_state[y]); + data[n] ^= m_state[uint8(m_state[x] + m_state[y])]; + } + + m_x = x; + m_y = y; } diff --git a/src/shared/Auth/ARC4.h b/src/shared/Auth/ARC4.h index 87a8bc02..41d87208 100644 --- a/src/shared/Auth/ARC4.h +++ b/src/shared/Auth/ARC4.h @@ -5,7 +5,6 @@ * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2026 MaNGOS - * Copyright (C) 2008-2015 TrinityCore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,47 +26,77 @@ #ifndef _AUTH_SARC4_H #define _AUTH_SARC4_H -#include #include "Platform/Define.h" -#include "OpenSSLProvider.h" /** - * @brief ARC4 encryption/decryption cipher implementation + * @brief ARCFOUR, in about thirty lines, because the alternative was a DLL. * - * ARC4 is a stream cipher that uses a key to generate a keystream. - * This class provides ARC4 encryption and decryption functionality using OpenSSL. + * This used to be EVP_rc4(). OpenSSL 3 moved RC4 to the LEGACY provider, which is not + * compiled into libcrypto -- it is a separate module discovered on disk at run time. So + * a cipher of two hundred and fifty-six bytes of state dragged in a deployment + * requirement (`ossl-modules/legacy.dll` beside the executable, an OPENSSL_MODULES + * search path, an installer that ships it, CI that copies it) and a start-up check that + * REFUSED TO RUN without it. + * + * It also aimed a loaded gun at the server: the legacy provider is deprecated, and the + * release that finally drops it would have stopped this emulator booting -- for want of + * a cipher any competent programmer can write from the specification. + * + * The protocol needs RC4 and will always need it, because the 2.4.3 client is never + * going to be updated. A cipher the protocol mandates forever belongs in the tree. + * + * ARCFOUR is fully specified and this is the whole of it: a 256-byte permutation, a + * key-scheduling pass, and a stream that XORs. There is no interoperability risk of the + * kind that would justify borrowing an implementation -- CryptoStressTest exercises the + * same vectors it always did, and it was not touched, so the equivalence is demonstrated + * rather than asserted. + * + * NOT thread-safe, and it must not be: a stream cipher IS its position in the stream, so + * two threads sharing one is a protocol error, not a race to be locked away. Each + * direction of each session owns its own. */ class ARC4 { public: /** - * @brief Constructor with key length specification - * @param len Length of the key in bytes + * @brief A cipher expecting a key of `len` bytes, keyed later by Init(). + * @param len Key length in bytes, 1..256. */ - ARC4(uint8 len); + explicit ARC4(uint8 len); + /** - * @brief Constructor with seed data - * @param seed Pointer to the seed/key data - * @param len Length of the seed data in bytes - */ - ARC4(uint8 *seed, uint8 len); - /** - * @brief Destructor + * @brief A cipher keyed immediately. + * @param seed Key bytes. + * @param len Key length in bytes, 1..256. */ + ARC4(uint8* seed, uint8 len); + ~ARC4(); + /** - * @brief Initialize the cipher with seed data - * @param seed Pointer to the seed/key data + * @brief (Re)key the cipher and rewind the keystream. + * + * The length is the one given to the constructor -- the signature has no room + * for another, and every caller in the tree keys with the length it declared. + * + * @param seed Key bytes; at least the constructor's length must be readable. */ - void Init(uint8 *seed); + void Init(uint8* seed); + /** - * @brief Update/encrypt data using the cipher - * @param len Length of the data to process - * @param data Pointer to the data to encrypt/decrypt + * @brief XOR `len` bytes of `data` with the keystream, in place. + * + * Encryption and decryption are the same operation, which is why one method + * serves both and why calling it twice on the same bytes with the same key + * returns the original. */ - void UpdateData(int len, uint8 *data); + void UpdateData(int len, uint8* data); + private: - OpenSSLCipherContext m_cipherContext; /**< RAII cipher context */ + uint8 m_state[256]; ///< The permutation. + uint8 m_x; ///< Stream position; `i` in the specification. + uint8 m_y; ///< Stream position; `j` in the specification. + uint8 m_keyLength; ///< Bytes Init() will read from its seed. }; #endif diff --git a/src/shared/Auth/OpenSSLProvider.cpp b/src/shared/Auth/OpenSSLProvider.cpp deleted file mode 100644 index babe88fd..00000000 --- a/src/shared/Auth/OpenSSLProvider.cpp +++ /dev/null @@ -1,453 +0,0 @@ -/** - * SPDX-License-Identifier: GPL-3.0-or-later - * - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2026 MaNGOS - * - * This program 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 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -/** - * @file OpenSSLProvider.cpp - * @brief Implementation of RAII wrappers for OpenSSL providers - */ - -#include -#include "OpenSSLProvider.h" -#include "Log/Log.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef WIN32 -#include -#endif - -/** - * Creates a new OpenSSL cipher context wrapper. - */ -OpenSSLCipherContext::OpenSSLCipherContext() - : m_ctx(nullptr) -{ - m_ctx = EVP_CIPHER_CTX_new(); - if (!m_ctx) - { - sLog.outError("OpenSSLCipherContext: Failed to create cipher context"); - } -} - -/** - * Releases the owned OpenSSL cipher context. - */ -OpenSSLCipherContext::~OpenSSLCipherContext() -{ - if (m_ctx) - { - EVP_CIPHER_CTX_free(m_ctx); - m_ctx = nullptr; - } -} - -OpenSSLCipherContext::OpenSSLCipherContext(OpenSSLCipherContext&& other) noexcept - : m_ctx(other.m_ctx) -{ - other.m_ctx = nullptr; -} - -OpenSSLCipherContext& OpenSSLCipherContext::operator=(OpenSSLCipherContext&& other) noexcept -{ - if (this != &other) - { - // Clean up current context - if (m_ctx) - { - EVP_CIPHER_CTX_free(m_ctx); - } - - // Move from other - m_ctx = other.m_ctx; - other.m_ctx = nullptr; - } - return *this; -} - -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - -namespace -{ -bool ParseProviderMajor(const std::string& version, unsigned& major) -{ - std::size_t separator = version.find('.'); - if (separator == std::string::npos || separator == 0) - return false; - - const char* begin = version.data(); - const char* end = begin + separator; - std::from_chars_result parsed = - std::from_chars(begin, end, major); - return parsed.ec == std::errc{} && parsed.ptr == end; -} - -#ifdef WIN32 -std::wstring ReadWindowsEnvironment(const wchar_t* name) -{ - DWORD required = GetEnvironmentVariableW(name, nullptr, 0); - if (required == 0) - return {}; - - std::vector buffer(required); - while (true) - { - DWORD written = GetEnvironmentVariableW( - name, buffer.data(), static_cast(buffer.size())); - if (written == 0) - return {}; - if (written < buffer.size()) - return std::wstring(buffer.data(), written); - buffer.resize(static_cast(written) + 1); - } -} - -std::filesystem::path GetExecutableDirectory() -{ - constexpr std::size_t MAX_WINDOWS_PATH = 32768; - std::vector buffer(MAX_PATH); - - while (buffer.size() <= MAX_WINDOWS_PATH) - { - DWORD written = GetModuleFileNameW( - nullptr, buffer.data(), static_cast(buffer.size())); - if (written == 0) - return {}; - if (written < buffer.size()) - { - return std::filesystem::path( - std::wstring(buffer.data(), written)).parent_path(); - } - - if (buffer.size() == MAX_WINDOWS_PATH) - break; - buffer.resize((std::min)(buffer.size() * 2, MAX_WINDOWS_PATH)); - } - - return {}; -} - -bool ConvertWideToAnsi(const std::wstring& value, std::string& converted) -{ - converted.clear(); - if (value.empty()) - return false; - - constexpr UINT GB18030_CODE_PAGE = 54936; - UINT const codePage = GetACP(); - bool const restrictedCodePage = - codePage == CP_UTF8 || codePage == GB18030_CODE_PAGE; - DWORD const flags = restrictedCodePage ? - WC_ERR_INVALID_CHARS : WC_NO_BEST_FIT_CHARS; - BOOL usedDefault = FALSE; - BOOL* usedDefaultPointer = restrictedCodePage ? nullptr : &usedDefault; - int required = WideCharToMultiByte( - codePage, flags, value.c_str(), -1, - nullptr, 0, nullptr, usedDefaultPointer); - if (required <= 0 || usedDefault) - return false; - - std::vector buffer(static_cast(required)); - usedDefault = FALSE; - int written = WideCharToMultiByte( - codePage, flags, value.c_str(), -1, - buffer.data(), required, nullptr, usedDefaultPointer); - if (written <= 0 || usedDefault) - return false; - - converted.assign(buffer.data(), static_cast(written - 1)); - return true; -} - -bool IsUsableOpenSSLPath(const std::wstring& path, std::string& converted) -{ - if (!ConvertWideToAnsi(path, converted)) - return false; - - constexpr std::size_t LEGACY_SUFFIX_LENGTH = sizeof("\\legacy.dll") - 1; - return converted.size() + LEGACY_SUFFIX_LENGTH < MAX_PATH; -} - -bool ConvertForOpenSSL( - const std::filesystem::path& directory, std::string& converted) -{ - const std::wstring& nativePath = directory.native(); - if (IsUsableOpenSSLPath(nativePath, converted)) - return true; - - DWORD required = GetShortPathNameW(nativePath.c_str(), nullptr, 0); - if (required == 0) - return false; - - std::vector shortPath(required); - DWORD written = GetShortPathNameW( - nativePath.c_str(), shortPath.data(), required); - if (written == 0 || written >= shortPath.size()) - return false; - - return IsUsableOpenSSLPath( - std::wstring(shortPath.data(), written), converted); -} - -void ConfigureBundledProviderSearchPath() -{ - if (!ReadWindowsEnvironment(L"OPENSSL_MODULES").empty()) - return; - - try - { - std::filesystem::path executableDirectory = GetExecutableDirectory(); - if (executableDirectory.empty()) - { - sLog.outError( - "OpenSSLProvider: Failed to resolve the executable directory"); - return; - } - - std::filesystem::path providerDirectory = - executableDirectory / L"ossl-modules"; - std::filesystem::path legacyProvider = - providerDirectory / L"legacy.dll"; - std::error_code fileError; - if (!std::filesystem::is_regular_file(legacyProvider, fileError)) - return; - - std::string providerPath; - if (!ConvertForOpenSSL(providerDirectory, providerPath)) - { - sLog.outError( - "OpenSSLProvider: Bundled provider path cannot be represented " - "for the OpenSSL Windows loader"); - return; - } - - if (OSSL_PROVIDER_set_default_search_path( - nullptr, providerPath.c_str()) != 1) - { - sLog.outError( - "OpenSSLProvider: Failed to configure bundled provider path '%s'", - providerPath.c_str()); - } - } - catch (const std::filesystem::filesystem_error& error) - { - sLog.outError( - "OpenSSLProvider: Failed to inspect bundled provider path: %s", - error.what()); - } -} -#endif - -OpenSSLProvider LoadLegacyProvider() -{ -#ifdef WIN32 - ConfigureBundledProviderSearchPath(); -#endif - return OpenSSLProvider("legacy"); -} - -std::string OpenSSLModulesForDiagnostic() -{ -#ifdef WIN32 - std::wstring modules = ReadWindowsEnvironment(L"OPENSSL_MODULES"); - if (modules.empty()) - return ""; - - std::string converted; - return ConvertWideToAnsi(modules, converted) - ? converted : ""; -#else - const char* modules = std::getenv("OPENSSL_MODULES"); - return modules ? std::string(modules) : std::string(""); -#endif -} -} - -/** - * Loads the named OpenSSL provider into the specified library context. - */ -OpenSSLProvider::OpenSSLProvider(const char* name, OSSL_LIB_CTX* libraryContext) - : m_provider(nullptr), m_providerName(name ? name : "") -{ - if (!name) - { - sLog.outError("OpenSSLProvider: Provider name cannot be null"); - return; - } - - m_provider = OSSL_PROVIDER_load(libraryContext, name); - if (!m_provider) - { - sLog.outError("OpenSSLProvider: Failed to load provider '%s'", name); - } -} - -/** - * Unloads the owned OpenSSL provider instance. - */ -OpenSSLProvider::~OpenSSLProvider() -{ - if (m_provider) - { - OSSL_PROVIDER_unload(m_provider); - m_provider = nullptr; - } -} - -std::string OpenSSLProvider::Version() const -{ - if (!m_provider) - return {}; - - char* version = nullptr; - OSSL_PARAM params[] = { - OSSL_PARAM_construct_utf8_ptr( - OSSL_PROV_PARAM_VERSION, &version, 0), - OSSL_PARAM_construct_end() - }; - if (OSSL_PROVIDER_get_params(m_provider, params) != 1 || !version) - return {}; - return version; -} - -OpenSSLProvider::OpenSSLProvider(OpenSSLProvider&& other) noexcept - : m_provider(other.m_provider), m_providerName(std::move(other.m_providerName)) -{ - other.m_provider = nullptr; -} - -OpenSSLProvider& OpenSSLProvider::operator=(OpenSSLProvider&& other) noexcept -{ - if (this != &other) - { - // Clean up current provider - if (m_provider) - { - OSSL_PROVIDER_unload(m_provider); - } - - // Move from other - m_provider = other.m_provider; - m_providerName = std::move(other.m_providerName); - other.m_provider = nullptr; - } - return *this; -} - -/** - * Initializes the OpenSSL provider manager and loads required providers. - */ -OpenSSLProviderManager::OpenSSLProviderManager() - : m_legacyProvider(LoadLegacyProvider()), - m_defaultProvider("default"), - m_initialized(false) -{ - if (!m_legacyProvider.IsLoaded() || !m_defaultProvider.IsLoaded()) - { - sLog.outError("Failed to load OpenSSL 3.x providers"); - - if (!m_legacyProvider.IsLoaded()) - { - sLog.outError(" - Legacy provider failed to load"); -#ifdef WIN32 - sLog.outError(" Use a complete release with ossl-modules\\legacy.dll"); - sLog.outError(" beside the daemon, or set OPENSSL_MODULES to the"); - sLog.outError(" directory containing a matching legacy.dll."); -#endif - } - - if (!m_defaultProvider.IsLoaded()) - { - sLog.outError(" - Default provider failed to load"); - } - return; - } - - std::string legacyVersion = m_legacyProvider.Version(); - std::string defaultVersion = m_defaultProvider.Version(); - unsigned legacyMajor = 0; - unsigned defaultMajor = 0; - bool parsedLegacy = - ParseProviderMajor(legacyVersion, legacyMajor); - bool parsedDefault = - ParseProviderMajor(defaultVersion, defaultMajor); - - unsigned runtimeMajor = - unsigned((OpenSSL_version_num() >> 28) & 0x0f); - if (runtimeMajor != 3 - || !parsedLegacy || legacyMajor != runtimeMajor - || !parsedDefault || defaultMajor != runtimeMajor) - { - std::string modules = OpenSSLModulesForDiagnostic(); - sLog.outError( - "OpenSSL 3.x provider/runtime validation failed: " - "runtime='%s', legacy provider='%s', default provider='%s', " - "OPENSSL_MODULES='%s'", - OpenSSL_version(OPENSSL_VERSION), - legacyVersion.empty() ? "" : legacyVersion.c_str(), - defaultVersion.empty() ? "" : defaultVersion.c_str(), - modules.c_str()); - return; - } - - EVP_CIPHER* rc4 = EVP_CIPHER_fetch(nullptr, "RC4", nullptr); - if (!rc4) - { - sLog.outError( - "OpenSSL legacy provider is loaded but RC4 is unavailable"); - return; - } - EVP_CIPHER_free(rc4); - - m_initialized = true; - sLog.outString( - "OpenSSL 3.x providers loaded successfully: legacy %s, default %s", - legacyVersion.c_str(), defaultVersion.c_str()); -} - -OpenSSLProviderManager& OpenSSLProviderManager::Instance() -{ - static OpenSSLProviderManager instance; - return instance; -} - -/** - * Logs provider shutdown when the OpenSSL provider manager is destroyed. - */ -OpenSSLProviderManager::~OpenSSLProviderManager() -{ - if (m_initialized) - { - sLog.outString("OpenSSL 3.x providers unloaded"); - } -} - -#endif // OPENSSL_VERSION_MAJOR >= 3 diff --git a/src/shared/Auth/OpenSSLProvider.h b/src/shared/Auth/OpenSSLProvider.h deleted file mode 100644 index bc4ad7e7..00000000 --- a/src/shared/Auth/OpenSSLProvider.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * SPDX-License-Identifier: GPL-3.0-or-later - * - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2026 MaNGOS - * - * This program 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 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -/** - * @file OpenSSLProvider.h - * @brief RAII wrapper for OpenSSL 3.x providers - * - * This file provides RAII wrappers for OpenSSL provider management, - * ensuring proper cleanup and exception safety for OpenSSL 3.x - * provider loading and unloading. - */ - -#ifndef _AUTH_OPENSSL_PROVIDER_H -#define _AUTH_OPENSSL_PROVIDER_H - -#include -#include - -/** - * @brief RAII wrapper for EVP_CIPHER_CTX - * - * This class manages EVP_CIPHER_CTX lifecycle with proper - * initialization and cleanup, preventing memory leaks. - */ -class OpenSSLCipherContext -{ -public: - /** - * @brief Constructor - creates new cipher context - */ - OpenSSLCipherContext(); - - /** - * @brief Destructor - automatically frees context - */ - ~OpenSSLCipherContext(); - - /** - * @brief Get the underlying cipher context - * @return EVP_CIPHER_CTX pointer - */ - EVP_CIPHER_CTX* Get() const { return m_ctx; } - - /** - * @brief Check if context is valid - * @return true if context is valid, false otherwise - */ - bool IsValid() const { return m_ctx != nullptr; } - - /** - * @brief Move constructor - */ - OpenSSLCipherContext(OpenSSLCipherContext&& other) noexcept; - - /** - * @brief Move assignment operator - */ - OpenSSLCipherContext& operator=(OpenSSLCipherContext&& other) noexcept; - - // Delete copy operations - OpenSSLCipherContext(const OpenSSLCipherContext&) = delete; - OpenSSLCipherContext& operator=(const OpenSSLCipherContext&) = delete; - -private: - EVP_CIPHER_CTX* m_ctx; /**< OpenSSL cipher context */ -}; - -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) -#include - -/** - * @brief RAII wrapper for OpenSSL 3.x OSSL_PROVIDER - * - * This class automatically loads and unloads OpenSSL providers, - * ensuring proper cleanup when the wrapper goes out of scope. - * It provides exception safety and prevents resource leaks. - */ -class OpenSSLProvider -{ -public: - /** - * @brief Constructor - loads specified provider - * @param name Provider name (e.g., "legacy", "default") - * @param libraryContext Library context (NULL for default) - */ - OpenSSLProvider(const char* name, OSSL_LIB_CTX* libraryContext = nullptr); - - /** - * @brief Destructor - automatically unloads provider - */ - ~OpenSSLProvider(); - - /** - * @brief Check if provider is successfully loaded - * @return true if provider is loaded, false otherwise - */ - bool IsLoaded() const { return m_provider != nullptr; } - - /** - * @brief Get the version reported by the loaded provider - * @return Provider version, or an empty string if unavailable - */ - std::string Version() const; - - /** - * @brief Get the underlying provider handle - * @return OSSL_PROVIDER pointer or nullptr if not loaded - */ - OSSL_PROVIDER* Get() const { return m_provider; } - - /** - * @brief Move constructor - */ - OpenSSLProvider(OpenSSLProvider&& other) noexcept; - - /** - * @brief Move assignment operator - */ - OpenSSLProvider& operator=(OpenSSLProvider&& other) noexcept; - - // Delete copy operations to prevent double-free - OpenSSLProvider(const OpenSSLProvider&) = delete; - OpenSSLProvider& operator=(const OpenSSLProvider&) = delete; - -private: - OSSL_PROVIDER* m_provider; /**< OpenSSL provider handle */ - std::string m_providerName; /**< Provider name for logging */ -}; - -/** - * @brief RAII wrapper for OpenSSL 3.x provider management - * - * This class manages both legacy and default providers for OpenSSL 3.x, - * ensuring they are loaded and unloaded properly. It's designed to be - * used at application startup to handle provider initialization. - */ -class OpenSSLProviderManager -{ -public: - /** - * @brief Constructor - loads legacy and default providers - */ - OpenSSLProviderManager(); - - /** - * @brief Destructor - automatically unloads providers - */ - ~OpenSSLProviderManager(); - - /** - * @brief The one manager for the process - * - * Providers are global to the library, so loading them per object costs an - * OpenSSL-wide lock on every construction and buys nothing. - */ - static OpenSSLProviderManager& Instance(); - - /** - * @brief Check if providers are successfully loaded - * @return true if both providers loaded successfully - */ - bool IsInitialized() const { return m_initialized; } - - /** - * @brief Get legacy provider - * @return Reference to legacy provider - */ - const OpenSSLProvider& GetLegacyProvider() const { return m_legacyProvider; } - - /** - * @brief Get default provider - * @return Reference to default provider - */ - const OpenSSLProvider& GetDefaultProvider() const { return m_defaultProvider; } - -private: - OpenSSLProvider m_legacyProvider; /**< Legacy provider for compatibility */ - OpenSSLProvider m_defaultProvider; /**< Default provider */ - bool m_initialized; /**< Initialization status */ -}; - -#endif // OPENSSL_VERSION_MAJOR >= 3 - -#endif // _AUTH_OPENSSL_PROVIDER_H diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index efb51346..748f4023 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -30,8 +30,6 @@ set(SRC_GRP_AUTH Auth/HMACSHA1.cpp Auth/HMACSHA1.h Auth/Md5.h - Auth/OpenSSLProvider.cpp - Auth/OpenSSLProvider.h Auth/Sha1.cpp Auth/Sha1.h Auth/WardenKeyGeneration.h diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 32fb5153..ed4d50c7 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -42,7 +42,6 @@ set(SRC_GRP_TESTS SessionMailboxTest.cpp SessionProtocolPolicyTest.cpp DatabaseConcurrencyTest.cpp - OpenSSLProviderTest.cpp ClientParserTest.cpp TerrainModelTest.cpp TileSerializerTest.cpp @@ -149,26 +148,7 @@ if(WIN32) "${MANGOS_TEST_MYSQL_DLL_DIR}/${MANGOS_TEST_MYSQL_DLL_NAME}" "$") endif() - set_tests_properties(mangos_tests PROPERTIES - ENVIRONMENT "OPENSSL_MODULES=") - - # Match the release layout and leave OPENSSL_MODULES empty: provider discovery - # must work from ossl-modules beside the executable, not from the build host. - find_file(MANGOS_TEST_OPENSSL_LEGACY_DLL - NAMES legacy.dll - HINTS "${MANGOS_SSL_ROOT}" "${OPENSSL_ROOT_DIR}" - PATH_SUFFIXES bin/ossl-modules bin ossl-modules lib/ossl-modules "" - NO_DEFAULT_PATH) - - if(MANGOS_TEST_OPENSSL_LEGACY_DLL) - add_custom_command(TARGET mangos_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - "$/ossl-modules" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${MANGOS_TEST_OPENSSL_LEGACY_DLL}" - "$/ossl-modules/legacy.dll") - else() - message(WARNING - "OpenSSL legacy provider (legacy.dll) not found; mangos_tests may fail its RC4 cases.") - endif() + # No ossl-modules here, and no OPENSSL_MODULES to point at one. RC4 was the only + # thing this tree ever asked the legacy provider for, and it is in the tree now, so + # the tests need libcrypto and nothing beside it. endif() diff --git a/src/tests/OpenSSLProviderTest.cpp b/src/tests/OpenSSLProviderTest.cpp deleted file mode 100644 index 6ba96fea..00000000 --- a/src/tests/OpenSSLProviderTest.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/** - * SPDX-License-Identifier: GPL-3.0-or-later - * - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2026 MaNGOS - * - * This program 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 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -#include -#include "TestHarness.h" -#include "Auth/OpenSSLProvider.h" - -#include -#include -#include -#include - -TEST(OpenSSL_runtime_and_providers_are_major_three) -{ - const char* modules = std::getenv("OPENSSL_MODULES"); - std::string modulesBefore = modules ? modules : ""; - - OpenSSLProviderManager& manager = - OpenSSLProviderManager::Instance(); - - const char* modulesAfter = std::getenv("OPENSSL_MODULES"); - CHECK_STR(std::string(modulesAfter ? modulesAfter : ""), modulesBefore); - REQUIRE(manager.IsInitialized()); - - unsigned runtimeMajor = - unsigned((OpenSSL_version_num() >> 28) & 0x0f); - CHECK_EQ(runtimeMajor, 3); - - auto checkProvider = [runtimeMajor]( - const OpenSSLProvider& provider) - { - std::string version = provider.Version(); - REQUIRE(!version.empty()); - std::size_t separator = version.find('.'); - REQUIRE(separator != std::string::npos); - unsigned major = 0; - std::from_chars_result result = std::from_chars( - version.data(), version.data() + separator, major); - CHECK(result.ec == std::errc{}); - CHECK(result.ptr == version.data() + separator); - CHECK_EQ(major, runtimeMajor); - }; - - checkProvider(manager.GetLegacyProvider()); - checkProvider(manager.GetDefaultProvider()); -} - -TEST(OpenSSL_legacy_provider_supplies_rc4) -{ - REQUIRE(OpenSSLProviderManager::Instance().IsInitialized()); - EVP_CIPHER* rc4 = EVP_CIPHER_fetch(nullptr, "RC4", nullptr); - CHECK(rc4 != nullptr); - EVP_CIPHER_free(rc4); -}