automatically determine the correct BB private key for each client
This commit is contained in:
@@ -17,6 +17,8 @@ endif()
|
||||
include_directories("/usr/local/include")
|
||||
link_directories("/usr/local/lib")
|
||||
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
|
||||
|
||||
|
||||
# Executable definitions
|
||||
|
||||
+2
-2
@@ -74,8 +74,8 @@ struct Client {
|
||||
uint16_t flags;
|
||||
|
||||
// Encryption
|
||||
std::unique_ptr<PSOEncryption> crypt_in;
|
||||
std::unique_ptr<PSOEncryption> crypt_out;
|
||||
std::shared_ptr<PSOEncryption> crypt_in;
|
||||
std::shared_ptr<PSOEncryption> crypt_out;
|
||||
|
||||
// Network
|
||||
struct sockaddr_storage local_addr;
|
||||
|
||||
+13
-9
@@ -239,18 +239,22 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
||||
s->allow_unregistered_users = true;
|
||||
}
|
||||
|
||||
{
|
||||
string key_file_name = d.at("BlueBurstKeyFile")->as_string();
|
||||
string key_file_contents = load_file("system/blueburst/keys/" + key_file_name + ".nsk");
|
||||
if (key_file_contents.size() != sizeof(PSOBBEncryption::KeyFile)) {
|
||||
log(WARNING, "Blue Burst key file is the wrong size (%zu bytes; should be %zu bytes)",
|
||||
key_file_contents.size(), sizeof(PSOBBEncryption::KeyFile));
|
||||
log(WARNING, "Ignoring key file; Blue Burst clients will not be able to connect");
|
||||
for (const string& filename : list_directory("system/blueburst/keys")) {
|
||||
if (!ends_with(filename, ".nsk")) {
|
||||
continue;
|
||||
}
|
||||
string contents = load_file("system/blueburst/keys/" + filename);
|
||||
if (contents.size() != sizeof(PSOBBEncryption::KeyFile)) {
|
||||
log(WARNING, "Blue Burst key file %s is the wrong size (%zu bytes; should be %zu bytes)",
|
||||
filename.c_str(), contents.size(), sizeof(PSOBBEncryption::KeyFile));
|
||||
} else {
|
||||
memcpy(&s->default_key_file, key_file_contents.data(), sizeof(PSOBBEncryption::KeyFile));
|
||||
log(INFO, "Loaded Blue Burst key file: %s", key_file_name.c_str());
|
||||
shared_ptr<PSOBBEncryption::KeyFile> k(new PSOBBEncryption::KeyFile());
|
||||
memcpy(k.get(), contents.data(), sizeof(PSOBBEncryption::KeyFile));
|
||||
s->bb_private_keys.emplace_back(k);
|
||||
log(INFO, "Loaded Blue Burst key file: %s", filename.c_str());
|
||||
}
|
||||
}
|
||||
log(INFO, "%zu Blue Burst key file(s) loaded", s->bb_private_keys.size());
|
||||
|
||||
try {
|
||||
bool run_shell = d.at("RunInteractiveShell")->as_bool();
|
||||
|
||||
@@ -281,6 +281,8 @@ PSOBBEncryption::PSOBBEncryption(
|
||||
const KeyFile& key, const void* original_seed, size_t seed_size)
|
||||
: stream(this->generate_stream(key, original_seed, seed_size)) { }
|
||||
|
||||
PSOBBEncryption::PSOBBEncryption() { }
|
||||
|
||||
|
||||
|
||||
vector<uint32_t> PSOBBEncryption::generate_stream(
|
||||
@@ -506,3 +508,74 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
|
||||
PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption(
|
||||
const vector<shared_ptr<const KeyFile>>& possible_keys,
|
||||
const string& expected_first_data,
|
||||
const void* seed,
|
||||
size_t seed_size)
|
||||
: possible_keys(possible_keys),
|
||||
expected_first_data(expected_first_data),
|
||||
seed(reinterpret_cast<const char*>(seed), seed_size) { }
|
||||
|
||||
void PSOBBMultiKeyClientEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
if (this->stream.empty()) {
|
||||
throw logic_error("PSOBB multi-key encryption requires client input first");
|
||||
}
|
||||
this->PSOBBEncryption::encrypt(data, size, advance);
|
||||
}
|
||||
|
||||
void PSOBBMultiKeyClientEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||
if (this->stream.empty()) {
|
||||
if (size != this->expected_first_data.size()) {
|
||||
throw logic_error("initial decryption size does not match expected first data size");
|
||||
}
|
||||
|
||||
for (const auto& key : this->possible_keys) {
|
||||
this->active_key = key;
|
||||
this->stream = PSOBBEncryption::generate_stream(
|
||||
*this->active_key.get(), this->seed.data(), this->seed.size());
|
||||
string test_data(reinterpret_cast<const char*>(data), size);
|
||||
this->PSOBBEncryption::decrypt(test_data.data(), test_data.size());
|
||||
if (test_data == this->expected_first_data) {
|
||||
break;
|
||||
}
|
||||
this->active_key.reset();
|
||||
this->stream.clear();
|
||||
}
|
||||
if (!this->active_key.get()) {
|
||||
throw runtime_error("none of the registered private keys are valid for this client");
|
||||
}
|
||||
}
|
||||
this->PSOBBEncryption::decrypt(data, size, advance);
|
||||
}
|
||||
|
||||
PSOBBMultiKeyServerEncryption::PSOBBMultiKeyServerEncryption(
|
||||
shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt,
|
||||
const void* seed,
|
||||
size_t seed_size)
|
||||
: client_crypt(client_crypt),
|
||||
seed(reinterpret_cast<const char*>(seed), seed_size) { }
|
||||
|
||||
void PSOBBMultiKeyServerEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
this->ensure_stream_ready();
|
||||
this->PSOBBEncryption::encrypt(data, size, advance);
|
||||
}
|
||||
|
||||
void PSOBBMultiKeyServerEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||
this->ensure_stream_ready();
|
||||
this->PSOBBEncryption::decrypt(data, size, advance);
|
||||
}
|
||||
|
||||
void PSOBBMultiKeyServerEncryption::ensure_stream_ready() {
|
||||
if (this->stream.empty()) {
|
||||
if (!this->client_crypt->active_key.get()) {
|
||||
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||
}
|
||||
log(INFO, "[PSOBB/MK] Generating server stream");
|
||||
this->stream = PSOBBEncryption::generate_stream(
|
||||
*this->client_crypt->active_key, this->seed.data(), this->seed.size());
|
||||
}
|
||||
}
|
||||
|
||||
+44
-1
@@ -79,8 +79,51 @@ public:
|
||||
virtual void skip(size_t size);
|
||||
|
||||
protected:
|
||||
PSOBBEncryption();
|
||||
|
||||
static std::vector<uint32_t> generate_stream(
|
||||
const KeyFile& key, const void* seed, size_t seed_size);
|
||||
|
||||
const std::vector<uint32_t> stream;
|
||||
std::vector<uint32_t> stream;
|
||||
};
|
||||
|
||||
// The following classes provide support for multiple PSOBB private keys, and
|
||||
// the ability to automatically detect which key the client is using based on
|
||||
// the first 8 bytes they send.
|
||||
|
||||
class PSOBBMultiKeyClientEncryption : public PSOBBEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyClientEncryption(
|
||||
const std::vector<std::shared_ptr<const KeyFile>>& possible_keys,
|
||||
const std::string& expected_first_data,
|
||||
const void* seed,
|
||||
size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
friend class PSOBBMultiKeyServerEncryption;
|
||||
|
||||
protected:
|
||||
std::vector<std::shared_ptr<const KeyFile>> possible_keys;
|
||||
std::shared_ptr<const KeyFile> active_key;
|
||||
std::string expected_first_data;
|
||||
std::string seed;
|
||||
};
|
||||
|
||||
class PSOBBMultiKeyServerEncryption : public PSOBBEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyServerEncryption(
|
||||
std::shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt,
|
||||
const void* seed,
|
||||
size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
protected:
|
||||
void ensure_stream_ready();
|
||||
|
||||
std::shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt;
|
||||
std::string seed;
|
||||
};
|
||||
|
||||
@@ -223,10 +223,14 @@ static bool process_server_bb_03(shared_ptr<ServerState> s,
|
||||
|
||||
// BB encryption is stateless after it's initialized, unlike previous
|
||||
// versions, so we can get away with only two instances instead of four here.
|
||||
session.server_input_crypt.reset(new PSOBBEncryption(
|
||||
s->default_key_file, cmd.server_key.data(), sizeof(cmd.server_key)));
|
||||
session.server_output_crypt.reset(new PSOBBEncryption(
|
||||
s->default_key_file, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||
// This is convenient because the two encryptions are linked together due to
|
||||
// our use of multiple private keys, unlike for the other versions.
|
||||
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
|
||||
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||
session.server_input_crypt.reset(new PSOBBMultiKeyServerEncryption(
|
||||
client_encr, cmd.server_key.data(), sizeof(cmd.server_key)));
|
||||
session.server_output_crypt = client_encr;
|
||||
session.client_input_crypt = session.server_output_crypt;
|
||||
session.client_output_crypt = session.server_input_crypt;
|
||||
|
||||
|
||||
+34
-24
@@ -575,33 +575,43 @@ void ProxyServer::LinkedSession::disconnect() {
|
||||
|
||||
|
||||
void ProxyServer::LinkedSession::on_client_input() {
|
||||
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->client_name.c_str());
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
false, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
try {
|
||||
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->client_name.c_str());
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
false, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
this->log(ERROR, "Failed to process command from client: %s", e.what());
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::on_server_input() {
|
||||
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
true, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
try {
|
||||
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
true, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
this->log(ERROR, "Failed to process command from server: %s", e.what());
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_end(
|
||||
|
||||
+5
-4
@@ -207,10 +207,11 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
cmd.after_message = anti_copyright;
|
||||
send_command(c, 0x03, 0x00, cmd);
|
||||
|
||||
c->crypt_out.reset(new PSOBBEncryption(s->default_key_file,
|
||||
cmd.server_key.data(), cmd.server_key.bytes()));
|
||||
c->crypt_in.reset(new PSOBBEncryption(s->default_key_file,
|
||||
cmd.client_key.data(), cmd.client_key.bytes()));
|
||||
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
|
||||
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||
c->crypt_in = client_encr;
|
||||
c->crypt_out.reset(new PSOBBMultiKeyServerEncryption(client_encr, cmd.server_key.data(), sizeof(cmd.server_key)));
|
||||
}
|
||||
|
||||
void send_server_init_patch(shared_ptr<Client> c) {
|
||||
|
||||
+12
-12
@@ -38,9 +38,9 @@ void Server::disconnect_client(shared_ptr<Client> c) {
|
||||
|
||||
int fd = bufferevent_getfd(bev);
|
||||
if (fd < 0) {
|
||||
this->log(INFO, "[Server] Client on virtual connection %p disconnected", bev);
|
||||
this->log(INFO, "Client on virtual connection %p disconnected", bev);
|
||||
} else {
|
||||
this->log(INFO, "[Server] Client on fd %d disconnected", fd);
|
||||
this->log(INFO, "Client on fd %d disconnected", fd);
|
||||
}
|
||||
|
||||
// if the output buffer is not empty, move the client into the draining pool
|
||||
@@ -208,16 +208,16 @@ void Server::on_disconnecting_client_error(struct bufferevent* bev,
|
||||
}
|
||||
|
||||
void Server::receive_and_process_commands(shared_ptr<Client> c) {
|
||||
for_each_received_command(c->bev, c->version, c->crypt_in.get(),
|
||||
[this, c](uint16_t command, uint16_t flag, const std::string& data) {
|
||||
try {
|
||||
process_command(this->state, c, command, flag, data);
|
||||
} catch (const exception& e) {
|
||||
this->log(INFO, "Error in client stream: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
try {
|
||||
for_each_received_command(c->bev, c->version, c->crypt_in.get(),
|
||||
[this, c](uint16_t command, uint16_t flag, const std::string& data) {
|
||||
process_command(this->state, c, command, flag, data);
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
this->log(INFO, "Error in client stream: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Server::Server(
|
||||
|
||||
@@ -20,8 +20,6 @@ ServerState::ServerState()
|
||||
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
|
||||
pre_lobby_event(0),
|
||||
ep3_menu_song(-1) {
|
||||
memset(&this->default_key_file, 0, sizeof(this->default_key_file));
|
||||
|
||||
vector<shared_ptr<Lobby>> ep3_only_lobbies;
|
||||
|
||||
for (size_t x = 0; x < 20; x++) {
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ struct ServerState {
|
||||
bool ip_stack_debug;
|
||||
bool allow_unregistered_users;
|
||||
RunShellBehavior run_shell_behavior;
|
||||
PSOBBEncryption::KeyFile default_key_file;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table;
|
||||
std::shared_ptr<const BattleParamTable> battle_params;
|
||||
|
||||
@@ -49,10 +49,6 @@
|
||||
// users cannot be banned!
|
||||
"AllowUnregisteredUsers": false,
|
||||
|
||||
// If you want to use Blue Burst clients with a different private key, put a
|
||||
// .nsk file in system/blueburst/keys and put its name here.
|
||||
"BlueBurstKeyFile": "default",
|
||||
|
||||
// User to run the server as. If present, newserv will attempt to switch to
|
||||
// this user's permissions after loading its configuration and opening
|
||||
// listening sockets. The special value $SUDO_USER causes newserv to look up
|
||||
|
||||
Reference in New Issue
Block a user