From ea0128c50b2a190d38c3f28d29e294066529858b Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Wed, 7 Aug 2024 13:32:48 +0200 Subject: [PATCH] Improve Authentication --- README.md | 2 + .../src/client/login/c_login_success.rs | 5 +- .../src/client/play/c_player_info_update.rs | 5 +- pumpkin/src/client/authentication.rs | 67 +++++++- pumpkin/src/client/client_packet.rs | 40 ++++- pumpkin/src/client/mod.rs | 2 +- pumpkin/src/config/auth_config.rs | 93 +++++++++++ pumpkin/src/{ => config}/configuration.rs | 18 +- pumpkin/src/config/mod.rs | 154 ++++++++++++++++++ pumpkin/src/main.rs | 6 +- pumpkin/src/server.rs | 2 +- 11 files changed, 362 insertions(+), 32 deletions(-) create mode 100644 pumpkin/src/config/auth_config.rs rename pumpkin/src/{ => config}/configuration.rs (92%) create mode 100644 pumpkin/src/config/mod.rs diff --git a/README.md b/README.md index 9a58292cb..e919bfafd 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,10 @@ Pumpkin is currently under heavy development. ## What Pumpkin aims to be: - A fast, efficient, and scalable Minecraft server +- Use Multi-Threading - Compatible with the latest Minecraft server version - Adherent to Vanilla game mechanics +- Be Secure and prevent all Exploits found - Highly configurable with the ability to disable unnecessary features - A platform for plugin development diff --git a/pumpkin-protocol/src/client/login/c_login_success.rs b/pumpkin-protocol/src/client/login/c_login_success.rs index 265527802..065dc5e10 100644 --- a/pumpkin-protocol/src/client/login/c_login_success.rs +++ b/pumpkin-protocol/src/client/login/c_login_success.rs @@ -33,10 +33,7 @@ impl<'a> ClientPacket for CLoginSuccess<'a> { bytebuf.put_list::(self.properties, |p, v| { p.put_string(&v.name); p.put_string(&v.value); - // has signature ? - // todo: for some reason we get "got too many bytes error when using a signature" - p.put_bool(false); - // p.put_option(&v.signature, |p,v| p.put_string(v)); + p.put_option(&v.signature, |p, v| p.put_string(v)); }); bytebuf.put_bool(self.strict_error_handling); } diff --git a/pumpkin-protocol/src/client/play/c_player_info_update.rs b/pumpkin-protocol/src/client/play/c_player_info_update.rs index 23028c855..163c7ce97 100644 --- a/pumpkin-protocol/src/client/play/c_player_info_update.rs +++ b/pumpkin-protocol/src/client/play/c_player_info_update.rs @@ -34,10 +34,7 @@ impl<'a> ClientPacket for CPlayerInfoUpdate<'a> { p.put_list::(properties, |p, v| { p.put_string(&v.name); p.put_string(&v.value); - // has signature ? - // todo: for some reason we get "got too many bytes error when using a signature" - p.put_bool(false); - // todo signature + p.put_option(&v.signature, |p, v| p.put_string(v)); }); } PlayerAction::InitializeChat(_) => todo!(), diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index b8f57c0fc..dcebe09a3 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -1,19 +1,49 @@ -use std::net::IpAddr; +use std::{collections::HashMap, net::IpAddr}; +use base64::{engine::general_purpose, Engine}; use num_bigint::BigInt; use pumpkin_protocol::Property; -use reqwest::StatusCode; -use serde::Deserialize; +use reqwest::{StatusCode, Url}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; -use crate::server::Server; +use crate::{config::auth_config::TextureConfig, server::Server}; + +#[derive(Deserialize, Clone, Debug)] +#[allow(non_snake_case)] +#[allow(dead_code)] +pub struct ProfileTextures { + timestamp: i64, + profileId: Uuid, + profileName: String, + signatureRequired: bool, + textures: HashMap, +} + +#[derive(Deserialize, Clone, Debug)] +#[allow(non_snake_case)] +#[allow(dead_code)] +pub struct Texture { + url: String, + metadata: Option>, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +pub enum ProfileAction { + #[serde(rename = "FORCED_NAME_CHANGE")] + ForcedNameChange, + #[serde(rename = "USING_BANNED_SKIN")] + UsingBannedSkin, +} #[derive(Deserialize, Clone, Debug)] pub struct GameProfile { pub id: Uuid, pub name: String, pub properties: Vec, + #[serde(rename = "profileActions")] + pub profile_actions: Option>, } pub fn authenticate( @@ -22,8 +52,13 @@ pub fn authenticate( ip: &IpAddr, server: &mut Server, ) -> Result { + assert!(server.advanced_config.authentication.use_authentication); assert!(server.auth_client.is_some()); - let address = if server.base_config.prevent_proxy_connections { + let address = if server + .advanced_config + .authentication + .prevent_proxy_connections + { format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}") } else { format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}") @@ -44,10 +79,32 @@ pub fn authenticate( Ok(profile) } +pub fn unpack_textures(property: Property, config: &TextureConfig) { + // todo: no unwrap + let from64 = general_purpose::STANDARD.decode(property.value).unwrap(); + let textures: ProfileTextures = serde_json::from_slice(&from64).unwrap(); + dbg!(&textures); + for texture in textures.textures { + is_texture_url_valid(Url::parse(&texture.1.url).unwrap(), config); + } +} + pub fn auth_digest(bytes: &[u8]) -> String { BigInt::from_signed_bytes_be(bytes).to_str_radix(16) } +pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> bool { + let scheme = url.scheme(); + if !config.allowed_url_schemes.contains(&scheme.to_string()) { + return false; + } + let domain = url.domain().unwrap_or(""); + if !config.allowed_url_domains.contains(&domain.to_string()) { + return false; + } + true +} + #[derive(Error, Debug)] pub enum AuthError { #[error("Authentication servers are down")] diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 3e44cbce3..a62286c40 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -23,7 +23,10 @@ use crate::{ server::Server, }; -use super::{authentication::auth_digest, Client, EncryptionError, PlayerConfig}; +use super::{ + authentication::{auth_digest, unpack_textures}, + Client, EncryptionError, PlayerConfig, +}; /// Processes incoming Packets from the Client to the Server /// Implements the `Client` Packets @@ -45,12 +48,15 @@ impl Client { } pub fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) { + // todo: do basic name validation dbg!("login start"); // default game profile, when no online mode + // todo: make offline uuid self.gameprofile = Some(GameProfile { id: login_start.uuid, name: login_start.name, properties: vec![], + profile_actions: None, }); // todo: check config for encryption @@ -91,10 +97,40 @@ impl Client { &ip, server, ) { - Ok(p) => self.gameprofile = Some(p), + Ok(p) => { + // Check if player should join + if let Some(p) = &p.profile_actions { + if !server + .advanced_config + .authentication + .player_profile + .allow_banned_players + { + if !p.is_empty() { + self.kick("Your account can't join"); + } + } else { + for allowed in server + .advanced_config + .authentication + .player_profile + .allowed_actions + .clone() + { + if !p.contains(&allowed) { + self.kick("Your account can't join"); + } + } + } + } + self.gameprofile = Some(p); + } Err(e) => self.kick(&e.to_string()), } } + for ele in self.gameprofile.as_ref().unwrap().properties.clone() { + unpack_textures(ele, &server.advanced_config.authentication.textures); + } if let Some(profile) = self.gameprofile.as_ref().cloned() { let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false); diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 908f644b6..99fc0c2c4 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -38,7 +38,7 @@ use pumpkin_protocol::{ use std::io::Read; use thiserror::Error; -mod authentication; +pub mod authentication; mod client_packet; pub mod player_packet; diff --git a/pumpkin/src/config/auth_config.rs b/pumpkin/src/config/auth_config.rs new file mode 100644 index 000000000..0fe373ca8 --- /dev/null +++ b/pumpkin/src/config/auth_config.rs @@ -0,0 +1,93 @@ +use serde::{Deserialize, Serialize}; + +use crate::client::authentication::ProfileAction; + +#[derive(Deserialize, Serialize)] +pub struct Authentication { + /// Whether to use Mojang authentication. + pub use_authentication: bool, + + /// Prevent proxy connections. + pub prevent_proxy_connections: bool, + + /// Player profile handling. + pub player_profile: PlayerProfileConfig, + + /// Texture handling. + pub textures: TextureConfig, +} + +#[derive(Deserialize, Serialize)] +pub struct PlayerProfileConfig { + /// Allow players flagged by Mojang (banned, forced name change). + pub allow_banned_players: bool, + /// Depends on the value above + pub allowed_actions: Vec, +} + +impl Default for PlayerProfileConfig { + fn default() -> Self { + Self { + allow_banned_players: false, + allowed_actions: vec![ + ProfileAction::ForcedNameChange, + ProfileAction::UsingBannedSkin, + ], + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TextureConfig { + /// Whether to use player textures. + pub enabled: bool, + + pub allowed_url_schemes: Vec, + pub allowed_url_domains: Vec, + + /// Specific texture types. + pub types: TextureTypes, +} + +impl Default for TextureConfig { + fn default() -> Self { + Self { + enabled: true, + allowed_url_schemes: vec!["http".into(), "https".into()], + allowed_url_domains: vec![".minecraft.net".into(), ".mojang.com".into()], + types: Default::default(), + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TextureTypes { + /// Use player skins. + pub skin: bool, + /// Use player capes. + pub cape: bool, + /// Use player elytras. + /// (i didn't know myself that there are custom elytras) + pub elytra: bool, +} + +impl Default for TextureTypes { + fn default() -> Self { + Self { + skin: true, + cape: true, + elytra: true, + } + } +} + +impl Default for Authentication { + fn default() -> Self { + Self { + use_authentication: true, + prevent_proxy_connections: true, + player_profile: Default::default(), + textures: Default::default(), + } + } +} diff --git a/pumpkin/src/configuration.rs b/pumpkin/src/config/configuration.rs similarity index 92% rename from pumpkin/src/configuration.rs rename to pumpkin/src/config/configuration.rs index 37fa53baa..9f28a0f72 100644 --- a/pumpkin/src/configuration.rs +++ b/pumpkin/src/config/configuration.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{entity::player::GameMode, server::Difficulty}; +use super::auth_config::Authentication; + /// Current Config version of the Base Config const CURRENT_BASE_VERSION: &str = "1.0.0"; @@ -12,19 +14,15 @@ const CURRENT_BASE_VERSION: &str = "1.0.0"; /// This also allows you get some Performance or Resource boosts. /// Important: The Configuration should match Vanilla by default pub struct AdvancedConfiguration { - /// Requires Online mode - /// Should player have skins - pub use_skins: bool, - /// Should chat be enabled - pub enable_chat: bool, - pub commands: Commands, + pub authentication: Authentication, } #[derive(Deserialize, Serialize)] pub struct Commands { - // Are commands from the Console accepted ? + /// Are commands from the Console accepted ? pub use_console: bool, + // todo commands... } impl Default for Commands { @@ -37,8 +35,7 @@ impl Default for Commands { impl Default for AdvancedConfiguration { fn default() -> Self { Self { - use_skins: true, - enable_chat: true, + authentication: Authentication::default(), commands: Commands::default(), } } @@ -74,8 +71,6 @@ pub struct BasicConfiguration { pub online_mode: bool, /// Whether packet encryption is enabled. Required when online mode is enabled. pub encryption: bool, - /// Whether to prevent proxy connections. - pub prevent_proxy_connections: bool, /// The server's description displayed on the status screen. pub motd: String, /// The default game mode for players. @@ -99,7 +94,6 @@ impl Default for BasicConfiguration { hardcore: false, online_mode: true, encryption: true, - prevent_proxy_connections: true, motd: "A Blazing fast Pumpkin Server!".to_string(), default_gamemode: GameMode::Survival, } diff --git a/pumpkin/src/config/mod.rs b/pumpkin/src/config/mod.rs new file mode 100644 index 000000000..3430a2330 --- /dev/null +++ b/pumpkin/src/config/mod.rs @@ -0,0 +1,154 @@ +use std::path::Path; + +use auth_config::Authentication; +use serde::{Deserialize, Serialize}; + +use crate::{entity::player::GameMode, server::Difficulty}; + +pub mod auth_config; + +/// Current Config version of the Base Config +const CURRENT_BASE_VERSION: &str = "1.0.0"; + +#[derive(Deserialize, Serialize)] +/// The idea is that Pumpkin should very customizable, You can Enable or Disable Features depning on your needs. +/// This also allows you get some Performance or Resource boosts. +/// Important: The Configuration should match Vanilla by default +pub struct AdvancedConfiguration { + pub commands: Commands, + pub authentication: Authentication, +} + +#[derive(Deserialize, Serialize)] +pub struct Commands { + /// Are commands from the Console accepted ? + pub use_console: bool, + // todo commands... +} + +impl Default for Commands { + fn default() -> Self { + Self { use_console: true } + } +} + +/// Important: The Configuration should match Vanilla by default +impl Default for AdvancedConfiguration { + fn default() -> Self { + Self { + authentication: Authentication::default(), + commands: Commands::default(), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct BasicConfiguration { + /// A version identifier for the configuration format. + pub config_version: String, + /// The address to bind the server to. + pub server_address: String, + /// The port to listen on. + pub server_port: u16, + /// The seed for world generation. + pub seed: String, + /// The maximum number of players allowed on the server. + pub max_players: u32, + /// The maximum view distance for players. + pub view_distance: u8, + /// The maximum simulated view distance. + pub simulation_distance: u8, + /// The path to the resource pack. + pub resource_pack: String, + /// The SHA1 hash of the resource pack. + pub resource_pack_sha1: String, + /// The default game difficulty. + pub default_difficulty: Difficulty, + /// Whether the Nether dimension is enabled. + pub allow_nether: bool, + /// Whether the server is in hardcore mode. + pub hardcore: bool, + /// Whether online mode is enabled. Requires valid Minecraft accounts. + pub online_mode: bool, + /// Whether packet encryption is enabled. Required when online mode is enabled. + pub encryption: bool, + /// The server's description displayed on the status screen. + pub motd: String, + /// The default game mode for players. + pub default_gamemode: GameMode, +} + +impl Default for BasicConfiguration { + fn default() -> Self { + Self { + config_version: CURRENT_BASE_VERSION.to_string(), + server_address: "127.0.0.1".to_string(), + server_port: 25565, + seed: "".to_string(), + max_players: 100000, + view_distance: 10, + simulation_distance: 10, + resource_pack: "".to_string(), + resource_pack_sha1: "".to_string(), + default_difficulty: Difficulty::Normal, + allow_nether: true, + hardcore: false, + online_mode: true, + encryption: true, + motd: "A Blazing fast Pumpkin Server!".to_string(), + default_gamemode: GameMode::Survival, + } + } +} + +impl AdvancedConfiguration { + pub fn load>(path: P) -> AdvancedConfiguration { + if path.as_ref().exists() { + let toml = std::fs::read_to_string(path).expect("Couldn't read configuration"); + toml::from_str(toml.as_str()).expect("Couldn't parse, Proberbly old config") + } else { + let config = AdvancedConfiguration::default(); + let toml = toml::to_string(&config).expect("Couldn't create toml!"); + std::fs::write(path, toml).expect("Couldn't save configuration"); + config + } + } +} + +impl BasicConfiguration { + pub fn load>(path: P) -> BasicConfiguration { + if path.as_ref().exists() { + let toml = std::fs::read_to_string(path).expect("Couldn't read configuration"); + toml::from_str(toml.as_str()).expect("Couldn't parse") + } else { + let config = BasicConfiguration::default(); + let toml = toml::to_string(&config).expect("Couldn't create toml!"); + std::fs::write(path, toml).expect("Couldn't save configuration"); + config.validate(); + config + } + } + + pub fn validate(&self) { + assert_eq!( + self.config_version, CURRENT_BASE_VERSION, + "Config version does not match used Config version. Please update your config" + ); + assert!(self.view_distance >= 2, "View distance must be atleast 2"); + assert!( + self.view_distance <= 32, + "View distance must be less than 32" + ); + if self.online_mode { + assert!( + self.encryption, + "When Online Mode is enabled, Encryption must be enabled" + ) + } + assert_eq!( + !self.resource_pack.is_empty(), + !self.resource_pack_sha1.is_empty(), + "Resource Pack path or Sha1 hash is missing" + ); + } +} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 13c25ce8f..0b5372ae2 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -4,12 +4,12 @@ use std::io::{self}; use client::Client; use commands::handle_command; -use configuration::AdvancedConfiguration; +use config::AdvancedConfiguration; use std::{collections::HashMap, rc::Rc, thread}; use client::interrupted; -use configuration::BasicConfiguration; +use config::BasicConfiguration; use server::Server; // Setup some tokens to allow us to identify which event is for which socket. @@ -17,7 +17,7 @@ const SERVER: Token = Token(0); pub mod client; pub mod commands; -pub mod configuration; +pub mod config; pub mod entity; pub mod server; pub mod util; diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index c60fa6ee2..bc73ee60a 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize}; use crate::{ client::Client, - configuration::{AdvancedConfiguration, BasicConfiguration}, + config::{AdvancedConfiguration, BasicConfiguration}, entity::player::{GameMode, Player}, };