#include "password.h" #include "Extension/Multiplayer/Net/protocol.h" #include #include #include #include namespace dingosdk::multiplayer { 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 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."); Algorithm algorithm; 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; 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)); 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) { 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)); 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))); 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; } 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()) SecureZeroMemory(text.data(), text.size()); text.clear(); } void erase_key(std::optional &key) noexcept { if (key) SecureZeroMemory(key->data(), key->size()); key.reset(); } } // namespace dingosdk::multiplayer