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")
link_directories("/usr/local/lib")
set(CMAKE_BUILD_TYPE Debug)
# Executable definitions
+2 -2
View File
@@ -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
View File
@@ -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();
+73
View File
@@ -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
View File
@@ -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;
};
+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
// 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
View File
@@ -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
View File
@@ -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
View File
@@ -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(
-2
View File
@@ -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
View File
@@ -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;
-4
View File
@@ -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