diff --git a/Cargo.toml b/Cargo.toml index 05255c27..63b6162b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,10 @@ ureq = "3.0.11" thiserror = "2.0.12" # Cryptography +rsa = "0.9.8" +sha1 = "0.10" +aes = "0.8" +cfb8 = "0.8" rand = "0.9.1" fnv = "1.0.7" wyhash = "0.6.0" @@ -152,6 +156,7 @@ byteorder = "1.5.0" dashmap = "7.0.0-rc2" uuid = { version = "1.17.0", features = ["v4", "v3", "serde"] } indexmap = { version = "2.9.0", features = ["serde"] } +once_cell = "1.21.3" # Macros lazy_static = "1.5.0" diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 5ecc5713..26102971 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -14,6 +14,7 @@ bevy_ecs = { workspace = true } ferrumc-net = { workspace = true } ferrumc-net-codec = { workspace = true } +ferrumc-net-encryption = { workspace = true } ferrumc-plugins = { workspace = true } ferrumc-storage = { workspace = true } ferrumc-utils = { workspace = true } diff --git a/src/lib/net/Cargo.toml b/src/lib/net/Cargo.toml index c95d8161..c8d65c70 100644 --- a/src/lib/net/Cargo.toml +++ b/src/lib/net/Cargo.toml @@ -33,3 +33,6 @@ typename = { workspace = true } bitcode = { workspace = true } indexmap = { workspace = true } lazy_static = { workspace = true } +aes = { workspace = true } +rsa = { workspace = true } +cfb8 = { workspace = true } diff --git a/src/lib/net/crates/encryption/Cargo.toml b/src/lib/net/crates/encryption/Cargo.toml index 100c2494..26ab7a31 100644 --- a/src/lib/net/crates/encryption/Cargo.toml +++ b/src/lib/net/crates/encryption/Cargo.toml @@ -4,4 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] +ferrumc-core = { workspace = true } + thiserror = { workspace = true } +rsa = { workspace = true } +sha1 = { workspace = true } +aes = { workspace = true } +cfb8 = { workspace = true } +rand = { workspace = true } +base64 = { workspace = true } +reqwest = { workspace = true } +bevy_ecs = { workspace = true } +once_cell = { workspace = true } diff --git a/src/lib/net/crates/encryption/src/digest.rs b/src/lib/net/crates/encryption/src/digest.rs new file mode 100644 index 00000000..8c24fba9 --- /dev/null +++ b/src/lib/net/crates/encryption/src/digest.rs @@ -0,0 +1,42 @@ +use sha1::{Digest, Sha1}; + +pub fn get_player_digest(name: &str) -> String { + let mut hash: [u8; 20] = Sha1::new().chain_update(name).finalize().into(); + + let negative = (hash[0] & 0x80) != 0; + + if negative { + let mut carry = true; + for byte in hash.iter_mut().rev() { + *byte = !*byte; + if carry { + *byte = byte.wrapping_add(1); + carry = *byte == 0; + } + } + } + + // Encode to hex + let mut hex = String::with_capacity(41); + if negative { + hex.push('-'); + } + let mut started = false; + for byte in hash.iter() { + for nibble in [byte >> 4, byte & 0x0F] { + if !started { + if nibble == 0 { + continue; + } + started = true; + } + hex.push(std::char::from_digit(nibble as u32, 16).unwrap()); + } + } + + if hex == "-" { + "0".to_string() + } else { + hex + } +} diff --git a/src/lib/net/crates/encryption/src/lib.rs b/src/lib/net/crates/encryption/src/lib.rs index 856e5ff7..61914583 100644 --- a/src/lib/net/crates/encryption/src/lib.rs +++ b/src/lib/net/crates/encryption/src/lib.rs @@ -1,16 +1,67 @@ +use std::sync::Arc; + +use aes::{cipher::KeyInit, Aes128Dec, Aes128Enc}; +use once_cell::sync::Lazy; +use rsa::{pkcs8::EncodePublicKey, rand_core::OsRng, RsaPrivateKey, RsaPublicKey}; + +pub mod digest; pub mod errors; -pub fn add(left: u64, right: u64) -> u64 { - left + right +#[cfg(test)] +mod tests; + +pub static ENCRYPTION_KEYS: Lazy = Lazy::new(|| EncryptionKeys::new()); + +pub struct EncryptionKeys { + pub private_key: Arc, + pub public_key: Arc, } -#[cfg(test)] -mod tests { - use super::*; +impl EncryptionKeys { + pub fn new() -> Self { + let mut rng = OsRng; + let private_key = + RsaPrivateKey::new(&mut rng, 1024).expect("Failed to generate PEM key for encryption"); + let public_key = RsaPublicKey::from(private_key.clone()); + + Self { + private_key: Arc::new(private_key), + public_key: Arc::new(public_key), + } + } + + pub fn get_public_der(&self) -> Vec { + self.public_key + .to_public_key_der() + .unwrap() + .as_bytes() + .to_vec() + } +} + +#[derive(Clone)] +pub struct ConnectionEncryption { + pub shared_secret: Vec, + pub decrypt_cipher: Aes128Dec, + pub encrypt_cipher: Aes128Enc, +} + +impl ConnectionEncryption { + pub fn new(shared_secret: Vec) -> Self { + let decrypt_cipher = Aes128Dec::new_from_slice(&shared_secret).unwrap(); + let encrypt_cipher = Aes128Enc::new_from_slice(&shared_secret).unwrap(); + Self { + shared_secret, + decrypt_cipher, + encrypt_cipher, + } + } + + pub fn encrypt(&mut self, data: &mut [u8]) { + self.encrypt_cipher.encrypt(data); + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub fn decrypt(&mut self, data: &mut [u8]) { + self.decrypt_cipher.decrypt(data); } } diff --git a/src/lib/net/crates/encryption/src/tests.rs b/src/lib/net/crates/encryption/src/tests.rs new file mode 100644 index 00000000..8e3008b3 --- /dev/null +++ b/src/lib/net/crates/encryption/src/tests.rs @@ -0,0 +1,102 @@ +use rsa::{ + pkcs1::EncodeRsaPublicKey, pkcs8::DecodePublicKey, rand_core::OsRng, Pkcs1v15Encrypt, + RsaPrivateKey, RsaPublicKey, +}; + +use crate::{digest::get_player_digest, EncryptionKeys, ENCRYPTION_KEYS}; + +#[test] +fn test_pem_generation() { + let keys = EncryptionKeys::new(); + let public_pem = keys + .public_key + .to_pkcs1_pem(Default::default()) + .expect("failed..."); + + println!("Public Pem Encryption Key: {public_pem}"); + println!( + "Public Pem Encryption Key (Bytes): {:?}", + public_pem.as_bytes() + ); +} + +#[test] +fn test_minecraft_hashes() { + assert_eq!( + get_player_digest("Notch"), + "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48" + ); + assert_eq!( + get_player_digest("jeb_"), + "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1" + ); + assert_eq!( + get_player_digest("simon"), + "88e16a1019277b15d58faf0541e11910eb756f6" + ); +} + +#[test] +fn test_decrypt() { + let mut rng = OsRng; + let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate key"); + let pub_key = RsaPublicKey::from(&priv_key); + + let message = b"test secret"; + + // Encrypt using the public key + let enc = pub_key + .encrypt(&mut rng, Pkcs1v15Encrypt, message) + .expect("encryption failed"); + + // Decrypt using the private key + let dec = priv_key + .decrypt(Pkcs1v15Encrypt, &enc) + .expect("decryption failed"); + + assert_eq!(dec, message); +} + +#[test] +fn test_encrypt_decrypt() { + let mut rng = OsRng; + + let priv_key = RsaPrivateKey::new(&mut rng, 1024).unwrap(); + let pub_key = RsaPublicKey::from(&priv_key); + + let message = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // under 117 bytes + let encrypted = pub_key + .encrypt(&mut rng, Pkcs1v15Encrypt, &message) + .unwrap(); + + let decrypted = priv_key.decrypt(Pkcs1v15Encrypt, &encrypted).unwrap(); + + assert_eq!(message, decrypted); +} + +#[test] +fn test_encrypt_decrypt_shared_secret() { + use rsa::pkcs8::EncodePublicKey; + use rsa::{rand_core::OsRng, Pkcs1v15Encrypt, RsaPrivateKey}; + + let mut rng = OsRng; + + let private_key = RsaPrivateKey::new(&mut rng, 1024).unwrap(); + let public_key = private_key.to_public_key(); + + // Get DER to simulate Minecraft client loading the key + let der = public_key.to_public_key_der().unwrap().as_bytes().to_vec(); + + // Simulate Minecraft client encrypting with DER key + let client_public_key = RsaPublicKey::from_public_key_der(&der).unwrap(); + + let secret = b"1234567890abcdef"; // 16 bytes + let encrypted = client_public_key + .encrypt(&mut rng, Pkcs1v15Encrypt, secret) + .unwrap(); + + // Simulate server decrypting + let decrypted = private_key.decrypt(Pkcs1v15Encrypt, &encrypted).unwrap(); + + assert_eq!(&decrypted, secret); +} diff --git a/src/lib/net/src/conn_init/login.rs b/src/lib/net/src/conn_init/login.rs index daabc860..68a300b7 100644 --- a/src/lib/net/src/conn_init/login.rs +++ b/src/lib/net/src/conn_init/login.rs @@ -1,22 +1,30 @@ +use std::time::Instant; + use crate::conn_init::NetDecodeOpts; use crate::conn_init::VarInt; use crate::conn_init::{send_packet, trim_packet_head}; use crate::errors::NetError; +use crate::packets::outgoing::encryption_request::EncryptionRequestPacket; use crate::packets::outgoing::registry_data::REGISTRY_PACKETS; use ferrumc_config::statics::get_global_config; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_net_codec::decode::NetDecode; use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; +use ferrumc_net_encryption::ConnectionEncryption; +use ferrumc_net_encryption::ENCRYPTION_KEYS; use ferrumc_state::GlobalState; +use rand::RngCore; +use rsa::Pkcs1v15Encrypt; use tokio::io::AsyncReadExt; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tracing::debug; use tracing::{error, trace}; pub(super) async fn login( mut conn_read: &mut OwnedReadHalf, conn_write: &mut OwnedWriteHalf, state: GlobalState, -) -> Result<(bool, Option), NetError> { +) -> Result<(bool, Option<(ConnectionEncryption, PlayerIdentity)>), NetError> { // ============================================================================================= trim_packet_head(conn_read, 0x00).await?; @@ -26,6 +34,52 @@ pub(super) async fn login( ) .await?; + debug!("Encrypting..."); + + let instant = Instant::now(); + let should_authenticate = get_global_config().online_mode; + let mut verify_token = [0u8; 16]; + rand::rng().fill_bytes(&mut verify_token); + + let encryption_request = EncryptionRequestPacket { + server_id: String::new(), + public_key: LengthPrefixedVec::new(ENCRYPTION_KEYS.get_public_der()), + verify_token: LengthPrefixedVec::new(verify_token.to_vec()), + should_authenticate, + }; + + send_packet(conn_write, &encryption_request).await?; + + trim_packet_head(conn_read, 0x01).await?; + let encryption_response = + crate::packets::incoming::encryption_response::EncryptionResponsePacket::decode_async( + &mut conn_read, + &NetDecodeOpts::None, + ) + .await?; + + // Decrypt, this has to be here since the response packet is decrypted differently with other packets + // This is because the response doesn't use the shared_key for decryption, since this is the packet that sends it. + let decrypted_secret = ENCRYPTION_KEYS + .private_key + .decrypt(Pkcs1v15Encrypt, &encryption_response.shared_secret.data) + .map_err(|_| NetError::Auth("Failed to decrypt shared_key".to_string()))?; + + let decrypted_token = ENCRYPTION_KEYS + .private_key + .decrypt(Pkcs1v15Encrypt, &encryption_response.verify_token.data) + .map_err(|_| NetError::Auth("Failed to decrypt verify_token".to_string()))?; + + if verify_token != decrypted_token.as_slice() { + return Err(NetError::Auth( + "Verify Token Mismatch, client may not have the correct encryption key...".to_string(), + )); + } + + // This results in encryption being successful + let player_encryption = ConnectionEncryption::new(decrypted_secret); + debug!("Encryption Completed... (Took: {:?})", instant.elapsed()); + // ============================================================================================= let login_success = crate::packets::outgoing::login_success::LoginSuccessPacket { @@ -207,5 +261,5 @@ pub(super) async fn login( } } - Ok((false, Some(player_identity))) + Ok((false, Some((player_encryption, player_identity)))) } diff --git a/src/lib/net/src/conn_init/mod.rs b/src/lib/net/src/conn_init/mod.rs index 21c28f84..971c043e 100644 --- a/src/lib/net/src/conn_init/mod.rs +++ b/src/lib/net/src/conn_init/mod.rs @@ -9,6 +9,7 @@ use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_net_codec::decode::{NetDecode, NetDecodeOpts}; use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts}; use ferrumc_net_codec::net_types::var_int::VarInt; +use ferrumc_net_encryption::ConnectionEncryption; use ferrumc_state::GlobalState; use ferrumc_text::{ComponentBuilder, NamedColor, TextComponent}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -19,7 +20,10 @@ use tracing::{error, trace}; /// sure we are going to get the right packet id and length, and we don't need to check it /// If we get a packet with the id 0x12, we will skip it, since it is a serverbound plugin message packet /// They have stupid formatting, and we don't want to deal with it -pub(crate) async fn trim_packet_head(conn: &mut OwnedReadHalf, value: u8) -> Result<(), NetError> { +pub(crate) async fn trim_packet_head( + conn: &mut OwnedReadHalf, + expected_id: u8, +) -> Result<(), NetError> { let mut len = VarInt::decode_async(conn, &NetDecodeOpts::None).await?; let mut id = VarInt::decode_async(conn, &NetDecodeOpts::None).await?; while id.0 == 0x14 { @@ -30,7 +34,7 @@ pub(crate) async fn trim_packet_head(conn: &mut OwnedReadHalf, value: u8) -> Res len = VarInt::decode_async(conn, &NetDecodeOpts::None).await?; id = VarInt::decode_async(conn, &NetDecodeOpts::None).await?; } - assert_eq!(id.0, value as i32); + assert_eq!(id.0, expected_id as i32); Ok(()) } @@ -62,7 +66,7 @@ pub async fn handle_handshake( mut conn_read: &mut OwnedReadHalf, conn_write: &mut OwnedWriteHalf, state: GlobalState, -) -> Result<(bool, Option), NetError> { +) -> Result<(bool, Option<(ConnectionEncryption, PlayerIdentity)>), NetError> { trim_packet_head(conn_read, 0x00).await?; // Get incoming handshake packet @@ -99,7 +103,7 @@ async fn handle_version_mismatch( conn_read: &mut OwnedReadHalf, conn_write: &mut OwnedWriteHalf, state: GlobalState, -) -> Result<(bool, Option), NetError> { +) -> Result<(bool, Option<(ConnectionEncryption, PlayerIdentity)>), NetError> { // Send appropriate disconnect packet based on the next state match hs_packet.next_state.0 { // If it was status, we can just send a status response, and the client will automatically understand the mismatch. diff --git a/src/lib/net/src/connection.rs b/src/lib/net/src/connection.rs index e53d9fda..014d1de6 100644 --- a/src/lib/net/src/connection.rs +++ b/src/lib/net/src/connection.rs @@ -106,15 +106,17 @@ pub async fn handle_connection( .await; let mut player_identity = PlayerIdentity::default(); + let mut player_encryption = None; match handshake_result { Ok(res) => match res { Ok((false, returned_player_identity)) => { trace!("Handshake successful"); match returned_player_identity { - Some(returned_player_identity) => { + Some((returned_player_encryption, returned_player_identity)) => { trace!("Player identity: {:?}", returned_player_identity); player_identity = returned_player_identity; + player_encryption = Some(returned_player_encryption); } None => { error!("Player identity not found"); @@ -149,87 +151,100 @@ pub async fn handle_connection( } } - // The player has successfully connected, so we can start the connection properly + match player_encryption { + Some(conn_encryption) => { + // The player has successfully connected, so we can start the connection properly - let compressed = false; - let running = Arc::new(AtomicBool::new(true)); + let compressed = false; + let running = Arc::new(AtomicBool::new(true)); - let stream = StreamWriter::new(tcp_writer, running.clone()).await; + let stream = StreamWriter::new(tcp_writer, running.clone()).await; - // Send the streamwriter to the main thread - let (entity_return, entity_recv) = oneshot::channel(); + // Send the streamwriter to the main thread + let (entity_return, entity_recv) = oneshot::channel(); - new_join_sender - .send(NewConnection { - stream, - player_identity, - entity_return, - }) - .map_err(|_| NetError::Misc("Failed to send new connection".to_string()))?; + new_join_sender + .send(NewConnection { + stream, + player_identity, + entity_return, + }) + .map_err(|_| NetError::Misc("Failed to send new connection".to_string()))?; - // Wait for the entity ID to be sent back - let entity = match entity_recv.await { - Ok(entity) => entity, - Err(err) => { - error!("Failed to receive entity ID: {:?}", err); - return Err(NetError::Misc("Failed to receive entity ID".to_string())); - } - }; - - 'recv: loop { - if !running.load(Ordering::Relaxed) { - trace!("Conn for entity {:?} is marked for disconnection", entity); - break 'recv; - } + // Wait for the entity ID to be sent back + let entity = match entity_recv.await { + Ok(entity) => entity, + Err(err) => { + error!("Failed to receive entity ID: {:?}", err); + return Err(NetError::Misc("Failed to receive entity ID".to_string())); + } + }; - if state.shut_down.load(Ordering::Relaxed) { - break 'recv; - } + 'recv: loop { + if !running.load(Ordering::Relaxed) { + trace!("Conn for entity {:?} is marked for disconnection", entity); + break 'recv; + } - let mut packet_skele = match PacketSkeleton::new(&mut tcp_reader, compressed).await { - Ok(packet_skele) => packet_skele, - Err(err) => { - if let NetError::ConnectionDropped = err { - trace!("Connection dropped for entity {:?}", entity); - running.store(false, Ordering::Relaxed); + if state.shut_down.load(Ordering::Relaxed) { break 'recv; } - error!("Failed to read packet skeleton: {:?} for {:?}", err, entity); - running.store(false, Ordering::Relaxed); - break 'recv; - } - }; - match handle_packet( - packet_skele.id, - entity, - &mut packet_skele.data, - packet_sender.clone(), - ) - .instrument(debug_span!("eid", %entity)) - .into_inner() - { - Ok(()) => { - trace!( - "Packet {:02X} handled for entity {:?}", + let mut packet_skele = match PacketSkeleton::new( + &mut tcp_reader, + compressed, + &conn_encryption, + ) + .await + { + Ok(packet_skele) => packet_skele, + Err(err) => { + if let NetError::ConnectionDropped = err { + trace!("Connection dropped for entity {:?}", entity); + running.store(false, Ordering::Relaxed); + break 'recv; + } + error!("Failed to read packet skeleton: {:?} for {:?}", err, entity); + running.store(false, Ordering::Relaxed); + break 'recv; + } + }; + + match handle_packet( packet_skele.id, - entity - ); - } - Err(err) => match &err { - NetError::Packet(InvalidPacket(id)) => { - trace!("Packet 0x{:02X} received, no handler implemented yet", id); - } - _ => { - warn!("Failed to handle packet: {:?}", err); - running.store(false, Ordering::Relaxed); - break 'recv; + entity, + &mut packet_skele.data, + packet_sender.clone(), + ) + .instrument(debug_span!("eid", %entity)) + .into_inner() + { + Ok(()) => { + trace!( + "Packet {:02X} handled for entity {:?}", + packet_skele.id, + entity + ); + } + Err(err) => match &err { + NetError::Packet(InvalidPacket(id)) => { + trace!("Packet 0x{:02X} received, no handler implemented yet", id); + } + _ => { + warn!("Failed to handle packet: {:?}", err); + running.store(false, Ordering::Relaxed); + break 'recv; + } + }, } - }, + } + + Ok(()) } + None => Err(NetError::Auth( + "ConnectionEncryption has failed...".to_string(), + )), } - - Ok(()) } impl StreamWriter { diff --git a/src/lib/net/src/errors.rs b/src/lib/net/src/errors.rs index 2464bea4..f3d30be0 100644 --- a/src/lib/net/src/errors.rs +++ b/src/lib/net/src/errors.rs @@ -50,6 +50,9 @@ pub enum NetError { #[error("World error: {0}")] World(#[from] ferrumc_world::errors::WorldError), + #[error("Authentication error: {0}")] + Auth(String), + #[error("Misc error: {0}")] Misc(String), } diff --git a/src/lib/net/src/packets/incoming/encryption_response.rs b/src/lib/net/src/packets/incoming/encryption_response.rs new file mode 100644 index 00000000..c7c71478 --- /dev/null +++ b/src/lib/net/src/packets/incoming/encryption_response.rs @@ -0,0 +1,9 @@ +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; + +#[derive(Debug, NetDecode)] +#[packet(packet_id = "key", state = "login")] +pub struct EncryptionResponsePacket { + pub shared_secret: LengthPrefixedVec, + pub verify_token: LengthPrefixedVec, +} diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index adbd4679..be248315 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -1,5 +1,6 @@ pub mod ack_finish_configuration; pub mod client_information; +pub mod encryption_response; pub mod handshake; pub mod login_acknowledged; pub mod login_start; diff --git a/src/lib/net/src/packets/incoming/packet_skeleton.rs b/src/lib/net/src/packets/incoming/packet_skeleton.rs index dfde43e7..8837d560 100644 --- a/src/lib/net/src/packets/incoming/packet_skeleton.rs +++ b/src/lib/net/src/packets/incoming/packet_skeleton.rs @@ -1,6 +1,7 @@ use crate::errors::NetError; use ferrumc_config::statics::get_global_config; use ferrumc_net_codec::{decode::errors::NetDecodeError, net_types::var_int::VarInt}; +use ferrumc_net_encryption::ConnectionEncryption; use std::io::Cursor; use std::{fmt::Debug, io::Read}; use tokio::io::AsyncRead; @@ -26,10 +27,11 @@ impl PacketSkeleton { pub async fn new( reader: &mut R, compressed: bool, + encryption: &ConnectionEncryption, ) -> Result { let pak = match compressed { - true => Self::read_compressed(reader).await, - false => Self::read_uncompressed(reader).await, + true => Self::read_compressed(reader, encryption).await, + false => Self::read_uncompressed(reader, encryption).await, }; match pak { Ok(p) => { @@ -53,7 +55,10 @@ impl PacketSkeleton { } // #[inline(always)] - async fn read_uncompressed(reader: &mut R) -> Result { + async fn read_uncompressed( + reader: &mut R, + encryption: &ConnectionEncryption, + ) -> Result { let length = VarInt::read_async(reader).await?.0 as usize; let mut buf = { let mut buf = vec![0; length]; @@ -72,7 +77,10 @@ impl PacketSkeleton { } #[inline(always)] - async fn read_compressed(reader: &mut R) -> Result { + async fn read_compressed( + reader: &mut R, + encryption: &ConnectionEncryption, + ) -> Result { let packet_length = VarInt::read_async(reader).await?.0 as usize; let data_length = VarInt::read_async(reader).await?.0 as usize; diff --git a/src/lib/net/src/packets/outgoing/encryption_request.rs b/src/lib/net/src/packets/outgoing/encryption_request.rs new file mode 100644 index 00000000..239ac1e5 --- /dev/null +++ b/src/lib/net/src/packets/outgoing/encryption_request.rs @@ -0,0 +1,12 @@ +use ferrumc_macros::{packet, NetEncode}; +use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = "hello", state = "login")] +pub struct EncryptionRequestPacket { + pub server_id: String, + pub public_key: LengthPrefixedVec, + pub verify_token: LengthPrefixedVec, + pub should_authenticate: bool, +} diff --git a/src/lib/net/src/packets/outgoing/mod.rs b/src/lib/net/src/packets/outgoing/mod.rs index 243d984b..44624c16 100644 --- a/src/lib/net/src/packets/outgoing/mod.rs +++ b/src/lib/net/src/packets/outgoing/mod.rs @@ -3,6 +3,7 @@ pub mod chunk_batch_finish; pub mod chunk_batch_start; pub mod client_bound_known_packs; pub mod disconnect; +pub mod encryption_request; pub mod finish_configuration; pub mod game_event; pub mod keep_alive; diff --git a/src/lib/utils/config/src/server_config.rs b/src/lib/utils/config/src/server_config.rs index 6eb49f01..7eb5a3d7 100644 --- a/src/lib/utils/config/src/server_config.rs +++ b/src/lib/utils/config/src/server_config.rs @@ -17,6 +17,7 @@ use serde_derive::{Deserialize, Serialize}; /// - `network_compression_threshold`: The threshold at which the server will compress network packets. /// - `whitelist`: Whether the server whitelist is enabled or not. /// - `chunk_render_distance`: The render distance of the chunks. This is the number of chunks that will be +/// - `online_mode`: If the server should authenticate player's while joining /// loaded around the player. #[derive(Debug, Deserialize, Serialize)] pub struct ServerConfig { @@ -30,6 +31,7 @@ pub struct ServerConfig { pub network_compression_threshold: i32, // Can be negative pub whitelist: bool, pub chunk_render_distance: u32, + pub online_mode: bool, } /// The database configuration section from [ServerConfig].