From 80d49a76073d417e118b85636df2a923043b0250 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 30 Apr 2022 21:30:45 -0500 Subject: azalea_auth::encryption -> azalea_crypto --- Cargo.lock | 36 ++++++++------ Cargo.toml | 1 + azalea-auth/Cargo.toml | 5 -- azalea-auth/src/encryption.rs | 106 ----------------------------------------- azalea-auth/src/lib.rs | 1 - azalea-client/Cargo.toml | 1 + azalea-client/src/connect.rs | 2 +- azalea-crypto/Cargo.toml | 14 ++++++ azalea-crypto/README.md | 3 ++ azalea-crypto/src/lib.rs | 106 +++++++++++++++++++++++++++++++++++++++++ azalea-protocol/Cargo.toml | 1 + azalea-protocol/src/connect.rs | 4 +- azalea-protocol/src/read.rs | 4 +- azalea-protocol/src/write.rs | 4 +- 14 files changed, 155 insertions(+), 133 deletions(-) delete mode 100644 azalea-auth/src/encryption.rs create mode 100644 azalea-crypto/Cargo.toml create mode 100644 azalea-crypto/README.md create mode 100644 azalea-crypto/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 18072c06..b12a09d0 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,12 +75,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "azalea-auth" version = "0.1.0" dependencies = [ - "aes", - "cfb8", "num-bigint", - "rand", - "rsa_public_encrypt_pkcs1", - "sha-1", "uuid", ] @@ -102,6 +97,7 @@ name = "azalea-client" version = "0.1.0" dependencies = [ "azalea-auth", + "azalea-crypto", "azalea-protocol", "tokio", ] @@ -115,6 +111,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "azalea-crypto" +version = "0.1.0" +dependencies = [ + "aes", + "cfb8", + "num-bigint", + "rand", + "rsa_public_encrypt_pkcs1", + "sha-1", +] + [[package]] name = "azalea-nbt" version = "0.1.0" @@ -140,6 +148,7 @@ dependencies = [ "azalea-brigadier", "azalea-chat", "azalea-core", + "azalea-crypto", "azalea-nbt", "byteorder", "bytes", @@ -163,9 +172,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -404,13 +413,12 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer", "crypto-common", - "generic-array", ] [[package]] @@ -784,9 +792,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" dependencies = [ "num-traits", ] @@ -814,9 +822,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", diff --git a/Cargo.toml b/Cargo.toml index ae27892e..f7272ff7 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "azalea-auth", "azalea-nbt", "azalea-brigadier", + "azalea-crypto", ] [profile.release] diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 5f62991b..1015547b 100755 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -6,10 +6,5 @@ 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 deleted file mode 100644 index dc2620cc..00000000 --- a/azalea-auth/src/encryption.rs +++ /dev/null @@ -1,106 +0,0 @@ -use aes::cipher::inout::InOutBuf; -use aes::cipher::BlockEncrypt; -use aes::{ - cipher::{ - generic_array::GenericArray, AsyncStreamCipher, BlockDecryptMut, BlockEncryptMut, KeyIvInit, - }, - Aes128, -}; -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 Aes128CfbEnc = cfb8::Encryptor; -pub type Aes128CfbDec = cfb8::Decryptor; - -pub fn create_cipher(key: &[u8]) -> (Aes128CfbEnc, Aes128CfbDec) { - ( - Aes128CfbEnc::new_from_slices(key, key).unwrap(), - Aes128CfbDec::new_from_slices(key, key).unwrap(), - ) -} - -// wow this is terrible -pub fn encrypt_packet(cipher: &mut Aes128CfbEnc, packet: &mut [u8]) { - let (chunks, rest) = InOutBuf::from(packet).into_chunks(); - assert!(rest.is_empty()); - cipher.encrypt_blocks_inout_mut(chunks); -} -pub fn decrypt_packet(cipher: &mut Aes128CfbDec, packet: &mut [u8]) { - let (chunks, rest) = InOutBuf::from(packet).into_chunks(); - assert!(rest.is_empty()); - cipher.decrypt_blocks_inout_mut(chunks); -} - -#[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 ba35055f..773ea1d9 100755 --- a/azalea-auth/src/lib.rs +++ b/azalea-auth/src/lib.rs @@ -1,4 +1,3 @@ //! Handle Minecraft authentication. -pub mod encryption; pub mod game_profile; diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index d8477ea1..ab3f6eda 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -7,5 +7,6 @@ version = "0.1.0" [dependencies] azalea-auth = {path = "../azalea-auth"} +azalea-crypto = {path = "../azalea-crypto"} azalea-protocol = {path = "../azalea-protocol"} tokio = {version = "1.18.0", features = ["sync"]} diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index 7c1880fa..5c6c8841 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -74,7 +74,7 @@ impl Client { Ok(packet) => match packet { LoginPacket::ClientboundHelloPacket(p) => { println!("Got encryption request"); - let e = azalea_auth::encryption::encrypt(&p.public_key, &p.nonce).unwrap(); + let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap(); // TODO: authenticate with the server here (authenticateServer) diff --git a/azalea-crypto/Cargo.toml b/azalea-crypto/Cargo.toml new file mode 100644 index 00000000..2532bff9 --- /dev/null +++ b/azalea-crypto/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2021" +name = "azalea-crypto" +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" diff --git a/azalea-crypto/README.md b/azalea-crypto/README.md new file mode 100644 index 00000000..b8531f78 --- /dev/null +++ b/azalea-crypto/README.md @@ -0,0 +1,3 @@ +# Azalea Crypto + +Cryprography features used in Minecraft. diff --git a/azalea-crypto/src/lib.rs b/azalea-crypto/src/lib.rs new file mode 100644 index 00000000..dc2620cc --- /dev/null +++ b/azalea-crypto/src/lib.rs @@ -0,0 +1,106 @@ +use aes::cipher::inout::InOutBuf; +use aes::cipher::BlockEncrypt; +use aes::{ + cipher::{ + generic_array::GenericArray, AsyncStreamCipher, BlockDecryptMut, BlockEncryptMut, KeyIvInit, + }, + Aes128, +}; +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 Aes128CfbEnc = cfb8::Encryptor; +pub type Aes128CfbDec = cfb8::Decryptor; + +pub fn create_cipher(key: &[u8]) -> (Aes128CfbEnc, Aes128CfbDec) { + ( + Aes128CfbEnc::new_from_slices(key, key).unwrap(), + Aes128CfbDec::new_from_slices(key, key).unwrap(), + ) +} + +// wow this is terrible +pub fn encrypt_packet(cipher: &mut Aes128CfbEnc, packet: &mut [u8]) { + let (chunks, rest) = InOutBuf::from(packet).into_chunks(); + assert!(rest.is_empty()); + cipher.encrypt_blocks_inout_mut(chunks); +} +pub fn decrypt_packet(cipher: &mut Aes128CfbDec, packet: &mut [u8]) { + let (chunks, rest) = InOutBuf::from(packet).into_chunks(); + assert!(rest.is_empty()); + cipher.decrypt_blocks_inout_mut(chunks); +} + +#[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-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 37df8697..9ebcd9d7 100755 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -13,6 +13,7 @@ azalea-auth = {path = "../azalea-auth"} azalea-brigadier = {path = "../azalea-brigadier"} azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core"} +azalea-crypto = {path = "../azalea-crypto"} azalea-nbt = {path = "../azalea-nbt"} byteorder = "^1.4.3" bytes = "^1.1.0" diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index c55f2e90..e9d898d6 100755 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs @@ -7,7 +7,7 @@ use crate::packets::status::StatusPacket; use crate::read::read_packet; use crate::write::write_packet; use crate::ServerIpAddress; -use azalea_auth::encryption::{Aes128CfbDec, Aes128CfbEnc}; +use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc}; use tokio::net::TcpStream; pub enum PacketFlow { @@ -159,7 +159,7 @@ 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 (enc_cipher, dec_cipher) = azalea_auth::encryption::create_cipher(&key); + let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key); self.enc_cipher = Some(enc_cipher); self.dec_cipher = Some(dec_cipher); } diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index 2a74d8de..9afdb0e9 100755 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -6,7 +6,7 @@ use std::{ use crate::{connect::PacketFlow, mc_buf::Readable, packets::ProtocolPacket}; use async_compression::tokio::bufread::ZlibDecoder; -use azalea_auth::encryption::Aes128CfbDec; +use azalea_crypto::Aes128CfbDec; use tokio::io::{AsyncRead, AsyncReadExt}; async fn frame_splitter(mut stream: &mut R) -> Result, String> @@ -122,7 +122,7 @@ where // (but only on linux and release mode for some reason LMAO) if buf.remaining() == 0 { if let Some(cipher) = self.as_mut().cipher.get_mut() { - azalea_auth::encryption::decrypt_packet(cipher, buf.filled_mut()); + azalea_crypto::decrypt_packet(cipher, buf.filled_mut()); } } match r { diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs index e39ce18e..38ef174c 100755 --- a/azalea-protocol/src/write.rs +++ b/azalea-protocol/src/write.rs @@ -1,6 +1,6 @@ use crate::{mc_buf::Writable, packets::ProtocolPacket, read::MAXIMUM_UNCOMPRESSED_LENGTH}; use async_compression::tokio::bufread::ZlibEncoder; -use azalea_auth::encryption::Aes128CfbEnc; +use azalea_crypto::Aes128CfbEnc; use std::fmt::Debug; use tokio::{ io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}, @@ -67,7 +67,7 @@ pub async fn write_packet( } // if we were given a cipher, encrypt the packet if let Some(cipher) = cipher { - azalea_auth::encryption::encrypt_packet(cipher, &mut buf); + azalea_crypto::encrypt_packet(cipher, &mut buf); } buf = frame_prepender(&mut buf).unwrap(); stream.write_all(&buf).await.unwrap(); -- cgit v1.2.3