From 8a5e69f8e314b77c6d9f44cc23eb9943f4d0de9e Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Mon, 29 Jul 2024 23:53:58 +0200 Subject: [PATCH] added encrytion --- Cargo.lock | 372 +++++++++++++++++++ pumpkin/Cargo.toml | 12 + pumpkin/src/client/client_packet.rs | 51 ++- pumpkin/src/client/mod.rs | 119 +++--- pumpkin/src/client/packet_decoder.rs | 112 ++++++ pumpkin/src/client/packet_encoder.rs | 74 ++++ pumpkin/src/main.rs | 8 +- pumpkin/src/protocol/bytebuf/buffer.rs | 87 ++++- pumpkin/src/protocol/client/login/mod.rs | 122 ++++++ pumpkin/src/protocol/client/mod.rs | 1 + pumpkin/src/protocol/mod.rs | 85 ++++- pumpkin/src/protocol/server/handshake/mod.rs | 2 +- pumpkin/src/protocol/server/login/mod.rs | 72 ++++ pumpkin/src/protocol/server/mod.rs | 1 + pumpkin/src/server.rs | 43 ++- pumpkin/src/util/mod.rs | 1 + 16 files changed, 1104 insertions(+), 58 deletions(-) create mode 100644 pumpkin/src/client/packet_decoder.rs create mode 100644 pumpkin/src/client/packet_encoder.rs create mode 100644 pumpkin/src/protocol/client/login/mod.rs create mode 100644 pumpkin/src/protocol/server/login/mod.rs create mode 100644 pumpkin/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 029703abe..85b08fed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "autocfg" version = "1.3.0" @@ -20,6 +37,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -44,12 +67,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" +[[package]] +name = "bytes" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" + +[[package]] +name = "cfb8" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014c0a0e1ad0dae6a86c082db2f9bd7fe8c2c734227047d0d8b4d4a3a094a1e1" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "colored" version = "2.1.0" @@ -60,6 +108,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -84,6 +147,27 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -93,6 +177,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "const-oid", + "crypto-common", +] + [[package]] name = "fdeflate" version = "0.3.4" @@ -112,6 +206,27 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -130,6 +245,15 @@ dependencies = [ "png", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.11" @@ -141,6 +265,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -148,6 +275,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "log" version = "0.4.21" @@ -177,12 +310,59 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -190,6 +370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -201,6 +382,36 @@ dependencies = [ "libc", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "png" version = "0.17.13" @@ -220,6 +431,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.86" @@ -233,12 +450,20 @@ dependencies = [ name = "pumpkin" version = "0.1.0" dependencies = [ + "aes", + "anyhow", "base64", "byteorder", + "bytes", + "cfb8", "crossbeam-channel", + "flate2", "image", "log", "mio", + "rand", + "rsa", + "rsa-der", "serde", "serde_json", "simple_logger", @@ -254,6 +479,65 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rsa-der" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19473b2de3164677ff38e4309c42448ba8d0fe5ad5fa722e7d278f991859aa6" +dependencies = [ + "simple_asn1", +] + [[package]] name = "ryu" version = "1.0.18" @@ -291,12 +575,34 @@ dependencies = [ "serde", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "simple_logger" version = "5.0.0" @@ -309,6 +615,34 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.72" @@ -320,6 +654,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.36" @@ -353,6 +707,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -365,6 +725,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -509,3 +875,9 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 926dc108d..dccbd49ef 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -8,6 +8,18 @@ edition = "2021" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +rand = "0.8.5" + +rsa = "0.9.6" +rsa-der = "0.3.0" + +aes = "0.8.4" +cfb8 = "0.8.1" +flate2 = "1.0.30" +bytes = "1.6.1" + +anyhow = "1.0.86" + base64 = "0.22.1" image = { version = "0.25", default-features = false, features = ["png"]} diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 32ae90527..77c0aa688 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -1,11 +1,15 @@ - use crate::protocol::{ - client::status::{CPingResponse, CStatusResponse}, + client::{ + login::{CEncryptionRequest, CLoginSuccess}, + status::{CPingResponse, CStatusResponse}, + }, server::{ handshake::SHandShake, + login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, status::{SPingRequest, SStatusRequest}, }, + ConnectionState, }; use super::Client; @@ -16,6 +20,11 @@ pub trait ClientPacketProcessor { // Status fn handle_status_request(&mut self, status_request: SStatusRequest); fn handle_ping_request(&mut self, ping_request: SPingRequest); + // Login + fn handle_login_start(&mut self, login_start: SLoginStart); + fn handle_encryption_response(&mut self, encryption_response: SEncryptionResponse); + fn handle_plugin_response(&mut self, plugin_response: SLoginPluginResponse); + fn handle_login_acknowledged(&mut self, login_acknowledged: SLoginAcknowledged); } impl ClientPacketProcessor for Client { @@ -38,4 +47,42 @@ impl ClientPacketProcessor for Client { self.send_packet(CPingResponse::new(ping_request.payload)); self.close(); } + + fn handle_login_start(&mut self, login_start: SLoginStart) { + dbg!("login start"); + self.name = Some(login_start.name); + self.uuid = Some(login_start.uuid); + let verify_token: [u8; 4] = rand::random(); + let public_key_der = &self.server.to_owned().public_key_der; + let packet = CEncryptionRequest::new( + "".into(), + public_key_der.len() as i32, + public_key_der, + verify_token.len() as i32, + &verify_token, + false, // TODO + ); + self.send_packet(packet); + } + + fn handle_encryption_response(&mut self, encryption_response: SEncryptionResponse) { + dbg!("encryption response"); + // should be impossible + if self.uuid.is_none() || self.name.is_none() { + self.kick("UUID or Name is none".into()); + return; + } + self.enable_encryption(encryption_response.shared_secret) + .unwrap(); + + let packet = CLoginSuccess::new(self.uuid.unwrap(), self.name.clone().unwrap(), 0, false); + self.send_packet(packet); + } + + fn handle_plugin_response(&mut self, plugin_response: SLoginPluginResponse) {} + + fn handle_login_acknowledged(&mut self, login_acknowledged: SLoginAcknowledged) { + self.connection_state = ConnectionState::Config; + dbg!("login achnowlaged"); + } } diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index dae67ea28..9a6322539 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -6,8 +6,10 @@ use std::{ use crate::{ protocol::{ + client::login::CLoginDisconnect, server::{ handshake::SHandShake, + login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, status::{SPingRequest, SStatusRequest}, }, ClientPacket, RawPacket, @@ -15,30 +17,48 @@ use crate::{ server::Server, }; -use crate::protocol::{bytebuf::buffer::ByteBuffer, ConnectionState}; +use crate::protocol::ConnectionState; +use anyhow::Context; use mio::{event::Event, net::TcpStream, Registry}; +use packet_decoder::PacketDecoder; +use packet_encoder::PacketEncoder; +use rsa::Pkcs1v15Encrypt; use std::io::Read; mod client_packet; pub mod player; +mod packet_decoder; +mod packet_encoder; + use client_packet::ClientPacketProcessor; +pub const MAX_PACKET_SIZE: i32 = 2097152; + pub struct Client { + pub name: Option, + pub uuid: Option, pub server: Rc, pub connection_state: ConnectionState, + pub encrytion: bool, pub closed: bool, pub connection: TcpStream, - // Packets coming from the client -> server - client_packets_queue: VecDeque, + enc: PacketEncoder, + dec: PacketDecoder, + pub client_packets_queue: VecDeque, } impl Client { pub fn new(server: Rc, connection: TcpStream) -> Self { Self { + name: None, + uuid: None, server, connection_state: ConnectionState::HandShake, connection, + enc: PacketEncoder::default(), + dec: PacketDecoder::default(), + encrytion: true, closed: false, client_packets_queue: VecDeque::new(), } @@ -48,26 +68,26 @@ impl Client { self.client_packets_queue.push_back(packet); } - pub fn send_packet(&mut self, packet: P) { - dbg!("WRITING"); - let mut packet_buf = ByteBuffer::new(); - packet.write(&mut packet_buf); - // Creating empty packet's buffer - let mut packet = ByteBuffer::new(); - // Creating length's bytes buffer and fill it as VarInt - let mut len_bytes = ByteBuffer::new(); - len_bytes.write_var_int(P::PACKET_ID); - // Writing full packet's length(content + length's bytes) - packet.write_var_int((packet_buf.len() + len_bytes.len()) as i32); - // Writing length bytes - packet.write_all(len_bytes.as_bytes()).unwrap(); - // Drop(Free) length bytes buffer - drop(len_bytes); - // Writing some packet's content - packet.write_all(packet_buf.as_bytes()).unwrap(); + pub fn enable_encryption(&mut self, shared_secret: Vec) -> anyhow::Result<()> { + self.encrytion = true; + let shared_secret = self + .server + .private_key + .decrypt(Pkcs1v15Encrypt, &shared_secret) + .context("failed to decrypt shared secret")?; + let crypt_key: [u8; 16] = shared_secret + .as_slice() + .try_into() + .context("shared secret has the wrong length")?; + self.dec.enable_encryption(&crypt_key); + self.enc.enable_encryption(&crypt_key); + Ok(()) + } - self.connection.write_all(packet.as_bytes()).unwrap(); - self.connection.flush().unwrap(); // todo do not flush every time + pub fn send_packet(&mut self, packet: P) { + dbg!("sending packet"); + self.enc.append_packet(packet).unwrap(); + self.connection.write_all(&self.enc.take()).unwrap(); } pub fn procress_packets(&mut self) { @@ -100,16 +120,28 @@ impl Client { packet.id ), }, + crate::protocol::ConnectionState::Login => match packet.id { + SLoginStart::PACKET_ID => self.handle_login_start(SLoginStart::read(bytebuf)), + SEncryptionResponse::PACKET_ID => { + self.handle_encryption_response(SEncryptionResponse::read(bytebuf)) + } + SLoginPluginResponse::PACKET_ID => { + self.handle_plugin_response(SLoginPluginResponse::read(bytebuf)) + } + SLoginAcknowledged::PACKET_ID => { + self.handle_login_acknowledged(SLoginAcknowledged::read(bytebuf)) + } + _ => log::error!( + "Failed to handle packet id {} while in Login state", + packet.id + ), + }, _ => log::error!("Invalid Connection state {:?}", self.connection_state), } } /// Returns `true` if the connection is done. - pub fn handle_connection_event( - &mut self, - _registry: &Registry, - event: &Event, - ) -> io::Result<()> { + pub fn poll(&mut self, _registry: &Registry, event: &Event) -> anyhow::Result<()> { if event.is_readable() { let mut received_data = vec![0; 4096]; let mut bytes_read = 0; @@ -133,33 +165,32 @@ impl Client { Err(ref err) if would_block(err) => break, Err(ref err) if interrupted(err) => continue, // Other errors we'll consider fatal. - Err(err) => return Err(err), + Err(err) => return anyhow::bail!(err), } } if bytes_read != 0 { - let received_data = &received_data[..bytes_read]; - let mut bytebuf = ByteBuffer::from_bytes(received_data); - let packet = RawPacket { - len: bytebuf.read_var_int().unwrap(), - id: bytebuf.read_var_int().unwrap(), - bytebuf, - }; - if packet.len + 1 != received_data.len() as i32 { - log::error!( - "Packet length does not match Data length: {} {}", - packet.len, - received_data.len() - ); + self.dec.reserve(4096); + self.dec.queue_slice(&received_data[..bytes_read]); + if let Some(packet) = self.dec.decode()? { + self.add_packet(packet); + self.procress_packets(); } - dbg!(&packet); - self.add_packet(packet); - self.procress_packets(); + self.dec.clear(); } } Ok(()) } + pub fn kick(&mut self, reason: String) { + // Todo + if self.connection_state == ConnectionState::Login { + self.send_packet(CLoginDisconnect::new(reason)); + } + self.close() + } + + // Kick before when needed pub fn close(&mut self) { self.closed = true; } diff --git a/pumpkin/src/client/packet_decoder.rs b/pumpkin/src/client/packet_decoder.rs new file mode 100644 index 000000000..74c6e5a38 --- /dev/null +++ b/pumpkin/src/client/packet_decoder.rs @@ -0,0 +1,112 @@ + +use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockSizeUser, KeyIvInit}; +use anyhow::{bail, ensure, Context}; +use bytes::{Buf, BytesMut}; + +use crate::{ + client::MAX_PACKET_SIZE, + protocol::{bytebuf::buffer::ByteBuffer, RawPacket, VarInt32, VarIntDecodeError}, +}; + +type Cipher = cfb8::Decryptor; + +// Decoder: Client -> Server +#[derive(Default)] +pub struct PacketDecoder { + buf: BytesMut, + + cipher: Option, +} + +impl PacketDecoder { + pub fn decode(&mut self) -> anyhow::Result> { + let mut r = &self.buf[..]; + + let packet_len = match VarInt32::decode_partial(&mut r) { + Ok(len) => len, + Err(VarIntDecodeError::Incomplete) => return Ok(None), + Err(VarIntDecodeError::TooLarge) => bail!("malformed packet length VarInt"), + }; + + ensure!( + (0..=MAX_PACKET_SIZE).contains(&packet_len), + "packet length of {packet_len} is out of bounds" + ); + + if r.len() < packet_len as usize { + // Not enough data arrived yet. + return Ok(None); + } + + let packet_len_len = VarInt32(packet_len).written_size(); + + let mut data; + + // no compression + + self.buf.advance(packet_len_len); + data = self.buf.split_to(packet_len as usize); + + r = &data[..]; + let packet_id = VarInt32::decode(&mut r) + .context("failed to decode packet ID")? + .0; + + data.advance(data.len() - r.len()); + dbg!(packet_id); + Ok(Some(RawPacket { + len: packet_len, + id: packet_id, + bytebuf: ByteBuffer::from_bytes(&data), + })) + } + + pub fn enable_encryption(&mut self, key: &[u8; 16]) { + assert!(self.cipher.is_none(), "encryption is already enabled"); + + let mut cipher = Cipher::new_from_slices(key, key).expect("invalid key"); + + // Don't forget to decrypt the data we already have. + Self::decrypt_bytes(&mut cipher, &mut self.buf); + + self.cipher = Some(cipher); + } + + fn decrypt_bytes(cipher: &mut Cipher, bytes: &mut [u8]) { + for chunk in bytes.chunks_mut(Cipher::block_size()) { + let gen_arr = GenericArray::from_mut_slice(chunk); + cipher.decrypt_block_mut(gen_arr); + } + } + + pub fn queue_bytes(&mut self, mut bytes: BytesMut) { + if let Some(cipher) = &mut self.cipher { + Self::decrypt_bytes(cipher, &mut bytes); + } + + self.buf.unsplit(bytes); + } + + pub fn queue_slice(&mut self, bytes: &[u8]) { + let len = self.buf.len(); + + self.buf.extend_from_slice(bytes); + + if let Some(cipher) = &mut self.cipher { + let slice = &mut self.buf[len..]; + Self::decrypt_bytes(cipher, slice); + } + } + + pub fn take_capacity(&mut self) -> BytesMut { + self.buf.split_off(self.buf.len()) + } + + pub fn clear(&mut self) { + self.buf.clear() + } + + pub fn reserve(&mut self, additional: usize) { + self.buf.reserve(additional); + } +} diff --git a/pumpkin/src/client/packet_encoder.rs b/pumpkin/src/client/packet_encoder.rs new file mode 100644 index 000000000..68c355045 --- /dev/null +++ b/pumpkin/src/client/packet_encoder.rs @@ -0,0 +1,74 @@ +use std::io::Write; + +use aes::cipher::{generic_array::GenericArray, BlockEncryptMut, BlockSizeUser, KeyIvInit}; +use anyhow::{ensure, Context}; +use bytes::{BufMut, BytesMut}; +use rsa::pkcs8::der::Encode; + +use crate::{ + client::MAX_PACKET_SIZE, + protocol::{bytebuf::buffer::ByteBuffer, ClientPacket, VarInt32}, +}; + +type Cipher = cfb8::Encryptor; + +// Encoder: Server -> Client +#[derive(Default)] +pub struct PacketEncoder { + buf: BytesMut, + + cipher: Option, +} + +impl PacketEncoder { + pub fn append_packet(&mut self, packet: P) -> anyhow::Result<()> { + let start_len = self.buf.len(); + + let mut writer = (&mut self.buf).writer(); + + let mut packet_buf = ByteBuffer::new(); + VarInt32(P::PACKET_ID) + .encode(&mut writer) + .context("failed to encode packet ID")?; + packet.write(&mut packet_buf); + + writer.write(packet_buf.as_bytes()).unwrap(); + + let data_len = self.buf.len() - start_len; + + if false { // compression + } + let packet_len = data_len; + + ensure!( + packet_len <= MAX_PACKET_SIZE as usize, + "packet exceeds maximum length" + ); + + let packet_len_size = VarInt32(packet_len as i32).written_size(); + + self.buf.put_bytes(0, packet_len_size); + self.buf + .copy_within(start_len..start_len + data_len, start_len + packet_len_size); + + let front = &mut self.buf[start_len..]; + VarInt32(packet_len as i32).encode(front)?; + Ok(()) + } + + pub fn enable_encryption(&mut self, key: &[u8; 16]) { + assert!(self.cipher.is_none(), "encryption is already enabled"); + self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key")); + } + + pub fn take(&mut self) -> BytesMut { + if let Some(cipher) = &mut self.cipher { + for chunk in self.buf.chunks_mut(Cipher::block_size()) { + let gen_arr = GenericArray::from_mut_slice(chunk); + cipher.encrypt_block_mut(gen_arr); + } + } + + self.buf.split() + } +} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 232674b21..20760fc6f 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -1,3 +1,5 @@ +#![feature(read_buf)] + use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; use std::collections::HashMap; @@ -9,6 +11,7 @@ const SERVER: Token = Token(0); pub mod client; pub mod protocol; pub mod server; +pub mod util; #[cfg(not(target_os = "wasi"))] fn main() -> io::Result<()> { @@ -37,8 +40,7 @@ fn main() -> io::Result<()> { // Unique token for each incoming connection. let mut unique_token = Token(SERVER.0 + 1); - log::info!("You can connect to the server using `nc`:"); - log::info!(" $ nc 127.0.0.1 9000"); + log::info!("You now can connect to the server"); let server = Rc::new(Server::new()); @@ -85,7 +87,7 @@ fn main() -> io::Result<()> { token => { // Maybe received an event for a TCP connection. let done = if let Some(client) = connections.get_mut(&token) { - client.handle_connection_event(poll.registry(), event)?; + client.poll(poll.registry(), event).unwrap(); client.closed } else { // Sporadic events happen, we can safely ignore them. diff --git a/pumpkin/src/protocol/bytebuf/buffer.rs b/pumpkin/src/protocol/bytebuf/buffer.rs index e7f381f72..1afa1a458 100644 --- a/pumpkin/src/protocol/bytebuf/buffer.rs +++ b/pumpkin/src/protocol/bytebuf/buffer.rs @@ -219,6 +219,33 @@ impl ByteBuffer { } } + pub fn write_bool(&mut self, v: bool) { + if v { + self.write_u8(1); + } else { + self.write_u8(0); + } + } + + pub fn write_bytes_len(&mut self, bytes: &[u8], max_len: usize) { + self.flush_bits(); + + let size = bytes.len() + self.wpos; + if size > max_len { + eprintln!("Write: size > max size"); + return; + } + + if size > self.data.len() { + self.resize(size); + } + + for v in bytes { + self.data[self.wpos] = *v; + self.wpos += 1; + } + } + /// Append a byte (8 bits value) to the buffer /// _Note_: This method resets the read and write cursor for bitwise reading. /// @@ -388,8 +415,12 @@ impl ByteBuffer { /// buffer.write_string("Hello") /// ``` pub fn write_string(&mut self, val: &str) { + self.write_string_len(val, 32767) + } + + pub fn write_string_len(&mut self, val: &str, max_len: usize) { self.write_var_int(val.len() as VarInt); - self.write_bytes(val.as_bytes()); + self.write_bytes_len(val.as_bytes(), max_len); } // Read operations @@ -435,6 +466,10 @@ impl ByteBuffer { Ok(self.data[pos]) } + pub fn read_bool(&mut self) -> Result { + Ok(self.read_u8()? != 0) + } + /// Same as `read_u8()` but for signed values pub fn read_i8(&mut self) -> Result { Ok(self.read_u8()? as i8) @@ -494,6 +529,25 @@ impl ByteBuffer { read_number!(self, read_u64, 8) } + /// Reads a boolean. If true, the closure is called, and the returned value is + /// wrapped in Some. Otherwise, this returns None. + pub fn read_option(&mut self, val: impl FnOnce(&mut ByteBuffer) -> T) -> Result> { + if self.read_bool()? { + Ok(Some(val(self))) + } else { + Ok(None) + } + } + /// Writes `true` if the option is Some, or `false` if None. If the option is + /// some, then it also calls the `write` closure. + pub fn write_option(&mut self, val: &Option, write: impl FnOnce(&mut ByteBuffer, &T)) { + self.write_bool(val.is_some()); + match val { + Some(v) => write(self, v), + None => {} + } + } + pub fn read_var_int(&mut self) -> Result { let mut value: i32 = 0; let mut position: i32 = 0; @@ -564,13 +618,42 @@ impl ByteBuffer { /// that must be encoded as UTF8. /// _Note_: This method resets the read and write cursor for bitwise reading. pub fn read_string(&mut self) -> Result { + self.read_string_len(32767) + } + + pub fn read_string_len(&mut self, max_size: usize) -> Result { let size = self.read_var_int()?; - match String::from_utf8(self.read_bytes(size as usize)?) { + if size as usize > max_size { + return Err(Error::new( + ErrorKind::InvalidData, + "String length is bigger than max size", + )); + } + let data = self.read_bytes(size as usize)?; + if data.len() > max_size { + return Err(Error::new( + ErrorKind::InvalidData, + "String is bigger than max size", + )); + } + match String::from_utf8(data) { Ok(string_result) => Ok(string_result), Err(e) => Err(Error::new(ErrorKind::InvalidData, e)), } } + /// Reads 16 bytes from the buffer, and returns that as a big endian UUID. + pub fn read_uuid(&mut self) -> Result { + let mut bytes = [0u8; 16]; + self.read_exact(&mut bytes)?; + uuid::Uuid::from_slice(&bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e)) + } + + /// This writes a UUID into the buffer (in big endian format). + pub fn write_uuid(&mut self, v: uuid::Uuid) { + self.write_bytes(v.as_bytes()); + } + // Other /// Dump the byte buffer to a string. diff --git a/pumpkin/src/protocol/client/login/mod.rs b/pumpkin/src/protocol/client/login/mod.rs new file mode 100644 index 000000000..b028425b3 --- /dev/null +++ b/pumpkin/src/protocol/client/login/mod.rs @@ -0,0 +1,122 @@ +use crate::protocol::{bytebuf::buffer::ByteBuffer, ClientPacket, VarInt}; + +pub struct CLoginDisconnect { + reason: String, +} + +impl CLoginDisconnect { + pub fn new(reason: String) -> Self { + Self { reason } + } +} + +impl ClientPacket for CLoginDisconnect { + const PACKET_ID: VarInt = 0; + + fn write(&self, bytebuf: &mut ByteBuffer) { + bytebuf.write_string(&serde_json::to_string_pretty(&self.reason).unwrap()); + } +} + +pub struct CEncryptionRequest<'a> { + server_id: String, // 20 + public_key_length: VarInt, + public_key: &'a [u8], + verify_token_length: VarInt, + verify_token: &'a [u8], + should_authenticate: bool, +} + +impl<'a> CEncryptionRequest<'a> { + pub fn new( + server_id: String, + public_key_length: VarInt, + public_key: &'a [u8], + verify_token_length: VarInt, + verify_token: &'a [u8], + should_authenticate: bool, + ) -> Self { + Self { + server_id, + public_key_length, + public_key, + verify_token_length, + verify_token, + should_authenticate, + } + } +} + +impl<'a> ClientPacket for CEncryptionRequest<'a> { + const PACKET_ID: VarInt = 1; + + fn write(&self, bytebuf: &mut ByteBuffer) { + bytebuf.write_string_len(self.server_id.as_str(), 20); + bytebuf.write_var_int(self.public_key_length); + bytebuf.write_bytes(self.public_key); + bytebuf.write_var_int(self.verify_token_length); + bytebuf.write_bytes(self.verify_token); + bytebuf.write_bool(self.should_authenticate); + } +} + +pub struct CSetCompression { + threshold: VarInt, +} + +pub struct CLoginSuccess { + pub uuid: uuid::Uuid, + pub username: String, // 16 + pub num_of_props: VarInt, + // pub property: Property, + pub strict_error_handling: bool, +} + +impl CLoginSuccess { + pub fn new( + uuid: uuid::Uuid, + username: String, + num_of_props: VarInt, + strict_error_handling: bool, + ) -> Self { + Self { + uuid, + username, + num_of_props, + strict_error_handling, + } + } +} + +pub struct Property { + name: String, + value: String, + is_signed: bool, + signature: Option, +} + +impl ClientPacket for CLoginSuccess { + const PACKET_ID: VarInt = 2; + + fn write(&self, bytebuf: &mut ByteBuffer) { + bytebuf.write_uuid(self.uuid); + bytebuf.write_string(&self.username); + bytebuf.write_var_int(self.num_of_props); + // Todo + bytebuf.write_bool(self.strict_error_handling); + } +} + +impl CSetCompression { + pub fn new(threshold: VarInt) -> Self { + Self { threshold } + } +} + +impl ClientPacket for CSetCompression { + const PACKET_ID: VarInt = 3; + + fn write(&self, bytebuf: &mut ByteBuffer) { + bytebuf.write_var_int(self.threshold); + } +} diff --git a/pumpkin/src/protocol/client/mod.rs b/pumpkin/src/protocol/client/mod.rs index 7befc88e7..5a39e3b3f 100644 --- a/pumpkin/src/protocol/client/mod.rs +++ b/pumpkin/src/protocol/client/mod.rs @@ -1,4 +1,5 @@ // Clientbound Packets // Server -> Client // +pub mod login; pub mod status; diff --git a/pumpkin/src/protocol/mod.rs b/pumpkin/src/protocol/mod.rs index 56fd39a56..9e25c8c3c 100644 --- a/pumpkin/src/protocol/mod.rs +++ b/pumpkin/src/protocol/mod.rs @@ -1,4 +1,8 @@ +use std::io::{Read, Write}; + +use anyhow::bail; use bytebuf::buffer::ByteBuffer; +use byteorder::ReadBytesExt; use serde::{Deserialize, Serialize}; pub mod bytebuf; @@ -6,15 +10,90 @@ pub mod bytebuf; pub mod client; pub mod server; -type VarInt = i32; -type VarLong = i64; +pub type VarInt = i32; -#[derive(Debug)] +pub struct VarInt32(pub i32); + +impl VarInt32 { + /// The maximum number of bytes a `VarInt` could occupy when read from and + /// written to the Minecraft protocol. + pub const MAX_SIZE: usize = 5; + + /// Returns the exact number of bytes this varint will write when + /// [`Encode::encode`] is called, assuming no error occurs. + pub const fn written_size(self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + pub fn decode_partial(mut r: R) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE { + let byte = r.read_u8().map_err(|_| VarIntDecodeError::Incomplete)?; + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(val); + } + } + + Err(VarIntDecodeError::TooLarge) + } + + pub fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { + let x = self.0 as u64; + let stage1 = (x & 0x000000000000007f) + | ((x & 0x0000000000003f80) << 1) + | ((x & 0x00000000001fc000) << 2) + | ((x & 0x000000000fe00000) << 3) + | ((x & 0x00000000f0000000) << 4); + + let leading = stage1.leading_zeros(); + + let unused_bytes = (leading - 1) >> 3; + let bytes_needed = 8 - unused_bytes; + + // set all but the last MSBs + let msbs = 0x8080808080808080; + let msbmask = 0xffffffffffffffff >> (((8 - bytes_needed + 1) << 3) - 1); + + let merged = stage1 | (msbs & msbmask); + let bytes = merged.to_le_bytes(); + + w.write_all(unsafe { bytes.get_unchecked(..bytes_needed as usize) })?; + + Ok(()) + } + + pub fn decode(r: &mut &[u8]) -> anyhow::Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE { + let byte = r.read_u8()?; + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarInt32(val)); + } + } + bail!("VarInt is too large") + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum VarIntDecodeError { + Incomplete, + TooLarge, +} + +pub type VarLong = i64; + +#[derive(Debug, PartialEq)] pub enum ConnectionState { HandShake, Status, Login, Transfer, + Config, } impl ConnectionState { diff --git a/pumpkin/src/protocol/server/handshake/mod.rs b/pumpkin/src/protocol/server/handshake/mod.rs index d63482b53..eaa9a6e98 100644 --- a/pumpkin/src/protocol/server/handshake/mod.rs +++ b/pumpkin/src/protocol/server/handshake/mod.rs @@ -13,7 +13,7 @@ impl SHandShake { pub fn read(bytebuf: &mut ByteBuffer) -> Self { Self { protocol_version: bytebuf.read_var_int().unwrap(), - server_address: bytebuf.read_string().unwrap(), + server_address: bytebuf.read_string_len(255).unwrap(), server_port: bytebuf.read_u16().unwrap(), next_state: ConnectionState::from_varint(bytebuf.read_var_int().unwrap()), } diff --git a/pumpkin/src/protocol/server/login/mod.rs b/pumpkin/src/protocol/server/login/mod.rs new file mode 100644 index 000000000..3ef70b46f --- /dev/null +++ b/pumpkin/src/protocol/server/login/mod.rs @@ -0,0 +1,72 @@ +use crate::protocol::{bytebuf::buffer::ByteBuffer, VarInt}; + +pub struct SLoginStart { + pub name: String, // 16 + pub uuid: uuid::Uuid, +} + +impl SLoginStart { + pub const PACKET_ID: VarInt = 0; + + pub fn read(bytebuf: &mut ByteBuffer) -> Self { + Self { + name: bytebuf.read_string_len(16).unwrap(), + uuid: bytebuf.read_uuid().unwrap(), + } + } +} + +pub struct SEncryptionResponse { + pub shared_secret_length: VarInt, + pub shared_secret: Vec, + pub verify_token_length: VarInt, + pub verify_token: Vec, +} + +impl SEncryptionResponse { + pub const PACKET_ID: VarInt = 1; + + pub fn read(bytebuf: &mut ByteBuffer) -> Self { + let shared_secret_length = bytebuf.read_var_int().unwrap(); + let shared_secret = bytebuf.read_bytes(shared_secret_length as usize).unwrap(); + let verify_token_length = bytebuf.read_var_int().unwrap(); + let verify_token = bytebuf.read_bytes(shared_secret_length as usize).unwrap(); + Self { + shared_secret_length, + shared_secret, + verify_token_length, + verify_token, + } + } +} + +pub struct SLoginPluginResponse<'a> { + message_id: VarInt, + successful: bool, + data: Option<&'a [u8]>, +} + +impl<'a> SLoginPluginResponse<'a> { + pub const PACKET_ID: VarInt = 2; + + pub fn read(bytebuf: &mut ByteBuffer) -> Self { + Self { + message_id: bytebuf.read_var_int().unwrap(), + successful: bytebuf.read_bool().unwrap(), + data: None, // TODO + } + } +} + +// Acknowledgement to the Login Success packet sent to the server. +pub struct SLoginAcknowledged { + // empty +} + +impl SLoginAcknowledged { + pub const PACKET_ID: VarInt = 3; + + pub fn read(_bytebuf: &mut ByteBuffer) -> Self { + Self {} + } +} diff --git a/pumpkin/src/protocol/server/mod.rs b/pumpkin/src/protocol/server/mod.rs index 70b6f91a9..e08af9d63 100644 --- a/pumpkin/src/protocol/server/mod.rs +++ b/pumpkin/src/protocol/server/mod.rs @@ -1,4 +1,5 @@ // Serverbound Packets // Client -> Server pub mod handshake; +pub mod login; pub mod status; diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 5d0527083..4f6f0da97 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -1,10 +1,19 @@ use std::io::Cursor; use base64::{engine::general_purpose, Engine}; +use rsa::{rand_core::OsRng, traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; use crate::protocol::{Players, Sample, StatusResponse, Version}; pub struct Server { + pub compression_threshold: Option, + + pub online_mode: bool, + pub encriyption: bool, // encription is always required when online_mode is disabled + pub public_key: RsaPublicKey, + pub private_key: RsaPrivateKey, + pub public_key_der: Box<[u8]>, + pub status_response: StatusResponse, } @@ -16,9 +25,32 @@ impl Default for Server { impl Server { pub fn new() -> Self { + let status_response = Self::default_response(); + + // todo, only create when needed + let (public_key, private_key) = Self::generate_keys(); + + let public_key_der = rsa_der::public_key_to_der( + &private_key.n().to_bytes_be(), + &private_key.e().to_bytes_be(), + ) + .into_boxed_slice(); + + Self { + online_mode: true, + encriyption: true, + compression_threshold: None, // 256 + public_key, + private_key, + status_response, + public_key_der, + } + } + + pub fn default_response() -> StatusResponse { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); - let status_response = StatusResponse { + StatusResponse { version: Version { name: "1.21".into(), protocol: 767, @@ -33,8 +65,7 @@ impl Server { }, description: "Pumpkin Server".into(), favicon: Self::load_icon(path), - }; - Self { status_response } + } } pub fn load_icon(path: &str) -> String { @@ -50,4 +81,10 @@ impl Server { general_purpose::STANDARD.encode_string(image, &mut result); result } + + pub fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) { + let priv_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("failed to generate a key"); + let pub_key = RsaPublicKey::from(&priv_key); + (pub_key, priv_key) + } } diff --git a/pumpkin/src/util/mod.rs b/pumpkin/src/util/mod.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/pumpkin/src/util/mod.rs @@ -0,0 +1 @@ +