#include "password.h" #include "Extension/Multiplayer/Net/protocol.h" #ifdef _WIN32 #include #include #else #include #include #include #include #endif #include #include namespace dingosdk::multiplayer { #ifdef _WIN32 namespace { void check(NTSTATUS result) { if (result < 0) throw std::runtime_error("Lobby password cryptography unavailable."); } struct Algorithm { BCRYPT_ALG_HANDLE handle{}; Algorithm() { check(BCryptOpenAlgorithmProvider(&handle, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG)); } ~Algorithm() { if (handle) BCryptCloseAlgorithmProvider(handle, 0); } }; } // namespace #else namespace { void secure_zero(void *data, std::size_t size) noexcept { volatile unsigned char *p = static_cast(data); while (size--) *p++ = 0; } } // namespace #endif std::optional password_key(std::string_view password, std::uint64_t session) { if (password.empty()) return {}; if (password.size() > 64 || !session) throw std::runtime_error("Lobby passwords must be 1-64 UTF-8 bytes."); std::array salt{'R', 'e', 'S', 'k', 'a', 't', 'e', static_cast(protocol_version)}; for (unsigned i = 0; i < 8; ++i) salt[8 + i] = static_cast(session >> (i * 8)); PasswordKey key; #ifdef _WIN32 Algorithm algorithm; check(BCryptDeriveKeyPBKDF2( algorithm.handle, reinterpret_cast(const_cast(password.data())), static_cast(password.size()), salt.data(), static_cast(salt.size()), 100000, key.data(), static_cast(key.size()), 0)); #else if (PKCS5_PBKDF2_HMAC(password.data(), static_cast(password.size()), salt.data(), static_cast(salt.size()), 100000, EVP_sha256(), static_cast(key.size()), key.data()) != 1) throw std::runtime_error("Lobby password cryptography unavailable."); #endif return key; } PasswordKey password_proof(const PasswordKey &key, std::uint64_t session, std::uint64_t map, std::uint64_t host, std::uint64_t guest, std::uint64_t host_epoch, std::uint64_t guest_epoch, std::uint64_t challenge) { std::vector message{'R', 'e', 'S', 'k', 'a', 't', 'e', 'P', 'r', 'o', 'o', 'f', static_cast(protocol_version)}; for (const auto v : {session, map, host, guest, host_epoch, guest_epoch, challenge}) for (unsigned i = 0; i < 8; ++i) message.push_back(static_cast(v >> (i * 8))); #ifdef _WIN32 Algorithm algorithm; struct Hash { BCRYPT_HASH_HANDLE handle{}; ~Hash() { if (handle) BCryptDestroyHash(handle); } } hash; check(BCryptCreateHash(algorithm.handle, &hash.handle, nullptr, 0, const_cast(key.data()), static_cast(key.size()), 0)); check(BCryptHashData(hash.handle, message.data(), static_cast(message.size()), 0)); PasswordKey proof; check(BCryptFinishHash(hash.handle, proof.data(), static_cast(proof.size()), 0)); return proof; #else PasswordKey proof; unsigned int length = 0; if (!HMAC(EVP_sha256(), key.data(), static_cast(key.size()), message.data(), message.size(), proof.data(), &length) || length != proof.size()) throw std::runtime_error("Lobby password cryptography unavailable."); return proof; #endif } bool proof_matches(const PasswordKey &a, const PasswordKey &b) noexcept { volatile unsigned difference = 0; for (std::size_t i = 0; i < a.size(); ++i) difference = difference | (a[i] ^ b[i]); return difference == 0; } void erase_password(std::string &text) noexcept { if (!text.empty()) #ifdef _WIN32 SecureZeroMemory(text.data(), text.size()); #else secure_zero(text.data(), text.size()); #endif text.clear(); } void erase_key(std::optional &key) noexcept { if (key) #ifdef _WIN32 SecureZeroMemory(key->data(), key->size()); #else secure_zero(key->data(), key->size()); #endif key.reset(); } } // namespace dingosdk::multiplayer