Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transport layer encryption and login #117

Draft
wants to merge 13 commits into
base: dev
Choose a base branch
from
7 changes: 5 additions & 2 deletions crates/proto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ base64 = "0.22"
uuid = { version = "1.11", features = ["v4"] }
serde_json = "1.0"
tokio = { version = "1.40", features = ["full"] }
p384 = "0.13.0"
sha2 = "0.10.8"
aes = "0.8.4"
ctr = "0.9.2"

flate2 = "1.0"
snap = "1.1"

x509-cert = "0.2"

bitflags = "2.6.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1.19"

rak-rs = { version = "0.3", default-features = false, features = ["async_tokio", "mcpe"] }
4 changes: 2 additions & 2 deletions crates/proto/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub fn encrypt_gamepackets<T: ProtoHelper>(
encryption: Option<&mut Encryption>,
) -> Result<Vec<u8>, ProtoCodecError> {
if let Some(encryption) = encryption {
gamepacket_stream = encryption.encrypt(gamepacket_stream)?;
encryption.encrypt(&mut gamepacket_stream)?;
}

Ok(gamepacket_stream)
Expand All @@ -112,7 +112,7 @@ pub fn decrypt_gamepackets<T: ProtoHelper>(
encryption: Option<&mut Encryption>,
) -> Result<Vec<u8>, ProtoCodecError> {
if let Some(encryption) = encryption {
gamepacket_stream = encryption.decrypt(gamepacket_stream)?;
encryption.decrypt(&mut gamepacket_stream)?;
}

Ok(gamepacket_stream)
Expand Down
159 changes: 147 additions & 12 deletions crates/proto/src/encryption.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,161 @@
use bedrockrs_proto_core::error::EncryptionError;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use bedrockrs_proto_core::error::{EncryptionError, ProtoCodecError};
use ctr::cipher::{KeyIvInit, StreamCipher};
use jsonwebtoken::Algorithm;
use p384::ecdh::diffie_hellman;
use p384::ecdsa::SigningKey;
use p384::pkcs8::{DecodePublicKey, EncodePrivateKey, EncodePublicKey};
use p384::PublicKey;
use rand::distributions::Alphanumeric;
use rand::rngs::OsRng;
use rand::Rng;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::fmt::{self, Debug};
use std::io::Write;

#[derive(Debug, Clone)]
type Aes256CtrBE = ctr::Ctr64BE<aes::Aes256>;

#[derive(Serialize)]
struct EncryptionClaims {
salt: String,
}

#[derive(Clone)]
pub struct Encryption {
recv_counter: u64,
send_counter: u64,
buf: [u8; 8],
key: Vec<u8>,
jwt: String,

decrypt: Aes256CtrBE,
encrypt: Aes256CtrBE,
secret: [u8; 32],
}

impl Debug for Encryption {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Encryption")
.field("jwt", &self.jwt)
.finish_non_exhaustive()
}
}

impl Encryption {
pub fn new() -> Self {
unimplemented!()
pub fn new(client_public_key_der: &str) -> Result<Self, ProtoCodecError> {
let salt = (0..16)
.map(|_| OsRng.sample(Alphanumeric) as char)
.collect::<String>();
let private_key: SigningKey = SigningKey::random(&mut OsRng);

let Ok(private_key_der) = private_key.to_pkcs8_der() else {
todo!()
};

let public_key = private_key.verifying_key();
let public_key_der = if let Ok(key) = public_key.to_public_key_der() {
BASE64_STANDARD.encode(key)
} else {
todo!()
};

let mut header = jsonwebtoken::Header::new(Algorithm::ES384);
header.typ = None;
header.x5u = Some(public_key_der);

let signing_key = jsonwebtoken::EncodingKey::from_ec_der(&private_key_der.to_bytes());
let claims = EncryptionClaims {
salt: BASE64_STANDARD.encode(&salt),
};

let jwt = jsonwebtoken::encode(&header, &claims, &signing_key)?;

let bytes = BASE64_STANDARD.decode(client_public_key_der)?;
let Ok(client_public_key) = PublicKey::from_public_key_der(&bytes) else {
todo!()
};

let shared_secret = diffie_hellman(
private_key.as_nonzero_scalar(),
client_public_key.as_affine(),
);

let mut hasher = Sha256::new();
hasher.update(salt);
hasher.update(shared_secret.raw_secret_bytes().as_slice());

let secret = hasher.finalize();

let mut iv = [0; 16];
iv[..12].copy_from_slice(&secret[..12]);
iv[12..].copy_from_slice(&[0x00, 0x00, 0x00, 0x02]);

let cipher = Aes256CtrBE::new((&secret).into(), (&iv).into());

Ok(Self {
send_counter: 0,
recv_counter: 0,

decrypt: cipher.clone(),
encrypt: cipher,
secret: secret.into(),

jwt,
})
}

pub fn decrypt(&mut self, _src: Vec<u8>) -> Result<Vec<u8>, EncryptionError> {
unimplemented!()
#[inline]
pub fn salt_jwt(&self) -> &str {
&self.jwt
}

pub fn encrypt(&mut self, _src: Vec<u8>) -> Result<Vec<u8>, EncryptionError> {
unimplemented!()
pub fn decrypt(&mut self, data: &mut Vec<u8>) -> Result<(), ProtoCodecError> {
dbg!("Decrypting");
println!("data: {:?}", &data[..10]);

if data.len() < 9 {
// This data cannot possibly be valid. Checksum is already 8 bytes.
todo!()
}

self.decrypt.apply_keystream(data);
println!("decrypt: {data:?}");

let counter = self.recv_counter;
self.recv_counter += 1;

let checksum = &data[data.len() - 8..];
let computed = self.checksum(&data[..data.len() - 8], counter);
if !checksum.eq(&computed) {
panic!("Checksum does not match")
}

data.truncate(data.len() - 8);

Ok(())
}

pub fn encrypt(&mut self, data: &mut Vec<u8>) -> Result<(), ProtoCodecError> {
let counter = self.send_counter;
self.send_counter += 1;

let checksum = self.checksum(data, counter);
data.write_all(&checksum)?;

self.encrypt.apply_keystream(data);

Ok(())
}

pub fn verify(&mut self, _src: &[u8]) -> Result<(), EncryptionError> {
unimplemented!()
pub fn checksum(&self, data: &[u8], counter: u64) -> [u8; 8] {
let mut hasher = Sha256::new();
hasher.update(counter.to_le_bytes());
hasher.update(data);
hasher.update(&self.secret);

let mut checksum = [0; 8];
checksum.copy_from_slice(&hasher.finalize()[..8]);

checksum
}
}
4 changes: 2 additions & 2 deletions crates/proto/src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ pub const MAGIC: [u8; 16] = [
0x00, 0xff, 0xff, 0x0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78,
];

/// Mojang's public JWT Key encoded as a base64 str
pub const MOAJNG_PUBLIC_KEY: &'static str = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
/// Mojang's public JWT Key encoded in DER format.
pub const MOJANG_PUBLIC_KEY: &str = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
3 changes: 2 additions & 1 deletion crates/proto/src/version/v662/enums/build_platform.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use serde_repr::Deserialize_repr;
use bedrockrs_macros::ProtoCodec;

#[derive(ProtoCodec, Clone, Debug)]
#[derive(ProtoCodec, Deserialize_repr, Clone, Debug)]
#[enum_repr(i32)]
#[enum_endianness(le)]
#[repr(i32)]
Expand Down
3 changes: 2 additions & 1 deletion crates/proto/src/version/v662/enums/input_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bedrockrs_macros::ProtoCodec;
use serde_repr::Deserialize_repr;

#[derive(ProtoCodec, Clone, Debug)]
#[derive(ProtoCodec, Deserialize_repr, Clone, Debug)]
#[enum_repr(u32)]
#[enum_endianness(var)]
#[repr(u32)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bedrockrs_macros::ProtoCodec;
#[enum_endianness(le)]
#[repr(u16)]
pub enum PacketCompressionAlgorithm {
ZLib = 0,
Zlib = 0,
Snappy = 1,
None = 0xffff,
}
5 changes: 4 additions & 1 deletion crates/proto/src/version/v662/enums/ui_profile.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/// UNUSED
use serde_repr::Deserialize_repr;

#[derive(Deserialize_repr, Debug, Copy, Clone, PartialEq, Eq)]
#[repr(i32)]
pub enum UIProfile {
Classic = 0,
Pocket = 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
use std::collections::BTreeMap;
use std::io::Cursor;
use std::io::{Cursor, Write};

use bedrockrs_macros::gamepacket;
use bedrockrs_macros::{gamepacket, ProtoCodec};
use bedrockrs_proto_core::error::ProtoCodecError;
use bedrockrs_proto_core::ProtoCodec;
use serde_json::Value;

use varint_rs::VarintWriter;
// Yeah we aren't supporting secure things rn...

#[gamepacket(id = 3)]
#[derive(Debug, Clone)]
pub struct HandshakeServerToClientPacket {
pub handshake_jwt: BTreeMap<String, Value>,
pub jwt: String,
}

impl ProtoCodec for HandshakeServerToClientPacket {
fn proto_serialize(&self, _stream: &mut Vec<u8>) -> Result<(), ProtoCodecError> {
todo!()
fn proto_serialize(&self, stream: &mut Vec<u8>) -> Result<(), ProtoCodecError> {
stream.write_u32_varint(self.jwt.len() as u32)?;
stream.write_all(self.jwt.as_bytes())?;

Ok(())
}

fn proto_deserialize(_stream: &mut Cursor<&[u8]>) -> Result<Self, ProtoCodecError> {
fn proto_deserialize(stream: &mut Cursor<&[u8]>) -> Result<Self, ProtoCodecError> {
todo!()
}

fn get_size_prediction(&self) -> usize {
todo!()
300
}
}

Expand Down
Loading