Created
November 19, 2022 18:12
-
-
Save nikita-petko/11eb0c012992bb567be34dd7fcc03daf to your computer and use it in GitHub Desktop.
server_authentication_initializer_processor.fx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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