Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save nikita-petko/11eb0c012992bb567be34dd7fcc03daf to your computer and use it in GitHub Desktop.

Select an option

Save nikita-petko/11eb0c012992bb567be34dd7fcc03daf to your computer and use it in GitHub Desktop.
server_authentication_initializer_processor.fx
#define FX_125
namespace com.mfdlabs.ba.authentication.server;
/*
This handles authentication initialization for the packet session.
*/
#include <com.threading>
#include <com.io>
#include <com.mfdlabs.ba.moderation>
#include <com.networking.replication>
#include <com.mfdlabs.ba.identification.server>
#include <com.mfdlabs.ba.crypto>
#include <com.mfdlabs.ba.settings.server>
#include <com.l22.packages.generation2>
#include <com.diagnostics.generation2>
#include <com.mfdlabs.clients.games.models.v1>
#define SERVER_AUTHENTICATOR_UNSUPPORTED_CIPHER_SUITE \
"The server '%s' does not support any of the following cipher suites: %s."
#define SERVER_AUTHENTICATOR_UNSUPPORTED_COMPRESSION_METHOD \
"The server '%s' does not support any of the following compression methods: %s."
#if FEATURE_REPLICATION_STRUCT_MODELS
using namespace com::mfdlabs::ba::crypto;
/* Loosely based on TLSv1.3 ClientHello */
[[private, volatile_allocation, replication_struct<inbound>]]
struct crypto_exchange_client
{
[[replication_string]]
com::string random;
[[replication_string]]
com::string session_id
[[replication_vector<cyper_suite>(1, -1)]]
cyper_suite cipher_suites[];
[[replication_vector<compression_method>(1, -1)]]
compression_method compression_methods[]
[[replication_string]]
com::string server_id; // client can specify and server will verify if present.
[[replication_vector<key_share>(1, -1)]]
key_share key_share[];
}
/* Loosely based on TLSv1.3 ServerHello */
[[private, volatile_allocation, replication_struct<outbound>]]
struct crypto_exchange_server
{
[[replication_string]]
com::string random;
[[replication_string]]
com::string session_id;
[[replication_integer<cyper_suite>]]
cyper_suite cipher_suite;
[[replication_integer<compression_method>]]
compression_method compression_method;
[[replication_vector<key_share>(1, -1)]]
key_share key_share[];
};
#endif
using namespace com::networking::replication;
using namespace com::mfdlabs::ba::moderation;
enum authentication_packet_type
{
// Handshake
// Crypto Algorithms for the encryption of every packet after this.
exchange_crypto = 0816,
// Server stream certificate down for client to
// verify to it's own.
server_certificate = 0817,
// Server signs some data to verify both
// client and server have mutual data.
server_certificate_verify_ack = 0820,
// Client acknowledges the server certificate with the verification signature.
certificate_verify_ack = 0821,
// Exchange of User Information
client_identification = 0822, // REPL-81766: Asks if this should only include
// the UID and server fetches info.
// feature/repl-81766-server-authenticator-cid-by-server
// implements a POC for this.
server_identification = 0823,
}
[[atomic, best_allocation, best_performance]]
class server_authentication_initializer : public replication_processor_shared // inherit from this so you don't have to impl custom methods.
{
private:
static void block_and_disconnect(this packet* packet)
{
moderation_provider::request_urgent_network_level_block(
packet: packet,
withMetrics: true
);
}
static void block_and_disconnect(this packet* packet, com::string reason)
{
packet->disconnect_for_message(disconnect_reason::blocked, reason);
moderation_provider::request_urgent_network_level_block(
packet: packet,
withMetrics: true,
disconnectNow: false,
);
}
public:
virtual auto on_received(^packet, ^next) noexcept override
{
packet->block(block_condition::until_ctx_next);
this->bind_to_disconnect([packet]() -> void
{
crypto_provider::destroy_session_if_exists(packet->session);
});
/* Maybe in the future don't NETBLOCK them? Could have been a transit error. */
if (packet->empty())
{
packet->block_and_disconnect();
return;
}
switch (packet->read_id_once())
{
case authentication_packet_type::exchange_crypto:
// Check ordering. Block is allowed here because normal people don't go out of order like this.
if (this->out_of_order(authentication_packet_type::exchange_crypto, replication_packet_type::initial_begin)
return packet->block_and_disconnect();
this->set_concurrent_outbound_packet_count(3); // needs both server_certificate and server_certificate_verify
// we split up the packets because we don't want to send the certificate
// and the verify packet at the same time as there is a size limit on packets
// and the client doesn't know what frame to expect the verify packet in.
// This accounts for exchange_crypto, server_certificate, and server_certificate_verify.
#if FEATURE_REPLICATION_STRUCT_MODELS
crypto_exchange_client data;
if (!replication_struct::read(packet, &data))
{
// NSERV-276611: Implement debug reasons for disconnect.
return packet->block_and_disconnect();
}
if (!data.server_id.empty())
{
if (!server_identifier::check_id(data.server_id))
{
this->disconnect_for(disconnect_reason::server_id_mismatch);
return;
}
}
auto* cipher_suite = crypto_provider::get_supported_cipher_suite(data.cipher_suites);
if (cipher_suite == nullptr && !settings_provider::current()->get("DebugSupportNoEncryption"))
{
auto client_ciper_suites_formatted = consider::combine(data.cipher_suites, ", ", ^::to_string);
auto server_id = server_identifier::get_id();
auto message = com::string::format(SERVER_AUTHENTICATOR_UNSUPPORTED_CIPHER_SUITE, server_id, client_ciper_suites_formatted);
return this->disconnect_for_message(disconnect_reason::unknown_client_data, message);
}
auto* compression_method = crypto_provider::get_supported_compression_method(data.compression_methods);
if (compression_method == nullptr && !settings_provider::current()->get("DebugSupportNoEncryption"))
{
auto client_compression_methods_formatted = consider::combine(data.compression_methods, ", ", ^::to_string);
auto server_id = server_identifier::get_id();
auto message = com::string::format(SERVER_AUTHENTICATOR_UNSUPPORTED_COMPRESSION_METHOD, server_id, client_compression_methods_formatted);
return this->disconnect_for_message(disconnect_reason::unknown_client_data, message);
}
key_share server_key_share[];
com::string server_random;
if (!crypto_provider::validate_and_consume(data.random, &data.session_id, cipherSuite, compressionMethod, data.key_share, &server_key_share, &server_random, <<-reasoning))
{
this->disconnect_for_message(disconnect_reason::crypto_failure, reasoning->message);
return;
}
crypto_exchange_server s = {
server_random,
com::string::empty,
*cipher_suite,
*compression_method,
server_key_share,
};
data_stream ds;
replication_struct::serialize(s, &ds);
packet->peer->replicate(
authentication_packet_type::exchange_crypto,
&ds,
replication_packet::data,
replication_reliability::most_reliable,
replication_flags::needs_ack | replication_flags::block_non_mutual | replication_flags::blocking
);
crypto_provider::set_for_replication_session(packet->session, data.session_id);
#else
data_stream data(packet);
data.fail_on_no_read = false;
com::string random;
com::string session_id;
cypher_suite cipher_suites[];
compression_method compression_methods[];
com::string server_id;
key_share key_share[];
data >> random >> session_id >> cipher_suites >> compression_method >> server_id >> key_share;
if (!server_id.empty())
{
if (!server_identifier::check_id(server_id))
{
this->disconnect_for(disconnect_reason::server_id_mismatch);
return;
}
}
auto* cipher_suite = crypto_provider::get_supported_cipher_suite(cipher_suites);
if (cipher_suite == nullptr && !settings_provider::current()->get("DebugSupportNoEncryption"))
{
auto client_ciper_suites_formatted = consider::combine(cipher_suites, ", ", ^::to_string);
auto server_id = server_identifier::get_id();
auto message = com::string::format(SERVER_AUTHENTICATOR_UNSUPPORTED_CIPHER_SUITE, server_id, client_ciper_suites_formatted);
return this->disconnect_for_message(disconnect_reason::unknown_client_data, message);
}
auto* compression_method = crypto_provider::get_supported_compression_method(compression_methods);
if (compression_method == nullptr && !settings_provider::current()->get("DebugSupportNoEncryption"))
{
auto client_compression_methods_formatted = consider::combine(compression_methods, ", ", ^::to_string);
auto server_id = server_identifier::get_id();
auto message = com::string::format(SERVER_AUTHENTICATOR_UNSUPPORTED_COMPRESSION_METHOD, server_id, client_compression_methods_formatted);
return this->disconnect_for_message(disconnect_reason::unknown_client_data, message);
}
key_share server_key_share[];
com::string server_random;
if (!crypto_provider::validate_and_consume(random, &session_id, cipherSuite, compressionMethod, key_share, &server_key_share, &server_random, <<-reasoning))
{
this->disconnect_for_message(disconnect_reason::crypto_failure, reasoning->message);
return;
}
data_stream ds;
ds << server_random << com::string::empty << *cipher_suite << *compression_method << server_key_share;
packet->peer->replicate(
authentication_packet_type::exchange_crypto,
&ds,
replication_packet::data,
replication_reliability::most_reliable,
replication_flags::needs_ack | replication_flags::block_non_mutual | replication_flags::blocking
);
crypto_provider::set_for_replication_session(packet->session, session_id);
#endif
auto* crypto_session = crypto_provider::get_session_by_replication_session(packet->session);
if (crypto_session == nullptr)
{
packet->block_and_disconnect();
return;
}
crypto_session->set_stage(crypto_session_stage::server_certificate);
auto* certificate = crypto_session->get_certificate();
if (certificate == nullptr)
{
// Failure on our side, just tell them that there is internal failure.
this->disconnect_for(disconnect_reason::internal_failure);
return;
}
data_stream ds;
ds.write_segmented(certificate->get_data(), certificate->get_data_size());
// Step 1: Send the certificate.
packet->peer->replicate_segmented_block(
authentication_packet_type::server_certificate,
&ds,
replication_packet::data,
replication_reliability::most_reliable,
replication_flags::needs_ack | replication_flags::block_non_mutual | replication_flags::blocking
);
crypto_session->set_has_sent_certificate();
// Step 2: Send the verify packet.
ds.clear();
crypto_session->set_stage(crypto_session_stage::server_certificate_verify);
auto verify_data = crypto_session->get_verify_data();
auto verify_data_size = sizeof(verify_data);
auto verify_data_signature = certificate->sign(verify_data, verify_data_size);
// No segmenting needed here as the signature is always smaller than the max packet size.
ds << verify_data_signature;
packet->peer->replicate(
authentication_packet_type::server_certificate_verify,
&ds,
replication_packet::data,
replication_reliability::most_reliable,
replication_flags::needs_ack | replication_flags::block_non_mutual | replication_flags::blocking
);
crypto_session->set_has_sent_certificate_verify();
break;
case authentication_packet_type::server_certificate_verify_ack:
// NSCRYPTO-16744: This needs to be a data packet because REPL does not have the ability to bind
// custom parameters to ACK, NACK and PING packets, and this verifies sessions.
// Check ordering. Block is allowed here because normal people don't go out of order like this.
if (this->out_of_order(authentication_packet_type::server_certificate_verify_ack, authentication_packet_type::server_certificate))
return packet->block_and_disconnect();
auto* crypto_session = crypto_provider::get_session_by_replication_session(packet->session);
if (crypto_session == nullptr)
{
packet->block_and_disconnect();
return;
}
com::string new_verify_data_signature; // Returned by crypto_provider::verify_signature. ( It's a digest of the verify data based on the random and session id. )
auto* data = crypto_session->get_encrypted_packet_data(packet);
if (data == nullptr)
{
packet->block_and_disconnect();
return;
}
*data >> verify_data_signature;
crypto_session->set_stage(crypto_session_stage::server_certificate_verify_ack);
auto* certificate = crypto_session->get_certificate();
if (certificate == nullptr)
{
// Failure on our side, just tell them that there is internal failure.
this->disconnect_for(disconnect_reason::internal_failure);
return;
}
auto verify_data = crypto_session->get_verify_data();
auto verify_data_size = sizeof(verify_data);
auto verify_data_signature = certificate->sign(verify_data, verify_data_size);
if (!crypto_provider::digests_match(verify_data_signature, new_verify_data_signature))
{
this->disconnect_for_message(disconnect_reason::crypto_failure, "The verify data signature did not match.");
return;
}
crypto_session->bind_to_outgoing_replication_packet(packet); // Encrypt the ACK packet.
// Send an ack back to the client.
packet->peer->acknowledge(packet->id);
crypto_session->set_has_received_certificate_verify_ack();
break;
case authentication_packet_type::client_identification:
if (this->out_of_order(authentication_packet_type::client_identification, authentication_packet_type::server_certificate_verify_ack))
return packet->block_and_disconnect();
auto* crypto_session = crypto_provider::get_session_by_replication_session(packet->session);
if (crypto_session == nullptr)
{
packet->block_and_disconnect();
return;
}
crypto_session->set_stage(crypto_session_stage::client_identification);
auto* data = crypto_session->get_encrypted_packet_data(packet);
if (data == nullptr)
{
packet->block_and_disconnect();
return;
}
com::string client_ticket; // Returned by the auth web service when calling /games-service/v1/join.
com::string client_ticket_signature; // Returned by the auth web service when calling /games-service/v1/join.
*data >> client_ticket >> client_ticket_signature;
// Verify the ticket signature.
auto* server_public_key = crypto_provider::get_server_public_key();
if (server_public_key == nullptr)
{
this->disconnect_for(disconnect_reason::internal_failure);
return;
}
if (!server_public_key->verify(client_ticket, client_ticket_signature))
{
packet->block_and_disconnect();
return;
}
// Deserialize the ticket.
auto* ticket = com::json::deserialize<com::mfdlabs::clients::games::models::v1::join_ticket>(client_ticket);
if (ticket == nullptr)
{
packet->block_and_disconnect();
return;
}
// Cast to com::mfdlabs::shared::user model.
auto* shared_user = dynamic_cast<com::mfdlabs::shared::user*>(ticket->user);
if (shared_user == nullptr)
{
packet->block_and_disconnect();
return;
}
// Check if the user is banned.
if (shared_user->is_banned())
{
packet->block_and_disconnect("You are banned.");
return;
}
// Check if the user is already connected.
if (shared_user->is_connected())
{
/* OLD REDUNDANT CHECK FROM WHEN WE SHARED GAME JOBS BY SERVER */
// // Call on global_session_provider to see which session should remain connected.
// // Get the machine addresses of the user.
// auto* machine_addresses = games_provider::get_registered_machine_addresses(shared_user->id);
// // Check if job manager will clash with this connection.
// if (machine_addresses != nullptr && machine_addresses->size() > 0)
// {
// // Get the jobs that this user is connected to.
// auto jobs = shared_user->get_jobs(server_identifier::get_id());
// if (jobs.size() > 0)
// {
// // Filter out the jobs that this user is in and that are running. ( GSS-162: Users persisting in jobs after job end. )
// auto running_jobs = consider::filter(jobs, [](auto* job) { return job->is_running(); });
// if (running_jobs.size() > 0)
// {
// // Check if the user is in a job that is running on the same machine as this connection.
// auto* job = consider::first(running_jobs, [machine_addresses](auto* job) { return machine_addresses->contains(job->get_machine_address()); });
// if (job != nullptr && job->get_game_id() == games_provider::get_game_id())
// {
// // The user is in a job that is running on the same machine as this connection and it is for the current game, disconnect the user.
// this->disconnect_for_message(disconnect_reason::already_connected, "You are already connected.");
// return;
// }
// }
// }
this->disconnect_for_message(disconnect_reason::already_connected, "You are already connected.");
return;
}
packet->session->bind_user(shared_user);
// Send back server identification.
data_stream stream;
crypto_session->bind_to_outgoing_replication_stream(stream); // Encrypt the packet.
// Get server id information and convert to baseless stream.
auto* server_info = server_identifier::enumerate();
if (server_info == nullptr)
{
this->disconnect_for(disconnect_reason::internal_failure);
return;
}
com::io::stream_helper::enumerative_to_stream(&stream, server_info);
// Send the packet.
packet->peer->replicate(
authentication_packet_type::server_identification,
&stream,
replication_packet::data,
replication_reliability::most_reliable,
replication_flags::needs_ack | replication_flags::block_non_mutual | replication_flags::blocking
);
// Replace the current processor with the auth verification processor.
packet->pipeline->set_at_current(authentication_verification_processor::singleton());
}
}
};
REPLICATION_PROCESSOR(
server_authentication_initializer,
processor_exclusions::ignore_ack | processor_exclusions::ignore_nack,
processor_priority::high,
processor_order::top_level,
processor_flags::full,
processor_metrics::all,
{ authentication_packet_type }
);
REGISTER_GLOBAL_PACKAGE(
server_authentication_initializer,
diagnostics_package_extensions::gc_on_app_released
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment