From 9f576c5600ba9a244bc0d433bb7de174284066a2 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 1 Jan 2022 18:59:38 -0600 Subject: [PATCH 1/3] start work on encryption --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++ azalea-auth/Cargo.toml | 3 ++ azalea-auth/src/encryption.rs | 53 +++++++++++++++++++ azalea-auth/src/lib.rs | 3 ++ azalea-client/src/connect.rs | 3 +- bot/src/main.rs | 4 +- 6 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 azalea-auth/src/encryption.rs diff --git a/Cargo.lock b/Cargo.lock index c037630d..c460835d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" name = "azalea-auth" version = "0.1.0" dependencies = [ + "num-bigint", + "rand", + "sha-1", "uuid", ] @@ -133,6 +136,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" @@ -198,6 +210,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" @@ -289,6 +310,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-common" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +dependencies = [ + "generic-array", +] + [[package]] name = "csv" version = "1.1.6" @@ -317,6 +347,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" @@ -419,6 +460,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" @@ -640,6 +691,17 @@ dependencies = [ "winapi", ] +[[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-derive" version = "0.3.3" @@ -651,6 +713,16 @@ 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-traits" version = "0.2.14" @@ -959,6 +1031,17 @@ 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 = "slab" version = "0.4.5" @@ -1133,6 +1216,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" @@ -1184,6 +1273,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..cb807776 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,4 +6,7 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +num-bigint = "^0.4.3" +rand = {version = "^0.8.4", features = ["getrandom"]} +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..334d8cb3 --- /dev/null +++ b/azalea-auth/src/encryption.rs @@ -0,0 +1,53 @@ +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) +} + +fn encrypt(public_key: &[u8], server_id: String, nonce: &[u8]) { + let secret_key = generate_secret_key(); + let hash = hex_digest(&digest_data(server_id.as_bytes(), public_key, &secret_key)); +} + +#[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 100644 --- 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/src/connect.rs b/azalea-client/src/connect.rs index 90cb3ad7..7d987eab 100644 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -39,6 +39,7 @@ 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); + panic!(""); } LoginPacket::ClientboundLoginCompressionPacket(p) => { println!("Got compression request {:?}", p.compression_threshold); @@ -73,7 +74,7 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { _ => panic!("unhandled packet"), }, Err(e) => { - println!("Error: {:?}", e); + panic!("Error: {:?}", e); } } } diff --git a/bot/src/main.rs b/bot/src/main.rs index 1041e765..435803e2 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -4,8 +4,8 @@ use azalea_client::connect::join_server; async fn main() { println!("Hello, world!"); - let address = "95.111.249.143:10000"; - // let address = "localhost:63482"; + // let address = "95.111.249.143:10000"; + let address = "localhost:58566"; let _response = join_server(&address.try_into().unwrap()).await.unwrap(); // println!("{}", response.description.to_ansi(None)); println!("connected"); From 4c00bd886578c70f6aeb35400d9d03b355df3155 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 24 Apr 2022 19:28:29 -0500 Subject: [PATCH 2/3] Add ServerboundKeyPacket --- Cargo.lock | 127 +++++++++++++++++- azalea-auth/Cargo.toml | 3 + azalea-auth/src/encryption.rs | 23 +++- azalea-client/Cargo.toml | 1 + azalea-client/src/connect.rs | 16 ++- azalea-protocol/src/packets/login/mod.rs | 6 +- .../packets/login/serverbound_hello_packet.rs | 24 +--- .../packets/login/serverbound_key_packet.rs | 10 ++ 8 files changed, 181 insertions(+), 29 deletions(-) create mode 100644 azalea-protocol/src/packets/login/serverbound_key_packet.rs diff --git a/Cargo.lock b/Cargo.lock index 430c72a6..6c5ebad7 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "async-compression" version = "0.3.8" @@ -64,8 +75,11 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" name = "azalea-auth" version = "0.1.0" dependencies = [ + "aes", + "cfb8", "num-bigint", "rand", + "rsa_public_encrypt_pkcs1", "sha-1", "uuid", ] @@ -87,6 +101,7 @@ dependencies = [ name = "azalea-client" version = "0.1.0" dependencies = [ + "azalea-auth", "azalea-protocol", ] @@ -201,12 +216,41 @@ dependencies = [ "rustc_version", ] +[[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 = "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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "2.34.0" @@ -320,11 +364,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", + "typenum", ] [[package]] @@ -535,6 +580,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -699,6 +753,20 @@ 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" @@ -710,6 +778,15 @@ dependencies = [ "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" @@ -731,6 +808,29 @@ dependencies = [ "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" @@ -971,6 +1071,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" @@ -1059,6 +1170,18 @@ dependencies = [ "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" diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index cb807776..5f62991b 100755 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,7 +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.8.1" +cfb8 = "0.8.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 index 334d8cb3..72739620 100644 --- a/azalea-auth/src/encryption.rs +++ b/azalea-auth/src/encryption.rs @@ -24,9 +24,28 @@ fn hex_digest(digest: &[u8]) -> String { num_bigint::BigInt::from_signed_bytes_be(digest).to_str_radix(16) } -fn encrypt(public_key: &[u8], server_id: String, nonce: &[u8]) { +pub struct EncryptResult { + 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)); + // 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 { + encrypted_public_key, + encrypted_nonce, + }) } #[cfg(test)] 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 884648b4..b402200a 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,7 +42,15 @@ 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); - panic!(""); + let e = azalea_auth::encryption::encrypt(&p.public_key, &p.nonce).unwrap(); + conn.write( + ServerboundKeyPacket { + nonce: e.encrypted_nonce, + shared_secret: e.encrypted_public_key, + } + .get(), + ) + .await; } LoginPacket::ClientboundLoginCompressionPacket(p) => { println!("Got compression request {:?}", p.compression_threshold); @@ -58,6 +69,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/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs index ab68518c..93c1200f 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, + // for some reason this is used instead of 0x00?? + 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..f402d357 --- /dev/null +++ b/azalea-protocol/src/packets/login/serverbound_key_packet.rs @@ -0,0 +1,10 @@ +use super::LoginPacket; +use crate::mc_buf::Writable; +use packet_macros::LoginPacket; +use std::hash::Hash; + +#[derive(Hash, Clone, Debug, LoginPacket)] +pub struct ServerboundKeyPacket { + pub shared_secret: Vec, + pub nonce: Vec, +} From f4dd3a9293367fa8f3d839a184e8055b22595204 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 24 Apr 2022 22:46:41 -0500 Subject: [PATCH 3/3] ENCRYPTION WORKS!!!!!!!!!!! --- Cargo.lock | 31 ++++----- azalea-auth/Cargo.toml | 4 +- azalea-auth/src/encryption.rs | 22 +++++++ azalea-client/src/connect.rs | 9 ++- azalea-protocol/src/connect.rs | 52 ++++++++++++--- azalea-protocol/src/mc_buf/mod.rs | 20 ++++++ azalea-protocol/src/mc_buf/read.rs | 18 ++++- azalea-protocol/src/mc_buf/write.rs | 10 ++- .../packets/login/clientbound_hello_packet.rs | 7 +- azalea-protocol/src/packets/login/mod.rs | 6 +- .../packets/login/serverbound_key_packet.rs | 6 +- azalea-protocol/src/read.rs | 66 +++++++++++++++++-- azalea-protocol/src/write.rs | 13 +++- 13 files changed, 210 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c5ebad7..8be7aa31 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,13 +10,14 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", "cipher", "cpufeatures", + "opaque-debug", ] [[package]] @@ -218,9 +219,9 @@ dependencies = [ [[package]] name = "cfb8" -version = "0.8.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014c0a0e1ad0dae6a86c082db2f9bd7fe8c2c734227047d0d8b4d4a3a094a1e1" +checksum = "c3a4b6c43bf284e617a659ce5dc149676680530a3a4a9bb6b278d1a9ed5b229d" dependencies = [ "cipher", ] @@ -243,12 +244,11 @@ dependencies = [ [[package]] name = "cipher" -version = "0.4.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "crypto-common", - "inout", + "generic-array", ] [[package]] @@ -580,15 +580,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "instant" version = "0.1.12" @@ -856,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" diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 5f62991b..6279d903 100755 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,8 +6,8 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aes = "0.8.1" -cfb8 = "0.8.1" +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" diff --git a/azalea-auth/src/encryption.rs b/azalea-auth/src/encryption.rs index 72739620..6f2e43a8 100644 --- a/azalea-auth/src/encryption.rs +++ b/azalea-auth/src/encryption.rs @@ -1,3 +1,8 @@ +use aes::{ + cipher::{AsyncStreamCipher, NewCipher}, + Aes128, +}; +use cfb8::Cfb8; use rand::{rngs::OsRng, RngCore}; use sha1::{Digest, Sha1}; @@ -24,7 +29,9 @@ fn hex_digest(digest: &[u8]) -> String { 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, } @@ -43,11 +50,26 @@ pub fn encrypt(public_key: &[u8], nonce: &[u8]) -> Result 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::*; diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index b402200a..44eff33f 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -43,14 +43,19 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { 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, - shared_secret: e.encrypted_public_key, + 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); 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 93c1200f..b160a28c 100755 --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -15,9 +15,9 @@ declare_state_packets!( 0x01: serverbound_key_packet::ServerboundKeyPacket, }, Clientbound => { - // 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, - // for some reason this is used instead of 0x00?? - 0x1a: 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, diff --git a/azalea-protocol/src/packets/login/serverbound_key_packet.rs b/azalea-protocol/src/packets/login/serverbound_key_packet.rs index f402d357..3750331f 100644 --- a/azalea-protocol/src/packets/login/serverbound_key_packet.rs +++ b/azalea-protocol/src/packets/login/serverbound_key_packet.rs @@ -1,10 +1,10 @@ use super::LoginPacket; -use crate::mc_buf::Writable; +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: Vec, - pub nonce: Vec, + 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(); }