mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
Add Authentication
This commit is contained in:
1000
Cargo.lock
generated
1000
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ Pumpkin is currently under heavy development.
|
||||
- [x] Custom Icon
|
||||
- [x] Custom Status (MOTD)
|
||||
- Login
|
||||
- [ ] Authentication
|
||||
- [x] Authentication
|
||||
- [x] Encryption
|
||||
- [ ] Packet Compression
|
||||
- Player Configuration
|
||||
@@ -32,6 +32,8 @@ Pumpkin is currently under heavy development.
|
||||
- [ ] Set Resource Pack
|
||||
- [ ] Cookies
|
||||
- World
|
||||
- [x] World Joining
|
||||
- [x] World Loading
|
||||
- [ ] Chunk Loading
|
||||
- [ ] World Generation
|
||||
- [ ] World Borders
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt};
|
||||
|
||||
pub struct CLoginDisconnect<'a> {
|
||||
@@ -81,11 +83,11 @@ impl<'a> CLoginSuccess<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct Property {
|
||||
name: String,
|
||||
value: String,
|
||||
is_signed: bool,
|
||||
signature: Option<String>,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
impl<'a> ClientPacket for CLoginSuccess<'a> {
|
||||
@@ -97,8 +99,11 @@ impl<'a> ClientPacket for CLoginSuccess<'a> {
|
||||
bytebuf.put_list::<Property>(self.properties, |p, v| {
|
||||
p.put_string(&v.name);
|
||||
p.put_string(&v.value);
|
||||
p.put_bool(v.is_signed);
|
||||
p.put_option(&v.signature, |p, v| p.put_string(v.as_str()))
|
||||
// has signature ?
|
||||
p.put_bool(true);
|
||||
// option
|
||||
p.put_bool(true);
|
||||
p.put_string(&v.signature);
|
||||
});
|
||||
bytebuf.put_bool(self.strict_error_handling);
|
||||
}
|
||||
|
||||
@@ -11,21 +11,33 @@ pumpkin-registry = { path = "../pumpkin-registry"}
|
||||
# config
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.8.19"
|
||||
|
||||
pollster = "0.3.0"
|
||||
|
||||
rand = "0.8.5"
|
||||
|
||||
num-bigint = "0.4.6"
|
||||
|
||||
# encryption
|
||||
rsa = "0.9.6"
|
||||
rsa-der = "0.3.0"
|
||||
|
||||
flate2 = "1.0.30"
|
||||
|
||||
# authentication
|
||||
reqwest = { version = "0.12.5", features = ["json"]}
|
||||
|
||||
sha1 = "0.10.6"
|
||||
digest = "=0.11.0-pre.9"
|
||||
|
||||
thiserror = "1.0.63"
|
||||
|
||||
# icon loading
|
||||
base64 = "0.22.1"
|
||||
image = { version = "0.25", default-features = false, features = ["png"]}
|
||||
|
||||
# logging
|
||||
simple_logger = "5.0.0"
|
||||
log = "0.4"
|
||||
|
||||
@@ -33,8 +45,7 @@ log = "0.4"
|
||||
mio = { version = "1.0.1", features = ["os-poll", "net"]}
|
||||
crossbeam-channel = "0.5.13"
|
||||
|
||||
uuid = "1.10"
|
||||
toml = "0.8.19"
|
||||
uuid = { version = "1.10", features = ["serde"]}
|
||||
|
||||
|
||||
|
||||
|
||||
62
pumpkin/src/client/authentication.rs
Normal file
62
pumpkin/src/client/authentication.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::{error, net::IpAddr};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use pumpkin_protocol::client::login::Property;
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::server::Server;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct GameProfile {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
pub async fn authenticate(
|
||||
username: &str,
|
||||
server_hash: &str,
|
||||
ip: &IpAddr,
|
||||
server: &mut Server,
|
||||
) -> Result<GameProfile, AuthError> {
|
||||
assert!(server.auth_client.is_some());
|
||||
let address = if server.base_config.prevent_proxy_connections {
|
||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}")
|
||||
} else {
|
||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")
|
||||
};
|
||||
let response = server
|
||||
.auth_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get(address)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| AuthError::FailedResponse)?;
|
||||
match response.status() {
|
||||
StatusCode::OK => {}
|
||||
StatusCode::NO_CONTENT => Err(AuthError::UnverifiedUsername)?,
|
||||
other => Err(AuthError::UnknownStatusCode(other.as_str().to_string()))?,
|
||||
}
|
||||
let profile: GameProfile = response.json().await.map_err(|_| AuthError::FailedParse)?;
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
pub fn auth_digest(bytes: &[u8]) -> String {
|
||||
BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AuthError {
|
||||
#[error("Authentication servers are down")]
|
||||
FailedResponse,
|
||||
#[error("Failed to verify username")]
|
||||
UnverifiedUsername,
|
||||
#[error("Failed to parse JSON into Game Profile")]
|
||||
FailedParse,
|
||||
#[error("Unknown Status Code")]
|
||||
UnknownStatusCode(String),
|
||||
}
|
||||
@@ -13,13 +13,16 @@ use pumpkin_protocol::{
|
||||
ConnectionState, KnownPack,
|
||||
};
|
||||
use pumpkin_registry::Registry;
|
||||
use rsa::Pkcs1v15Encrypt;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use crate::{
|
||||
client::authentication::{self, GameProfile},
|
||||
entity::player::{ChatMode, Hand},
|
||||
server::Server,
|
||||
};
|
||||
|
||||
use super::{Client, PlayerConfig};
|
||||
use super::{authentication::auth_digest, Client, EncryptionError, PlayerConfig};
|
||||
|
||||
/// Processes incoming Packets from the Client to the Server
|
||||
/// Implements the `Client` Packets, So everything before the Play state, then will use the `PlayerPacketProcessor`
|
||||
@@ -84,15 +87,21 @@ impl ClientPacketProcessor for Client {
|
||||
|
||||
fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) {
|
||||
dbg!("login start");
|
||||
self.name = Some(login_start.name);
|
||||
self.uuid = Some(login_start.uuid);
|
||||
// default game profile, when no online mode
|
||||
self.gameprofile = Some(GameProfile {
|
||||
id: login_start.uuid,
|
||||
name: login_start.name,
|
||||
properties: vec![],
|
||||
});
|
||||
|
||||
// we want encryption
|
||||
let verify_token: [u8; 4] = rand::random();
|
||||
let public_key_der = &server.public_key_der;
|
||||
let packet = CEncryptionRequest::new(
|
||||
"",
|
||||
public_key_der,
|
||||
&verify_token,
|
||||
false, // TODO
|
||||
server.base_config.online_mode, // TODO
|
||||
);
|
||||
self.send_packet(packet)
|
||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||
@@ -103,20 +112,39 @@ impl ClientPacketProcessor for Client {
|
||||
server: &mut Server,
|
||||
encryption_response: SEncryptionResponse,
|
||||
) {
|
||||
dbg!("encryption response");
|
||||
self.enable_encryption(server, encryption_response.shared_secret)
|
||||
let shared_secret = server
|
||||
.private_key
|
||||
.decrypt(Pkcs1v15Encrypt, &encryption_response.shared_secret)
|
||||
.map_err(|_| EncryptionError::FailedDecrypt)
|
||||
.unwrap();
|
||||
|
||||
if server.base_config.online_mode {
|
||||
let hash = Sha1::new()
|
||||
.chain_update(&shared_secret)
|
||||
.chain_update(&server.public_key_der)
|
||||
.finalize();
|
||||
let hash = auth_digest(&hash);
|
||||
let ip = self.address.ip();
|
||||
match pollster::block_on(authentication::authenticate(
|
||||
&self.gameprofile.as_ref().unwrap().name,
|
||||
&hash,
|
||||
&ip,
|
||||
server,
|
||||
)) {
|
||||
Ok(p) => self.gameprofile = Some(p),
|
||||
Err(e) => self.kick(&e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
self.enable_encryption(server, shared_secret)
|
||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||
|
||||
if let Some(uuid) = self.uuid {
|
||||
if let Some(name) = &self.name {
|
||||
let packet = CLoginSuccess::new(uuid, name.clone(), &[], false); // todo
|
||||
self.send_packet(packet)
|
||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||
} else {
|
||||
self.kick("Name is none");
|
||||
}
|
||||
if let Some(profile) = self.gameprofile.as_ref().cloned() {
|
||||
let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false);
|
||||
self.send_packet(packet)
|
||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||
} else {
|
||||
self.kick("UUID is none");
|
||||
self.kick("game profile is none");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
net::SocketAddr,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -9,6 +10,7 @@ use crate::{
|
||||
server::Server,
|
||||
};
|
||||
|
||||
use authentication::GameProfile;
|
||||
use mio::{event::Event, net::TcpStream, Token};
|
||||
use player_packet::PlayerPacketProcessor;
|
||||
use pumpkin_protocol::{
|
||||
@@ -34,6 +36,7 @@ use rsa::Pkcs1v15Encrypt;
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
|
||||
mod authentication;
|
||||
mod client_packet;
|
||||
pub mod player_packet;
|
||||
|
||||
@@ -53,8 +56,8 @@ pub struct PlayerConfig {
|
||||
pub struct Client {
|
||||
pub player: Option<Player>,
|
||||
|
||||
pub name: Option<String>,
|
||||
pub uuid: Option<uuid::Uuid>,
|
||||
pub gameprofile: Option<GameProfile>,
|
||||
|
||||
pub config: Option<PlayerConfig>,
|
||||
pub brand: Option<String>,
|
||||
|
||||
@@ -63,19 +66,20 @@ pub struct Client {
|
||||
pub closed: bool,
|
||||
pub token: Rc<Token>,
|
||||
pub connection: TcpStream,
|
||||
pub address: SocketAddr,
|
||||
enc: PacketEncoder,
|
||||
dec: PacketDecoder,
|
||||
pub client_packets_queue: VecDeque<RawPacket>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(token: Rc<Token>, connection: TcpStream) -> Self {
|
||||
pub fn new(token: Rc<Token>, connection: TcpStream, address: SocketAddr) -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
uuid: None,
|
||||
gameprofile: None,
|
||||
config: None,
|
||||
brand: None,
|
||||
token,
|
||||
address,
|
||||
player: None,
|
||||
connection_state: ConnectionState::HandShake,
|
||||
connection,
|
||||
@@ -96,7 +100,7 @@ impl Client {
|
||||
pub fn enable_encryption(
|
||||
&mut self,
|
||||
server: &mut Server,
|
||||
shared_secret: Vec<u8>,
|
||||
shared_secret: Vec<u8>, // decrypted
|
||||
) -> Result<(), EncryptionError> {
|
||||
self.encrytion = true;
|
||||
let shared_secret = server
|
||||
|
||||
@@ -95,7 +95,7 @@ fn main() -> io::Result<()> {
|
||||
Interest::READABLE.add(Interest::WRITABLE),
|
||||
)?;
|
||||
|
||||
connections.insert(token, Client::new(Rc::new(token), connection));
|
||||
connections.insert(token, Client::new(Rc::new(token), connection, addr));
|
||||
},
|
||||
|
||||
token => {
|
||||
|
||||
Reference in New Issue
Block a user