Make into optional features: authentication, compression, encryption.
parent
60c9a48293
commit
5302c04fa4
40
Cargo.toml
40
Cargo.toml
|
@ -7,6 +7,26 @@ repository = "https://github.com/jamestmartin/tmd"
|
|||
license = "GPL-3.0+"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["authentication", "compression", "encryption"]
|
||||
|
||||
# Enables authentication via Mojang's servers, i.e. online mode.
|
||||
authentication = ["encryption", "num-bigint", "sha-1", "reqwest"]
|
||||
|
||||
# Enables protocol compression.
|
||||
compression = ["flate2"]
|
||||
|
||||
# Enables protocol encryption *without enabling authentication*.
|
||||
#
|
||||
# The protocol itself doesn't depend on authentication to use encryption.
|
||||
# If the client and server both simply *don't* check in with Mojang,
|
||||
# then the protocol will proceed as normal, but encrypted.
|
||||
# However, be warned that there is no way to disable authentication
|
||||
# for Notchian clients (they will disconnect you from the server),
|
||||
# so a modified client would be necessary
|
||||
# to use offline encryption, and none exist that I know of.
|
||||
encryption = ["aes", "cfb8", "rand", "rsa"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.36"
|
||||
clap = { version = "2.33.1", features = ["yaml"] }
|
||||
|
@ -15,14 +35,18 @@ serde_json = "1.0.56"
|
|||
tokio = { version = "0.2.22", features = ["io-util", "macros", "net", "tcp", "rt-threaded"] }
|
||||
uuid = { version = "0.8", features = ["serde"] }
|
||||
|
||||
# Dependencies required for authentication
|
||||
# Used to format Minecraft's "server id" hash.
|
||||
num-bigint = { version = "0.3.0", optional = true }
|
||||
sha-1 = { version = "0.9.1", optional = true }
|
||||
# Used to make the request to the Mojang servers.
|
||||
reqwest = { version = "0.10.7", optional = true }
|
||||
|
||||
# Dependencies required for compression
|
||||
flate2 = "1.0.16"
|
||||
flate2 = { version = "1.0.16", optional = true }
|
||||
|
||||
# Dependencies required for encryption
|
||||
aes = "0.4.0"
|
||||
cfb8 = "0.4.0"
|
||||
num-bigint = "0.3.0"
|
||||
rand = "0.7.3"
|
||||
reqwest = "0.10.7"
|
||||
rsa = "0.3.0"
|
||||
sha-1 = "0.9.1"
|
||||
aes = { version = "0.4.0", optional = true }
|
||||
cfb8 = { version = "0.4.0", optional = true }
|
||||
rand = { version = "0.7.3", optional = true }
|
||||
rsa = { version = "0.3.0", optional = true }
|
||||
|
|
114
src/main.rs
114
src/main.rs
|
@ -124,72 +124,78 @@ async fn interact_login(mut con: Connection<Login>) -> io::Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
use rand::rngs::OsRng;
|
||||
use rsa::{RSAPrivateKey, PaddingScheme};
|
||||
#[cfg(feature = "encryption")]
|
||||
{
|
||||
use rand::Rng;
|
||||
use rand::rngs::OsRng;
|
||||
use rsa::{RSAPrivateKey, PaddingScheme};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
let mut public_key = Vec::new();
|
||||
File::open("private/pub.der").expect("missing public key").read_to_end(&mut public_key)?;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
let mut public_key = Vec::new();
|
||||
File::open("private/pub.der").expect("missing public key").read_to_end(&mut public_key)?;
|
||||
|
||||
let mut private_key = Vec::new();
|
||||
File::open("private/priv.der").expect("missing private key").read_to_end(&mut private_key)?;
|
||||
let mut private_key = Vec::new();
|
||||
File::open("private/priv.der").expect("missing private key").read_to_end(&mut private_key)?;
|
||||
|
||||
let key = RSAPrivateKey::from_pkcs1(&private_key).expect("Invalid private key.");
|
||||
let key = RSAPrivateKey::from_pkcs1(&private_key).expect("Invalid private key.");
|
||||
|
||||
let mut verify_token = Vec::new();
|
||||
verify_token.resize(4, 0u8);
|
||||
OsRng.fill(verify_token.as_mut_slice());
|
||||
let mut verify_token = Vec::new();
|
||||
verify_token.resize(4, 0u8);
|
||||
OsRng.fill(verify_token.as_mut_slice());
|
||||
|
||||
let server_id = "";
|
||||
let server_id = "";
|
||||
|
||||
con.write(&Clientbound::EncryptionRequest(EncryptionRequest {
|
||||
server_id: server_id.to_string().into_boxed_str(),
|
||||
public_key: public_key.clone().into_boxed_slice(),
|
||||
verify_token: verify_token.clone().into_boxed_slice(),
|
||||
})).await?;
|
||||
con.write(&Clientbound::EncryptionRequest(EncryptionRequest {
|
||||
server_id: server_id.to_string().into_boxed_str(),
|
||||
public_key: public_key.clone().into_boxed_slice(),
|
||||
verify_token: verify_token.clone().into_boxed_slice(),
|
||||
})).await?;
|
||||
|
||||
let secret = match con.read().await? {
|
||||
Serverbound::EncryptionResponse(encryption_response) => {
|
||||
let token = key.decrypt(PaddingScheme::PKCS1v15Encrypt,
|
||||
&encryption_response.verify_token)
|
||||
.expect("Failed to decrypt verify token.");
|
||||
if token.as_slice() != verify_token.as_slice() {
|
||||
return mk_err("Incorrect verify token.");
|
||||
let secret = match con.read().await? {
|
||||
Serverbound::EncryptionResponse(encryption_response) => {
|
||||
let token = key.decrypt(PaddingScheme::PKCS1v15Encrypt,
|
||||
&encryption_response.verify_token)
|
||||
.expect("Failed to decrypt verify token.");
|
||||
if token.as_slice() != verify_token.as_slice() {
|
||||
return mk_err("Incorrect verify token.");
|
||||
}
|
||||
|
||||
key.decrypt(PaddingScheme::PKCS1v15Encrypt, &encryption_response.shared_secret)
|
||||
.expect("Failed to decrypt shared secret.")
|
||||
},
|
||||
_ => {
|
||||
return mk_err("Unexpected packet (expected Encryption Response).");
|
||||
}
|
||||
|
||||
key.decrypt(PaddingScheme::PKCS1v15Encrypt, &encryption_response.shared_secret)
|
||||
.expect("Failed to decrypt shared secret.")
|
||||
},
|
||||
_ => {
|
||||
return mk_err("Unexpected packet (expected Encryption Response).");
|
||||
}
|
||||
};
|
||||
|
||||
con = con.set_encryption(&secret).expect("Failed to set encryption.");
|
||||
|
||||
use reqwest::Client;
|
||||
|
||||
let server_hash = {
|
||||
let server_hash_bytes = {
|
||||
use sha1::{Sha1, Digest};
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(server_id.as_bytes());
|
||||
hasher.update(&secret);
|
||||
hasher.update(&public_key);
|
||||
hasher.finalize()
|
||||
};
|
||||
|
||||
format!("{:x}", num_bigint::BigInt::from_signed_bytes_be(&server_hash_bytes))
|
||||
};
|
||||
con = con.set_encryption(&secret).expect("Failed to set encryption.");
|
||||
|
||||
// TODO: Authentication, not just encryption.
|
||||
println!("{:?}", Client::new().get("https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||
.header("Content-Type", "application/json")
|
||||
.query(&[("username", name.clone()), ("serverId", server_hash.into_boxed_str())])
|
||||
.send().await.expect("Request failed.").text().await.unwrap());
|
||||
#[cfg(feature = "authentication")]
|
||||
{
|
||||
let server_hash = {
|
||||
let server_hash_bytes = {
|
||||
use sha1::{Sha1, Digest};
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(server_id.as_bytes());
|
||||
hasher.update(&secret);
|
||||
hasher.update(&public_key);
|
||||
hasher.finalize()
|
||||
};
|
||||
|
||||
format!("{:x}", num_bigint::BigInt::from_signed_bytes_be(&server_hash_bytes))
|
||||
};
|
||||
|
||||
use reqwest::Client;
|
||||
|
||||
println!("{:?}", Client::new().get("https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||
.header("Content-Type", "application/json")
|
||||
.query(&[("username", name.clone()), ("serverId", server_hash.into_boxed_str())])
|
||||
.send().await.expect("Request failed.").text().await.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
con.set_compression(Some(64)).await?;
|
||||
|
||||
con.write(&Clientbound::LoginSuccess(LoginSuccess {
|
||||
|
|
|
@ -72,6 +72,7 @@ impl Connection<Handshake> {
|
|||
}
|
||||
|
||||
impl Connection<Login> {
|
||||
#[cfg(feature = "compression")]
|
||||
pub async fn set_compression(&mut self, threshold: Option<u32>) -> io::Result<()> {
|
||||
use crate::net::connection::packet_format::compressed::CompressedPacketFormat;
|
||||
use crate::net::serialize::VarInt;
|
||||
|
@ -99,6 +100,7 @@ impl Connection<Login> {
|
|||
|
||||
/// WARNING: This function is not idempontent.
|
||||
/// Calling it twice will result in the underlying stream getting encrypted twice.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn set_encryption(self, secret: &[u8]) -> Result<Self, String> {
|
||||
use cfb8::Cfb8;
|
||||
use cfb8::stream_cipher::NewStreamCipher;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "compression")]
|
||||
pub mod compressed;
|
||||
pub mod default;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "encryption")]
|
||||
pub mod encrypted;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
|
Loading…
Reference in New Issue