automatically determine the correct BB private key for each client

This commit is contained in:
Martin Michelsen
2022-05-04 14:57:02 -07:00
parent 294c328e7a
commit d5c38c2bc5
12 changed files with 194 additions and 63 deletions
+2
View File
@@ -17,6 +17,8 @@ endif()
include_directories("/usr/local/include") include_directories("/usr/local/include")
link_directories("/usr/local/lib") link_directories("/usr/local/lib")
set(CMAKE_BUILD_TYPE Debug)
# Executable definitions # Executable definitions
+2 -2
View File
@@ -74,8 +74,8 @@ struct Client {
uint16_t flags; uint16_t flags;
// Encryption // Encryption
std::unique_ptr<PSOEncryption> crypt_in; std::shared_ptr<PSOEncryption> crypt_in;
std::unique_ptr<PSOEncryption> crypt_out; std::shared_ptr<PSOEncryption> crypt_out;
// Network // Network
struct sockaddr_storage local_addr; struct sockaddr_storage local_addr;
+13 -9
View File
@@ -239,18 +239,22 @@ void populate_state_from_config(shared_ptr<ServerState> s,
s->allow_unregistered_users = true; s->allow_unregistered_users = true;
} }
{ for (const string& filename : list_directory("system/blueburst/keys")) {
string key_file_name = d.at("BlueBurstKeyFile")->as_string(); if (!ends_with(filename, ".nsk")) {
string key_file_contents = load_file("system/blueburst/keys/" + key_file_name + ".nsk"); continue;
if (key_file_contents.size() != sizeof(PSOBBEncryption::KeyFile)) { }
log(WARNING, "Blue Burst key file is the wrong size (%zu bytes; should be %zu bytes)", string contents = load_file("system/blueburst/keys/" + filename);
key_file_contents.size(), sizeof(PSOBBEncryption::KeyFile)); if (contents.size() != sizeof(PSOBBEncryption::KeyFile)) {
log(WARNING, "Ignoring key file; Blue Burst clients will not be able to connect"); 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 { } else {
memcpy(&s->default_key_file, key_file_contents.data(), sizeof(PSOBBEncryption::KeyFile)); shared_ptr<PSOBBEncryption::KeyFile> k(new PSOBBEncryption::KeyFile());
log(INFO, "Loaded Blue Burst key file: %s", key_file_name.c_str()); 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 { try {
bool run_shell = d.at("RunInteractiveShell")->as_bool(); bool run_shell = d.at("RunInteractiveShell")->as_bool();
+73
View File
@@ -281,6 +281,8 @@ PSOBBEncryption::PSOBBEncryption(
const KeyFile& key, const void* original_seed, size_t seed_size) const KeyFile& key, const void* original_seed, size_t seed_size)
: stream(this->generate_stream(key, original_seed, seed_size)) { } : stream(this->generate_stream(key, original_seed, seed_size)) { }
PSOBBEncryption::PSOBBEncryption() { }
vector<uint32_t> PSOBBEncryption::generate_stream( vector<uint32_t> PSOBBEncryption::generate_stream(
@@ -506,3 +508,74 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
return 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
View File
@@ -79,8 +79,51 @@ public:
virtual void skip(size_t size); virtual void skip(size_t size);
protected: protected:
PSOBBEncryption();
static std::vector<uint32_t> generate_stream( static std::vector<uint32_t> generate_stream(
const KeyFile& key, const void* seed, size_t seed_size); 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;
}; };
+8 -4
View File
@@ -223,10 +223,14 @@ static bool process_server_bb_03(shared_ptr<ServerState> s,
// BB encryption is stateless after it's initialized, unlike previous // BB encryption is stateless after it's initialized, unlike previous
// versions, so we can get away with only two instances instead of four here. // versions, so we can get away with only two instances instead of four here.
session.server_input_crypt.reset(new PSOBBEncryption( // This is convenient because the two encryptions are linked together due to
s->default_key_file, cmd.server_key.data(), sizeof(cmd.server_key))); // our use of multiple private keys, unlike for the other versions.
session.server_output_crypt.reset(new PSOBBEncryption( static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
s->default_key_file, cmd.client_key.data(), sizeof(cmd.client_key))); 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_input_crypt = session.server_output_crypt;
session.client_output_crypt = session.server_input_crypt; session.client_output_crypt = session.server_input_crypt;
+10
View File
@@ -575,6 +575,7 @@ void ProxyServer::LinkedSession::disconnect() {
void ProxyServer::LinkedSession::on_client_input() { void ProxyServer::LinkedSession::on_client_input() {
try {
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(), for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) { [&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(), print_received_command(command, flag, data.data(), data.size(),
@@ -587,9 +588,14 @@ void ProxyServer::LinkedSession::on_client_input() {
flag, flag,
data); 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() { void ProxyServer::LinkedSession::on_server_input() {
try {
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(), for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) { [&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(), print_received_command(command, flag, data.data(), data.size(),
@@ -602,6 +608,10 @@ void ProxyServer::LinkedSession::on_server_input() {
flag, flag,
data); 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( void ProxyServer::LinkedSession::send_to_end(
+5 -4
View File
@@ -207,10 +207,11 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
cmd.after_message = anti_copyright; cmd.after_message = anti_copyright;
send_command(c, 0x03, 0x00, cmd); send_command(c, 0x03, 0x00, cmd);
c->crypt_out.reset(new PSOBBEncryption(s->default_key_file, static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
cmd.server_key.data(), cmd.server_key.bytes())); shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
c->crypt_in.reset(new PSOBBEncryption(s->default_key_file, s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
cmd.client_key.data(), cmd.client_key.bytes())); 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) { void send_server_init_patch(shared_ptr<Client> c) {
+4 -4
View File
@@ -38,9 +38,9 @@ void Server::disconnect_client(shared_ptr<Client> c) {
int fd = bufferevent_getfd(bev); int fd = bufferevent_getfd(bev);
if (fd < 0) { 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 { } 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 // 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) { void Server::receive_and_process_commands(shared_ptr<Client> c) {
try {
for_each_received_command(c->bev, c->version, c->crypt_in.get(), for_each_received_command(c->bev, c->version, c->crypt_in.get(),
[this, c](uint16_t command, uint16_t flag, const std::string& data) { [this, c](uint16_t command, uint16_t flag, const std::string& data) {
try {
process_command(this->state, c, command, flag, data); process_command(this->state, c, command, flag, data);
});
} catch (const exception& e) { } catch (const exception& e) {
this->log(INFO, "Error in client stream: %s", e.what()); this->log(INFO, "Error in client stream: %s", e.what());
c->should_disconnect = true; c->should_disconnect = true;
return; return;
} }
});
} }
Server::Server( Server::Server(
-2
View File
@@ -20,8 +20,6 @@ ServerState::ServerState()
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1), run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
pre_lobby_event(0), pre_lobby_event(0),
ep3_menu_song(-1) { ep3_menu_song(-1) {
memset(&this->default_key_file, 0, sizeof(this->default_key_file));
vector<shared_ptr<Lobby>> ep3_only_lobbies; vector<shared_ptr<Lobby>> ep3_only_lobbies;
for (size_t x = 0; x < 20; x++) { for (size_t x = 0; x < 20; x++) {
+1 -1
View File
@@ -44,7 +44,7 @@ struct ServerState {
bool ip_stack_debug; bool ip_stack_debug;
bool allow_unregistered_users; bool allow_unregistered_users;
RunShellBehavior run_shell_behavior; 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 QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table; std::shared_ptr<const LevelTable> level_table;
std::shared_ptr<const BattleParamTable> battle_params; std::shared_ptr<const BattleParamTable> battle_params;
-4
View File
@@ -49,10 +49,6 @@
// users cannot be banned! // users cannot be banned!
"AllowUnregisteredUsers": false, "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 // User to run the server as. If present, newserv will attempt to switch to
// this user's permissions after loading its configuration and opening // this user's permissions after loading its configuration and opening
// listening sockets. The special value $SUDO_USER causes newserv to look up // listening sockets. The special value $SUDO_USER causes newserv to look up