added encrytion

This commit is contained in:
Snowiiii
2024-07-29 23:53:58 +02:00
parent b5541ab5ba
commit 8a5e69f8e3
16 changed files with 1104 additions and 58 deletions

372
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"]}

View File

@@ -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");
}
}

View File

@@ -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<String>,
pub uuid: Option<uuid::Uuid>,
pub server: Rc<Server>,
pub connection_state: ConnectionState,
pub encrytion: bool,
pub closed: bool,
pub connection: TcpStream,
// Packets coming from the client -> server
client_packets_queue: VecDeque<RawPacket>,
enc: PacketEncoder,
dec: PacketDecoder,
pub client_packets_queue: VecDeque<RawPacket>,
}
impl Client {
pub fn new(server: Rc<Server>, 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<P: ClientPacket>(&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<u8>) -> 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<P: ClientPacket>(&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;
}

View File

@@ -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<aes::Aes128>;
// Decoder: Client -> Server
#[derive(Default)]
pub struct PacketDecoder {
buf: BytesMut,
cipher: Option<Cipher>,
}
impl PacketDecoder {
pub fn decode(&mut self) -> anyhow::Result<Option<RawPacket>> {
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);
}
}

View File

@@ -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<aes::Aes128>;
// Encoder: Server -> Client
#[derive(Default)]
pub struct PacketEncoder {
buf: BytesMut,
cipher: Option<Cipher>,
}
impl PacketEncoder {
pub fn append_packet<P: ClientPacket>(&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()
}
}

View File

@@ -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.

View File

@@ -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<bool> {
Ok(self.read_u8()? != 0)
}
/// Same as `read_u8()` but for signed values
pub fn read_i8(&mut self) -> Result<i8> {
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<T>(&mut self, val: impl FnOnce(&mut ByteBuffer) -> T) -> Result<Option<T>> {
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<T>(&mut self, val: &Option<T>, 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<VarInt> {
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<String> {
self.read_string_len(32767)
}
pub fn read_string_len(&mut self, max_size: usize) -> Result<String> {
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<uuid::Uuid> {
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.

View File

@@ -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<String>,
}
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);
}
}

View File

@@ -1,4 +1,5 @@
// Clientbound Packets
// Server -> Client
//
pub mod login;
pub mod status;

View File

@@ -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<R: Read>(mut r: R) -> Result<i32, VarIntDecodeError> {
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<Self> {
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 {

View File

@@ -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()),
}

View File

@@ -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<u8>,
pub verify_token_length: VarInt,
pub verify_token: Vec<u8>,
}
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 {}
}
}

View File

@@ -1,4 +1,5 @@
// Serverbound Packets
// Client -> Server
pub mod handshake;
pub mod login;
pub mod status;

View File

@@ -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<u8>,
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)
}
}

1
pumpkin/src/util/mod.rs Normal file
View File

@@ -0,0 +1 @@