diff --git a/Cargo.lock b/Cargo.lock index a0d60170..8be7aa31 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "async-compression" version = "0.3.8" @@ -64,6 +76,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" name = "azalea-auth" version = "0.1.0" dependencies = [ + "aes", + "cfb8", + "num-bigint", + "rand", + "rsa_public_encrypt_pkcs1", + "sha-1", "uuid", ] @@ -84,6 +102,7 @@ dependencies = [ name = "azalea-client" version = "0.1.0" dependencies = [ + "azalea-auth", "azalea-protocol", ] @@ -141,6 +160,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array", +] + [[package]] name = "bot" version = "0.1.0" @@ -189,12 +217,40 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cfb8" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a4b6c43bf284e617a659ce5dc149676680530a3a4a9bb6b278d1a9ed5b229d" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "2.34.0" @@ -206,6 +262,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.0" @@ -297,6 +362,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "csv" version = "1.1.6" @@ -325,6 +400,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "digest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +dependencies = [ + "block-buffer", + "crypto-common", + "generic-array", +] + [[package]] name = "either" version = "1.6.1" @@ -427,6 +513,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -648,6 +744,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -659,6 +789,39 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -684,6 +847,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "packet-macros" version = "0.1.0" @@ -899,6 +1068,17 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rsa_public_encrypt_pkcs1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e9243a1f8b312c5535c09de102cc061416515201b194ee4f0a9a76da20ebf4" +dependencies = [ + "num", + "rand", + "simple_asn1", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -976,6 +1156,29 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "simple_asn1" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb4ea60fb301dc81dfc113df680571045d375ab7345d171c5dc7d7e13107a80" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", + "thiserror", +] + [[package]] name = "slab" version = "0.4.5" @@ -1150,6 +1353,12 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -1201,6 +1410,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "walkdir" version = "2.3.2" diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index aa9b7bdb..6279d903 100755 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,4 +6,10 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +aes = "0.7.4" +cfb8 = "0.7.1" +num-bigint = "^0.4.3" +rand = {version = "^0.8.4", features = ["getrandom"]} +rsa_public_encrypt_pkcs1 = "0.4.0" +sha-1 = "^0.10.0" uuid = "^0.8.2" diff --git a/azalea-auth/src/encryption.rs b/azalea-auth/src/encryption.rs new file mode 100644 index 00000000..6f2e43a8 --- /dev/null +++ b/azalea-auth/src/encryption.rs @@ -0,0 +1,94 @@ +use aes::{ + cipher::{AsyncStreamCipher, NewCipher}, + Aes128, +}; +use cfb8::Cfb8; +use rand::{rngs::OsRng, RngCore}; +use sha1::{Digest, Sha1}; + +fn generate_secret_key() -> [u8; 16] { + let mut key = [0u8; 16]; + OsRng.fill_bytes(&mut key); + key +} + +fn digest_data(server_id: &[u8], public_key: &[u8], private_key: &[u8]) -> Vec { + let mut digest = Sha1::new(); + digest.update(server_id); + digest.update(public_key); + digest.update(private_key); + digest.finalize().to_vec() +} + +fn hex_digest(digest: &[u8]) -> String { + // Note that the Sha1.hexdigest() method used by minecraft is non standard. + // It doesn't match the digest method found in most programming languages + // and libraries. It works by treating the sha1 output bytes as one large + // integer in two's complement and then printing the integer in base 16, + // placing a minus sign if the interpreted number is negative. + num_bigint::BigInt::from_signed_bytes_be(digest).to_str_radix(16) +} + +#[derive(Debug)] +pub struct EncryptResult { + pub secret_key: [u8; 16], + pub encrypted_public_key: Vec, + pub encrypted_nonce: Vec, +} + +pub fn encrypt(public_key: &[u8], nonce: &[u8]) -> Result { + // On receipt of a Encryption Request from the server, the client will + // generate a random 16-byte shared secret, to be used with the AES/CFB8 + // stream cipher. + let secret_key = generate_secret_key(); + // let hash = hex_digest(&digest_data(server_id.as_bytes(), public_key, &secret_key)); + + // this.keybytes = Crypt.encryptUsingKey(publicKey, secretKey.getEncoded()); + // this.nonce = Crypt.encryptUsingKey(publicKey, arrby); + let encrypted_public_key: Vec = + rsa_public_encrypt_pkcs1::encrypt(&public_key, &secret_key)?; + let encrypted_nonce: Vec = rsa_public_encrypt_pkcs1::encrypt(&public_key, &nonce)?; + + Ok(EncryptResult { + secret_key, + encrypted_public_key, + encrypted_nonce, + }) +} + +// TODO: update the aes and cfb8 crates +pub type Aes128Cfb = Cfb8; + +pub fn create_cipher(key: &[u8]) -> Aes128Cfb { + Aes128Cfb::new_from_slices(&key, &key).unwrap() +} + +pub fn encrypt_packet(cipher: &mut Aes128Cfb, packet: &mut [u8]) { + cipher.encrypt(packet); +} +pub fn decrypt_packet(cipher: &mut Aes128Cfb, packet: &mut [u8]) { + cipher.decrypt(packet); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_secret_key() { + let key = generate_secret_key(); + assert_eq!(key.len(), 16); + } + + #[test] + fn test_hex_digest() { + let digest = hex_digest(&digest_data(b"Notch", &[], &[])); + assert_eq!(digest, "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"); + + let digest = hex_digest(&digest_data(b"jeb_", &[], &[])); + assert_eq!(digest, "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"); + + let digest = hex_digest(&digest_data(b"simon", &[], &[])); + assert_eq!(digest, "88e16a1019277b15d58faf0541e11910eb756f6"); + } +} diff --git a/azalea-auth/src/lib.rs b/azalea-auth/src/lib.rs index 773ea1d9..afe9eb33 100755 --- a/azalea-auth/src/lib.rs +++ b/azalea-auth/src/lib.rs @@ -1,3 +1,6 @@ //! Handle Minecraft authentication. pub mod game_profile; +pub mod encryption; + + diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 7e22f727..5769f289 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -6,4 +6,5 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +azalea-auth = {path = "../azalea-auth"} azalea-protocol = {path = "../azalea-protocol"} diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index 6e58bee6..44eff33f 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -4,7 +4,10 @@ use azalea_protocol::{ packets::{ game::GamePacket, handshake::client_intention_packet::ClientIntentionPacket, - login::{serverbound_hello_packet::ServerboundHelloPacket, LoginPacket}, + login::{ + serverbound_hello_packet::ServerboundHelloPacket, + serverbound_key_packet::ServerboundKeyPacket, LoginPacket, + }, ConnectionProtocol, PROTOCOL_VERSION, }, resolver, ServerAddress, @@ -39,6 +42,20 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { Ok(packet) => match packet { LoginPacket::ClientboundHelloPacket(p) => { println!("Got encryption request {:?} {:?}", p.nonce, p.public_key); + let e = azalea_auth::encryption::encrypt(&p.public_key, &p.nonce).unwrap(); + + // TODO: authenticate with the server here (authenticateServer) + println!("Sending encryption response {:?}", e); + + conn.write( + ServerboundKeyPacket { + nonce: e.encrypted_nonce.into(), + shared_secret: e.encrypted_public_key.into(), + } + .get(), + ) + .await; + conn.set_encryption_key(e.secret_key); } LoginPacket::ClientboundLoginCompressionPacket(p) => { println!("Got compression request {:?}", p.compression_threshold); @@ -57,6 +74,7 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { LoginPacket::ClientboundCustomQueryPacket(p) => { println!("Got custom query {:?}", p); } + LoginPacket::ServerboundKeyPacket(_) => todo!(), }, Err(e) => { panic!("Error: {:?}", e); diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index 3d910d3a..e81bc368 100755 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs @@ -7,6 +7,7 @@ use crate::packets::status::StatusPacket; use crate::read::read_packet; use crate::write::write_packet; use crate::ServerIpAddress; +use azalea_auth::encryption::Aes128Cfb; use tokio::net::TcpStream; pub enum PacketFlow { @@ -25,6 +26,7 @@ pub struct GameConnection { /// The buffered writer pub stream: TcpStream, pub compression_threshold: Option, + pub cipher: Option, } pub struct StatusConnection { @@ -38,6 +40,7 @@ pub struct LoginConnection { /// The buffered writer pub stream: TcpStream, pub compression_threshold: Option, + pub cipher: Option, } impl HandshakeConnection { @@ -65,6 +68,7 @@ impl HandshakeConnection { flow: self.flow, stream: self.stream, compression_threshold: None, + cipher: None, } } @@ -76,46 +80,69 @@ impl HandshakeConnection { } pub async fn read(&mut self) -> Result { - read_packet::(&self.flow, &mut self.stream, None).await + read_packet::(&self.flow, &mut self.stream, None, &mut None).await } /// Write a packet to the server pub async fn write(&mut self, packet: HandshakePacket) { - write_packet(packet, &mut self.stream, None).await; + write_packet(packet, &mut self.stream, None, &mut None).await; } } impl GameConnection { pub async fn read(&mut self) -> Result { - read_packet::(&self.flow, &mut self.stream, self.compression_threshold).await + read_packet::( + &self.flow, + &mut self.stream, + self.compression_threshold, + &mut self.cipher, + ) + .await } /// Write a packet to the server pub async fn write(&mut self, packet: GamePacket) { - write_packet(packet, &mut self.stream, self.compression_threshold).await; + write_packet( + packet, + &mut self.stream, + self.compression_threshold, + &mut self.cipher, + ) + .await; } } impl StatusConnection { pub async fn read(&mut self) -> Result { - read_packet::(&self.flow, &mut self.stream, None).await + read_packet::(&self.flow, &mut self.stream, None, &mut None).await } /// Write a packet to the server pub async fn write(&mut self, packet: StatusPacket) { - write_packet(packet, &mut self.stream, None).await; + write_packet(packet, &mut self.stream, None, &mut None).await; } } impl LoginConnection { pub async fn read(&mut self) -> Result { - read_packet::(&self.flow, &mut self.stream, self.compression_threshold) - .await + read_packet::( + &self.flow, + &mut self.stream, + self.compression_threshold, + &mut self.cipher, + ) + .await } /// Write a packet to the server pub async fn write(&mut self, packet: LoginPacket) { - write_packet(packet, &mut self.stream, self.compression_threshold).await; + write_packet( + packet, + &mut self.stream, + self.compression_threshold, + &mut self.cipher, + ) + .await; } pub fn set_compression_threshold(&mut self, threshold: i32) { @@ -127,11 +154,18 @@ impl LoginConnection { } } + pub fn set_encryption_key(&mut self, key: [u8; 16]) { + // minecraft has a cipher decoder and encoder, i don't think it matters though? + let cipher = azalea_auth::encryption::create_cipher(&key); + self.cipher = Some(cipher); + } + pub fn game(self) -> GameConnection { GameConnection { flow: self.flow, stream: self.stream, compression_threshold: self.compression_threshold, + cipher: self.cipher, } } } diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-protocol/src/mc_buf/mod.rs index 4ecb65d1..3ba6ac3e 100755 --- a/azalea-protocol/src/mc_buf/mod.rs +++ b/azalea-protocol/src/mc_buf/mod.rs @@ -5,11 +5,31 @@ mod write; pub use read::{McBufReadable, McBufVarintReadable, Readable}; pub use write::{McBufVarintWritable, McBufWritable, Writable}; +use std::ops::Deref; // const DEFAULT_NBT_QUOTA: u32 = 2097152; const MAX_STRING_LENGTH: u16 = 32767; // const MAX_COMPONENT_STRING_LENGTH: u32 = 262144; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ByteArray(Vec); + +impl Deref for ByteArray { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for ByteArray { + fn from(vec: Vec) -> Self { + Self(vec) + } +} + + #[cfg(test)] mod tests { use super::*; diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs index 1e031916..3d50e5aa 100755 --- a/azalea-protocol/src/mc_buf/read.rs +++ b/azalea-protocol/src/mc_buf/read.rs @@ -5,6 +5,7 @@ use azalea_core::{ }; use serde::Deserialize; use tokio::io::{AsyncRead, AsyncReadExt}; +use crate::mc_buf::ByteArray; use super::MAX_STRING_LENGTH; @@ -14,7 +15,7 @@ pub trait Readable { async fn read_varint(&mut self) -> Result; fn get_varint_size(&mut self, value: i32) -> u8; fn get_varlong_size(&mut self, value: i32) -> u8; - async fn read_byte_array(&mut self) -> Result, String>; + async fn read_byte_array(&mut self) -> Result; async fn read_bytes_with_len(&mut self, n: usize) -> Result, String>; async fn read_bytes(&mut self) -> Result, String>; async fn read_utf(&mut self) -> Result; @@ -80,9 +81,9 @@ where 10 } - async fn read_byte_array(&mut self) -> Result, String> { + async fn read_byte_array(&mut self) -> Result { let length = self.read_varint().await? as usize; - Ok(self.read_bytes_with_len(length).await?) + Ok(ByteArray(self.read_bytes_with_len(length).await?)) } async fn read_bytes_with_len(&mut self, n: usize) -> Result, String> { @@ -251,6 +252,17 @@ impl McBufReadable for Vec { } } + +#[async_trait] +impl McBufReadable for ByteArray { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_byte_array().await + } +} + // string #[async_trait] impl McBufReadable for String { diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs index 05f613d8..f1362402 100755 --- a/azalea-protocol/src/mc_buf/write.rs +++ b/azalea-protocol/src/mc_buf/write.rs @@ -1,3 +1,5 @@ +use super::MAX_STRING_LENGTH; +use crate::mc_buf::ByteArray; use async_trait::async_trait; use azalea_chat::component::Component; use azalea_core::{ @@ -6,8 +8,6 @@ use azalea_core::{ use byteorder::{BigEndian, WriteBytesExt}; use std::io::Write; -use super::MAX_STRING_LENGTH; - #[async_trait] pub trait Writable { fn write_list(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> @@ -193,6 +193,12 @@ impl McBufWritable for Vec { } } +impl McBufWritable for ByteArray { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_byte_array(&self) + } +} + // string impl McBufWritable for String { fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { diff --git a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs index 9d0cec39..d36f1335 100755 --- a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs @@ -1,14 +1,13 @@ use std::hash::Hash; -use crate::mc_buf::Readable; - use super::LoginPacket; +use crate::mc_buf::{ByteArray, Readable}; #[derive(Hash, Clone, Debug)] pub struct ClientboundHelloPacket { pub server_id: String, - pub public_key: Vec, - pub nonce: Vec, + pub public_key: ByteArray, + pub nonce: ByteArray, } impl ClientboundHelloPacket { diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs index ab68518c..b160a28c 100755 --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -4,6 +4,7 @@ pub mod clientbound_hello_packet; pub mod clientbound_login_compression_packet; pub mod clientbound_login_disconnect_packet; pub mod serverbound_hello_packet; +pub mod serverbound_key_packet; use packet_macros::declare_state_packets; @@ -11,10 +12,13 @@ declare_state_packets!( LoginPacket, Serverbound => { 0x00: serverbound_hello_packet::ServerboundHelloPacket, + 0x01: serverbound_key_packet::ServerboundKeyPacket, }, Clientbound => { - // 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, - 26: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + // sometimes this is used for some reason? + // 0x1a: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 0x01: clientbound_hello_packet::ClientboundHelloPacket, 0x02: clientbound_game_profile_packet::ClientboundGameProfilePacket, 0x03: clientbound_login_compression_packet::ClientboundLoginCompressionPacket, diff --git a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs index a72480f2..eb6a4065 100755 --- a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs @@ -1,27 +1,7 @@ +use packet_macros::LoginPacket; use std::hash::Hash; -use crate::mc_buf::Writable; - -use super::LoginPacket; - -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, LoginPacket)] pub struct ServerboundHelloPacket { pub username: String, } - -impl ServerboundHelloPacket { - pub fn get(self) -> LoginPacket { - LoginPacket::ServerboundHelloPacket(self) - } - - pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { - buf.write_utf(&self.username).unwrap(); - Ok(()) - } - - pub async fn read( - _buf: &mut T, - ) -> Result { - Err("ServerboundHelloPacket::read not implemented".to_string()) - } -} diff --git a/azalea-protocol/src/packets/login/serverbound_key_packet.rs b/azalea-protocol/src/packets/login/serverbound_key_packet.rs new file mode 100644 index 00000000..3750331f --- /dev/null +++ b/azalea-protocol/src/packets/login/serverbound_key_packet.rs @@ -0,0 +1,10 @@ +use super::LoginPacket; +use crate::mc_buf::{ByteArray, Writable}; +use packet_macros::LoginPacket; +use std::hash::Hash; + +#[derive(Hash, Clone, Debug, LoginPacket)] +pub struct ServerboundKeyPacket { + pub shared_secret: ByteArray, + pub nonce: ByteArray, +} diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index ed36d6b6..be20ac23 100755 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -1,13 +1,17 @@ +use std::{cell::Cell, pin::Pin}; + use crate::{connect::PacketFlow, mc_buf::Readable, packets::ProtocolPacket}; use async_compression::tokio::bufread::ZlibDecoder; +use azalea_auth::encryption::Aes128Cfb; use tokio::io::{AsyncRead, AsyncReadExt}; -async fn frame_splitter(stream: &mut R) -> Result, String> +async fn frame_splitter(mut stream: &mut R) -> Result, String> where R: AsyncRead + std::marker::Unpin + std::marker::Send, { // Packet Length let length_result = stream.read_varint().await; + println!("length_result: {:?}", length_result); match length_result { Ok(length) => { let mut buf = vec![0; length as usize]; @@ -17,6 +21,8 @@ where .await .map_err(|e| e.to_string())?; + println!("buf: {:?}", buf); + Ok(buf) } Err(_) => Err("length wider than 21-bit".to_string()), @@ -90,15 +96,61 @@ where Ok(decoded_buf) } -pub async fn read_packet( - flow: &PacketFlow, - stream: &mut R, - compression_threshold: Option, -) -> Result +struct EncryptedStream<'a, R> where R: AsyncRead + std::marker::Unpin + std::marker::Send, { - let mut buf = frame_splitter(stream).await?; + cipher: Cell<&'a mut Option>, + stream: &'a mut Pin<&'a mut R>, +} + +impl AsyncRead for EncryptedStream<'_, R> +where + R: AsyncRead + std::marker::Unpin + std::marker::Send, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + // i hate this + let polled = self.as_mut().stream.as_mut().poll_read(cx, buf); + match polled { + std::task::Poll::Ready(r) => { + println!("encrypted packet {:?}", buf.initialized_mut()); + if let Some(cipher) = self.as_mut().cipher.get_mut() { + azalea_auth::encryption::decrypt_packet(cipher, buf.initialized_mut()); + println!("decrypted packet {:?}", buf.initialized_mut()); + } + match r { + Ok(()) => std::task::Poll::Ready(Ok(())), + Err(e) => panic!("{:?}", e), + } + } + std::task::Poll::Pending => { + return std::task::Poll::Pending; + } + } + } +} + +pub async fn read_packet<'a, P: ProtocolPacket, R>( + flow: &PacketFlow, + stream: &'a mut R, + compression_threshold: Option, + cipher: &mut Option, +) -> Result +where + R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync, +{ + // if we were given a cipher, decrypt the packet + let mut encrypted_stream = EncryptedStream { + cipher: Cell::new(cipher), + stream: &mut Pin::new(stream), + }; + + let mut buf = frame_splitter(&mut encrypted_stream).await?; + if let Some(compression_threshold) = compression_threshold { buf = compression_decoder(&mut buf.as_slice(), compression_threshold).await?; } diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs index 821ed6e8..e72a74b1 100755 --- a/azalea-protocol/src/write.rs +++ b/azalea-protocol/src/write.rs @@ -1,5 +1,6 @@ use crate::{mc_buf::Writable, packets::ProtocolPacket, read::MAXIMUM_UNCOMPRESSED_LENGTH}; use async_compression::tokio::bufread::ZlibEncoder; +use azalea_auth::encryption::Aes128Cfb; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream, @@ -50,14 +51,22 @@ async fn compression_encoder(data: &[u8], compression_threshold: u32) -> Result< } } -pub async fn write_packet

(packet: P, stream: &mut TcpStream, compression_threshold: Option) -where +pub async fn write_packet

( + packet: P, + stream: &mut TcpStream, + compression_threshold: Option, + cipher: &mut Option, +) where P: ProtocolPacket + std::fmt::Debug, { let mut buf = packet_encoder(&packet).unwrap(); if let Some(threshold) = compression_threshold { buf = compression_encoder(&buf, threshold).await.unwrap(); } + // if we were given a cipher, encrypt the packet + if let Some(cipher) = cipher { + azalea_auth::encryption::encrypt_packet(cipher, &mut buf); + } buf = frame_prepender(&mut buf).unwrap(); stream.write_all(&buf).await.unwrap(); } diff --git a/bot/src/main.rs b/bot/src/main.rs old mode 100755 new mode 100644