Server RSA

This commit is contained in:
Garen Tyler 2025-06-06 19:28:07 -06:00
parent 4cc58fbf81
commit d5a87ed4f5
Signed by: garentyler
SSH Key Fingerprint: SHA256:G4ke7blZMdpWPbkescyZ7IQYE4JAtwpI85YoJdq+S7U
9 changed files with 136 additions and 112 deletions

1
Cargo.lock generated
View File

@ -236,6 +236,7 @@ dependencies = [
"rsa", "rsa",
"serde", "serde",
"serde_json", "serde_json",
"spki",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
"tokio-util", "tokio-util",

View File

@ -44,3 +44,4 @@ der = { version = "0.7.10", features = ["alloc", "derive"] }
aes = "0.8.4" aes = "0.8.4"
cfb8 = { version = "0.8.1", features = ["alloc"] } cfb8 = { version = "0.8.1", features = ["alloc"] }
generic-array = "0.14.7" generic-array = "0.14.7"
spki = { version = "0.7.3", features = ["std"] }

View File

@ -55,17 +55,17 @@ impl DownstreamConnection {
pub async fn handle_handshake(&mut self) -> Result<(), Error> { pub async fn handle_handshake(&mut self) -> Result<(), Error> {
use packets::handshake::serverbound::Handshake; use packets::handshake::serverbound::Handshake;
let handshake = self let handshake = self.read_specific_packet::<Handshake>().await?;
.read_specific_packet::<Handshake>()
.await
.ok_or(Error::Unexpected)??;
match handshake.next_state { match handshake.next_state {
ClientState::Status => { ClientState::Status => {
*self.client_state_mut() = DownstreamConnectionState::StatusRequest; *self.client_state_mut() = DownstreamConnectionState::StatusRequest;
*self.inner_state_mut() = ClientState::Status; *self.inner_state_mut() = ClientState::Status;
} }
ClientState::Login => todo!(), ClientState::Login => {
*self.client_state_mut() = DownstreamConnectionState::LoginStart;
*self.inner_state_mut() = ClientState::Login;
}
_ => { _ => {
self.disconnect(Some( self.disconnect(Some(
serde_json::json!({ "text": "Received invalid handshake." }), serde_json::json!({ "text": "Received invalid handshake." }),
@ -79,14 +79,13 @@ impl DownstreamConnection {
pub async fn handle_status_ping(&mut self, online_player_count: usize) -> Result<(), Error> { pub async fn handle_status_ping(&mut self, online_player_count: usize) -> Result<(), Error> {
// The state just changed from Handshake to Status. // The state just changed from Handshake to Status.
use base64::Engine; use base64::Engine;
use packets::status::clientbound::{PingResponse, StatusResponse}; use packets::status::{
clientbound::{PingResponse, StatusResponse},
serverbound::{PingRequest, StatusRequest},
};
// Read the status request packet. // Read the status request packet.
let Packet::StatusRequest(_status_request) = let _status_request = self.read_specific_packet::<StatusRequest>().await?;
self.read_packet().await.ok_or(Error::Unexpected)??
else {
return Err(Error::Unexpected);
};
// Send the status response packet. // Send the status response packet.
let config = Config::instance(); let config = Config::instance();
@ -110,19 +109,101 @@ impl DownstreamConnection {
}).await?; }).await?;
// Read the ping request packet. // Read the ping request packet.
let Packet::PingRequest(ping_request) = let payload = self.read_specific_packet::<PingRequest>().await?.payload;
self.read_packet().await.ok_or(Error::Unexpected)??
else {
return Err(Error::Unexpected);
};
// Send the ping response packet. // Send the ping response packet.
self.send_packet(PingResponse { self.send_packet(PingResponse { payload }).await?;
payload: ping_request.payload,
self.disconnect(None).await?;
Ok(())
}
pub async fn handle_login(&mut self) -> Result<(), Error> {
// The state just changed from Handshake to Login.
use packets::login::{clientbound::LoginSuccess, serverbound::LoginStart};
// Read login start packet.
let login_start = self.read_specific_packet::<LoginStart>().await?;
// Enable encryption and authenticate with Mojang.
self.enable_encryption().await?;
// Enable compression.
self.enable_compression().await?;
// Send login success packet.
self.send_packet(LoginSuccess {
// Generate a random UUID if none was provided.
uuid: login_start.uuid.unwrap_or(uuid::Uuid::new_v4()),
username: login_start.name,
properties: vec![],
}) })
.await?; .await?;
self.disconnect(None).await Ok(())
}
pub async fn enable_encryption(&mut self) -> Result<(), Error> {
use crate::protocol::encryption::*;
use packets::login::{clientbound::EncryptionRequest, serverbound::EncryptionResponse};
use rand::{rngs::StdRng, Rng, SeedableRng};
assert!(matches!(self.inner_state(), ClientState::Login));
// RSA keys were generated on startup.
let config = Config::instance();
let (public_key, private_key) = &config.rsa_key_pair;
tracing::trace!(
"{}",
public_key
.serialize()
.iter()
.map(|b| format!("{b:02X?}"))
.collect::<Vec<String>>()
.join("")
);
// Generate a verify token.
let mut rng = StdRng::from_entropy();
let verify_token: [u8; 16] = rng.gen();
// Send the encryption request packet.
self.send_packet(EncryptionRequest {
server_id: "".into(),
public_key: public_key.serialize(),
verify_token: verify_token.to_vec(),
// TODO: Implement Mojang authentication.
use_mojang_authentication: false,
})
.await?;
// Read the encryption response packet.
let encryption_response = self.read_specific_packet::<EncryptionResponse>().await?;
// Verify the response.
let decrypted_verify_token = private_key
.decrypt(Pkcs1v15Encrypt, &encryption_response.verify_token)
.expect("failed to decrypt verify token");
if decrypted_verify_token != verify_token {
return Err(Error::Invalid);
}
// Decrypt the shared secret.
let shared_secret = private_key
.decrypt(Pkcs1v15Encrypt, &encryption_response.shared_secret)
.expect("failed to decrypt shared secret");
// Enable encryption on the connection.
let encryptor =
Aes128Cfb8Encryptor::new((&(*shared_secret)).into(), (&(*shared_secret)).into());
let decryptor =
Aes128Cfb8Decryptor::new((&(*shared_secret)).into(), (&(*shared_secret)).into());
self.inner.stream.codec_mut().aes_cipher = Some((encryptor, decryptor, 0));
Ok(())
}
pub async fn enable_compression(&mut self) -> Result<(), Error> {
// TODO: Implement compression.
Ok(())
} }
pub async fn read_packet(&mut self) -> Option<Result<Packet, Error>> { pub async fn read_packet(&mut self) -> Option<Result<Packet, Error>> {
self.inner.read_packet().await self.inner.read_packet().await

View File

@ -78,14 +78,9 @@ impl GenericConnection {
packet packet
} }
pub async fn read_specific_packet<P: TryFrom<Packet>>(&mut self) -> Option<Result<P, Error>> { pub async fn read_specific_packet<P: TryFrom<Packet>>(&mut self) -> Result<P, Error> {
self.read_packet() let packet = self.read_packet().await.ok_or(Error::Unexpected)??;
.await P::try_from(packet).map_err(|_| Error::Unexpected)
.map(|packet| match packet.map(P::try_from) {
Ok(Ok(p)) => Ok(p),
Ok(Err(_)) => Err(Error::Unexpected),
Err(e) => Err(e),
})
} }
pub async fn send_packet<P: Into<Packet>>(&mut self, packet: P) -> Result<(), Error> { pub async fn send_packet<P: Into<Packet>>(&mut self, packet: P) -> Result<(), Error> {
let packet: Packet = packet.into(); let packet: Packet = packet.into();

View File

@ -25,6 +25,15 @@ impl UpstreamConnection {
match packet { match packet {
Packet::EncryptionRequest(ref packet) => { Packet::EncryptionRequest(ref packet) => {
// Extract the public key from the packet. // Extract the public key from the packet.
tracing::trace!(
"{}",
packet
.public_key
.iter()
.map(|b| format!("{b:02X?}"))
.collect::<Vec<String>>()
.join("")
);
let public_key = rsa::RsaPublicKey::parse(&packet.public_key) let public_key = rsa::RsaPublicKey::parse(&packet.public_key)
.expect("Failed to parse RSA public key from packet") .expect("Failed to parse RSA public key from packet")
.1; .1;

View File

@ -12,6 +12,8 @@ pub enum Error {
Unexpected, Unexpected,
#[error("Internal channel disconnected")] #[error("Internal channel disconnected")]
ConnectionChannelDisconnnection, ConnectionChannelDisconnnection,
#[error("Invalid response")]
Invalid,
} }
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self { fn from(value: std::io::Error) -> Self {

View File

@ -1,7 +1,5 @@
use der::{ use der::Encode;
asn1::{AnyRef, ObjectIdentifier}, use spki::{DecodePublicKey, SubjectPublicKeyInfo};
Decode, DecodeValue, Encode, EncodeValue, Header, Reader, Sequence, Tag,
};
pub use crate::protocol::parsing::Parsable; pub use crate::protocol::parsing::Parsable;
pub use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; pub use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
@ -9,85 +7,19 @@ pub use generic_array::{
typenum::{UInt, UTerm, B1}, typenum::{UInt, UTerm, B1},
GenericArray, GenericArray,
}; };
pub use rsa::{RsaPrivateKey, RsaPublicKey}; pub use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
pub type Aes128Cfb8Encryptor = cfb8::Encryptor<aes::Aes128>; pub type Aes128Cfb8Encryptor = cfb8::Encryptor<aes::Aes128>;
pub type Aes128Cfb8Decryptor = cfb8::Decryptor<aes::Aes128>; pub type Aes128Cfb8Decryptor = cfb8::Decryptor<aes::Aes128>;
pub type GenericCFB8BlockArray = GenericArray<u8, UInt<UTerm, B1>>; pub type GenericCFB8BlockArray = GenericArray<u8, UInt<UTerm, B1>>;
impl Parsable for RsaPublicKey { impl Parsable for RsaPublicKey {
fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> { fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> {
let spki = SubjectPublicKeyInfo::from_der(data).unwrap(); Ok((&[], RsaPublicKey::from_public_key_der(data).unwrap()))
let modulus = rsa::BigUint::from_bytes_be(spki.subject_public_key.modulus.as_bytes());
let exponent =
rsa::BigUint::from_bytes_be(spki.subject_public_key.public_exponent.as_bytes());
Ok((&[], RsaPublicKey::new(modulus, exponent).unwrap()))
} }
fn serialize(&self) -> Vec<u8> { fn serialize(&self) -> Vec<u8> {
use rsa::traits::PublicKeyParts; SubjectPublicKeyInfo::from_key(self.clone())
let algorithm = PublicKeyAlgorithm::default(); .unwrap()
let subject_public_key = SubjectPublicKey { .to_der()
modulus: der::asn1::Int::new(&self.n().to_bytes_be()).unwrap(), .unwrap()
public_exponent: der::asn1::Int::new(&self.e().to_bytes_be()).unwrap(),
};
let spki = SubjectPublicKeyInfo {
algorithm,
subject_public_key,
};
let mut buf = Vec::new();
spki.encode(&mut buf).unwrap();
buf
} }
} }
// Custom decode implementation for SubjectPublicKeyInfo.
#[derive(Debug, Clone, PartialEq, Eq)]
struct SubjectPublicKeyInfo<'a> {
algorithm: PublicKeyAlgorithm<'a>,
subject_public_key: SubjectPublicKey,
}
impl<'a> DecodeValue<'a> for SubjectPublicKeyInfo<'a> {
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> der::Result<Self> {
let algorithm = reader.decode()?;
let spk_der: der::asn1::BitString = reader.decode()?;
let spk_der = spk_der.as_bytes().unwrap();
let subject_public_key = SubjectPublicKey::from_der(spk_der).unwrap();
Ok(Self {
algorithm,
subject_public_key,
})
}
}
impl EncodeValue for SubjectPublicKeyInfo<'_> {
fn value_len(&self) -> der::Result<der::Length> {
self.algorithm.value_len()? + self.subject_public_key.value_len()?
}
fn encode_value(&self, writer: &mut impl der::Writer) -> der::Result<()> {
self.algorithm.encode_value(writer)?;
self.subject_public_key.encode_value(writer)?;
Ok(())
}
}
impl<'a> Sequence<'a> for SubjectPublicKeyInfo<'a> {}
#[derive(Debug, Clone, PartialEq, Eq, Sequence)]
struct PublicKeyAlgorithm<'a> {
pub algorithm: ObjectIdentifier,
pub parameters: Option<AnyRef<'a>>,
}
impl Default for PublicKeyAlgorithm<'_> {
fn default() -> Self {
Self {
algorithm: ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"),
parameters: Some(AnyRef::new(Tag::Null, &[]).unwrap()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Sequence)]
struct SubjectPublicKey {
pub modulus: der::asn1::Int,
pub public_exponent: der::asn1::Int,
}

View File

@ -7,14 +7,9 @@ pub use tokio::task::JoinError as TaskError;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
#[error(transparent)] #[error(transparent)]
Io(IoError), Io(#[from] IoError),
#[error(transparent)] #[error(transparent)]
Task(TaskError), Task(#[from] TaskError),
#[error(transparent)] #[error(transparent)]
Network(NetworkError), Network(#[from] NetworkError),
}
impl From<NetworkError> for Error {
fn from(err: NetworkError) -> Self {
Error::Network(err)
}
} }

View File

@ -16,7 +16,7 @@ use tokio_util::sync::CancellationToken;
#[derive(Debug)] #[derive(Debug)]
pub struct Server { pub struct Server {
running: CancellationToken, running: CancellationToken,
connections: DownstreamConnectionManager, pub connections: DownstreamConnectionManager,
listener: JoinHandle<()>, listener: JoinHandle<()>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -80,6 +80,14 @@ impl App for Server {
.await; .await;
// Handle login connections. // Handle login connections.
let _ = futures::future::join_all(
self.connections
.clients_mut()
.filter(|c| matches!(c.client_state(), DownstreamConnectionState::LoginStart))
.map(|c| c.handle_login()),
)
.await;
// Handle play connection packets. // Handle play connection packets.
// Process world updates. // Process world updates.
// Send out play connection updates. // Send out play connection updates.