filename:
src/network/session/SessionAuth.cpp
branch:
feature/world
back to repo
/*
*
* _____ _ _
* / ___|| | | |
* \ `--. | |_ _ __ __ _ | |_ ___ ___
* `--. \| __|| '__| / _` || __| / _ \ / __|
* /\__/ /| |_ | | | (_| || |_ | (_) |\__ \
* \____/ \__||_| \__,_| \__| \___/ |___/
*
*
* 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 3 of the License, or
* (at your option) any later version.
*
* Copyright (C) 2025 Armen Deroian
*
*/
#include "SessionAuth.h"
#include "cpr/session.h"
#include "network/Network.h"
#include "NetworkClient.h"
#include "utils/UUID.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
#include <iomanip>
#include <openssl/sha.h>
void stratos::authenticate(NetworkConnection* connection, const std::string& serverId, const std::vector<uint8_t>& secret, const EVPKeyPtr& pubKey) {
const std::string loginHash = generateMinecraftSha1HexDigest(serverId, secret, pubKey);
const auto session = std::make_shared<cpr::Session>();
session->SetUrl(getAuthenticationUrl(connection->getSessionInfo()->get().username, loginHash, ""));//connection->getNetwork()->getAddress()));
session->SetHeader({{"User-Agent", "Stratos/1.0"}});
session->SetHeader({{"Content-Type", "application/json"}});
session->SetTimeout(std::chrono::seconds(10));
std::thread([s = session, conn = connection] {
cpr::Response r = s->Get();
if (conn && !conn->isDisconnected()) {
if (r.status_code == 200) {
auto body = json::parse(r.text);
UUID id = UUIDFromString(body["id"]);
std::string name = body["name"];
conn->updateSessionInfo({name, id});
conn->getLogger()->info("Successfully authenticated user {} with UUID {}", conn->getSessionInfo()->get().username, UUIDToString(id));
std::vector<LoginProperty> properties = {{body["properties"][0]["name"], body["properties"][0]["value"], body["properties"][0]["signature"]}};
conn->sendPacket(std::make_unique<LoginSuccess>(id, std::move(name), std::move(properties)));
} else {
conn->getLogger()->error("Failed to authenticate user {}: HTTP {} - {}", conn->getSessionInfo()->get().username, r.status_code, r.error.message);
conn->disconnect("Authentication failed");
}
}
}).detach();
}
cpr::Url stratos::getAuthenticationUrl(const std::string& username, const std::string& loginHash, const std::string& serverIp) {
return {"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + username + "&serverId=" + loginHash + "&ip=" + serverIp};
}
std::string stratos::generateMinecraftSha1HexDigest(const std::string& serverId, const std::vector<uint8_t>& secret, const EVPKeyPtr& pubKey) {
SHA_CTX ctx;
SHA1_Init(&ctx);
SHA1_Update(&ctx, serverId.data(), serverId.size());
SHA1_Update(&ctx, secret.data(), secret.size());
const std::vector<uint8_t> pubKeyDER = encodeServerPublicKey(&pubKey);
SHA1_Update(&ctx, pubKeyDER.data(), pubKeyDER.size());
uint8_t hash[SHA_DIGEST_LENGTH];
SHA1_Final(hash, &ctx);
bool isNegative = (hash[0] & 0x80) != 0;
std::vector num(hash, hash + SHA_DIGEST_LENGTH);
if (isNegative) {
for (auto& b : num) b = ~b;
for (int i = SHA_DIGEST_LENGTH - 1; i >= 0; --i) {
if (++num[i] != 0) break;
}
}
std::ostringstream oss;
if (isNegative) oss << "-";
bool leading = true;
for (uint8_t byte : num) {
if (leading && byte == 0) continue;
leading = false;
oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(byte);
}
if (oss.str().empty() || oss.str() == "-")
oss << "0";
return oss.str();
}