Add Authentication
This commit is contained in:
parent
5dfebfafba
commit
41c4d23821
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 Icon
|
||||||
- [x] Custom Status (MOTD)
|
- [x] Custom Status (MOTD)
|
||||||
- Login
|
- Login
|
||||||
- [ ] Authentication
|
- [x] Authentication
|
||||||
- [x] Encryption
|
- [x] Encryption
|
||||||
- [ ] Packet Compression
|
- [ ] Packet Compression
|
||||||
- Player Configuration
|
- Player Configuration
|
||||||
@ -32,6 +32,8 @@ Pumpkin is currently under heavy development.
|
|||||||
- [ ] Set Resource Pack
|
- [ ] Set Resource Pack
|
||||||
- [ ] Cookies
|
- [ ] Cookies
|
||||||
- World
|
- World
|
||||||
|
- [x] World Joining
|
||||||
|
- [x] World Loading
|
||||||
- [ ] Chunk Loading
|
- [ ] Chunk Loading
|
||||||
- [ ] World Generation
|
- [ ] World Generation
|
||||||
- [ ] World Borders
|
- [ ] World Borders
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt};
|
use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt};
|
||||||
|
|
||||||
pub struct CLoginDisconnect<'a> {
|
pub struct CLoginDisconnect<'a> {
|
||||||
@ -81,11 +83,11 @@ impl<'a> CLoginSuccess<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub struct Property {
|
pub struct Property {
|
||||||
name: String,
|
pub name: String,
|
||||||
value: String,
|
pub value: String,
|
||||||
is_signed: bool,
|
pub signature: String,
|
||||||
signature: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ClientPacket for CLoginSuccess<'a> {
|
impl<'a> ClientPacket for CLoginSuccess<'a> {
|
||||||
@ -97,8 +99,11 @@ impl<'a> ClientPacket for CLoginSuccess<'a> {
|
|||||||
bytebuf.put_list::<Property>(self.properties, |p, v| {
|
bytebuf.put_list::<Property>(self.properties, |p, v| {
|
||||||
p.put_string(&v.name);
|
p.put_string(&v.name);
|
||||||
p.put_string(&v.value);
|
p.put_string(&v.value);
|
||||||
p.put_bool(v.is_signed);
|
// has signature ?
|
||||||
p.put_option(&v.signature, |p, v| p.put_string(v.as_str()))
|
p.put_bool(true);
|
||||||
|
// option
|
||||||
|
p.put_bool(true);
|
||||||
|
p.put_string(&v.signature);
|
||||||
});
|
});
|
||||||
bytebuf.put_bool(self.strict_error_handling);
|
bytebuf.put_bool(self.strict_error_handling);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,21 +11,33 @@ pumpkin-registry = { path = "../pumpkin-registry"}
|
|||||||
# config
|
# config
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
toml = "0.8.19"
|
||||||
|
|
||||||
|
pollster = "0.3.0"
|
||||||
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
num-bigint = "0.4.6"
|
||||||
|
|
||||||
# encryption
|
# encryption
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
rsa-der = "0.3.0"
|
rsa-der = "0.3.0"
|
||||||
|
|
||||||
flate2 = "1.0.30"
|
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"
|
thiserror = "1.0.63"
|
||||||
|
|
||||||
# icon loading
|
# icon loading
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"]}
|
image = { version = "0.25", default-features = false, features = ["png"]}
|
||||||
|
|
||||||
|
# logging
|
||||||
simple_logger = "5.0.0"
|
simple_logger = "5.0.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
@ -33,8 +45,7 @@ log = "0.4"
|
|||||||
mio = { version = "1.0.1", features = ["os-poll", "net"]}
|
mio = { version = "1.0.1", features = ["os-poll", "net"]}
|
||||||
crossbeam-channel = "0.5.13"
|
crossbeam-channel = "0.5.13"
|
||||||
|
|
||||||
uuid = "1.10"
|
uuid = { version = "1.10", features = ["serde"]}
|
||||||
toml = "0.8.19"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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,
|
ConnectionState, KnownPack,
|
||||||
};
|
};
|
||||||
use pumpkin_registry::Registry;
|
use pumpkin_registry::Registry;
|
||||||
|
use rsa::Pkcs1v15Encrypt;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
client::authentication::{self, GameProfile},
|
||||||
entity::player::{ChatMode, Hand},
|
entity::player::{ChatMode, Hand},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Client, PlayerConfig};
|
use super::{authentication::auth_digest, Client, EncryptionError, PlayerConfig};
|
||||||
|
|
||||||
/// Processes incoming Packets from the Client to the Server
|
/// Processes incoming Packets from the Client to the Server
|
||||||
/// Implements the `Client` Packets, So everything before the Play state, then will use the `PlayerPacketProcessor`
|
/// 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) {
|
fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) {
|
||||||
dbg!("login start");
|
dbg!("login start");
|
||||||
self.name = Some(login_start.name);
|
// default game profile, when no online mode
|
||||||
self.uuid = Some(login_start.uuid);
|
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 verify_token: [u8; 4] = rand::random();
|
||||||
let public_key_der = &server.public_key_der;
|
let public_key_der = &server.public_key_der;
|
||||||
let packet = CEncryptionRequest::new(
|
let packet = CEncryptionRequest::new(
|
||||||
"",
|
"",
|
||||||
public_key_der,
|
public_key_der,
|
||||||
&verify_token,
|
&verify_token,
|
||||||
false, // TODO
|
server.base_config.online_mode, // TODO
|
||||||
);
|
);
|
||||||
self.send_packet(packet)
|
self.send_packet(packet)
|
||||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||||
@ -103,20 +112,39 @@ impl ClientPacketProcessor for Client {
|
|||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
encryption_response: SEncryptionResponse,
|
encryption_response: SEncryptionResponse,
|
||||||
) {
|
) {
|
||||||
dbg!("encryption response");
|
let shared_secret = server
|
||||||
self.enable_encryption(server, encryption_response.shared_secret)
|
.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()));
|
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||||
|
|
||||||
if let Some(uuid) = self.uuid {
|
if let Some(profile) = self.gameprofile.as_ref().cloned() {
|
||||||
if let Some(name) = &self.name {
|
let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false);
|
||||||
let packet = CLoginSuccess::new(uuid, name.clone(), &[], false); // todo
|
self.send_packet(packet)
|
||||||
self.send_packet(packet)
|
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
||||||
.unwrap_or_else(|e| self.kick(&e.to_string()));
|
|
||||||
} else {
|
|
||||||
self.kick("Name is none");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.kick("UUID is none");
|
self.kick("game profile is none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
|
net::SocketAddr,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ use crate::{
|
|||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use authentication::GameProfile;
|
||||||
use mio::{event::Event, net::TcpStream, Token};
|
use mio::{event::Event, net::TcpStream, Token};
|
||||||
use player_packet::PlayerPacketProcessor;
|
use player_packet::PlayerPacketProcessor;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
@ -34,6 +36,7 @@ use rsa::Pkcs1v15Encrypt;
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod authentication;
|
||||||
mod client_packet;
|
mod client_packet;
|
||||||
pub mod player_packet;
|
pub mod player_packet;
|
||||||
|
|
||||||
@ -53,8 +56,8 @@ pub struct PlayerConfig {
|
|||||||
pub struct Client {
|
pub struct Client {
|
||||||
pub player: Option<Player>,
|
pub player: Option<Player>,
|
||||||
|
|
||||||
pub name: Option<String>,
|
pub gameprofile: Option<GameProfile>,
|
||||||
pub uuid: Option<uuid::Uuid>,
|
|
||||||
pub config: Option<PlayerConfig>,
|
pub config: Option<PlayerConfig>,
|
||||||
pub brand: Option<String>,
|
pub brand: Option<String>,
|
||||||
|
|
||||||
@ -63,19 +66,20 @@ pub struct Client {
|
|||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
pub token: Rc<Token>,
|
pub token: Rc<Token>,
|
||||||
pub connection: TcpStream,
|
pub connection: TcpStream,
|
||||||
|
pub address: SocketAddr,
|
||||||
enc: PacketEncoder,
|
enc: PacketEncoder,
|
||||||
dec: PacketDecoder,
|
dec: PacketDecoder,
|
||||||
pub client_packets_queue: VecDeque<RawPacket>,
|
pub client_packets_queue: VecDeque<RawPacket>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(token: Rc<Token>, connection: TcpStream) -> Self {
|
pub fn new(token: Rc<Token>, connection: TcpStream, address: SocketAddr) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
gameprofile: None,
|
||||||
uuid: None,
|
|
||||||
config: None,
|
config: None,
|
||||||
brand: None,
|
brand: None,
|
||||||
token,
|
token,
|
||||||
|
address,
|
||||||
player: None,
|
player: None,
|
||||||
connection_state: ConnectionState::HandShake,
|
connection_state: ConnectionState::HandShake,
|
||||||
connection,
|
connection,
|
||||||
@ -96,7 +100,7 @@ impl Client {
|
|||||||
pub fn enable_encryption(
|
pub fn enable_encryption(
|
||||||
&mut self,
|
&mut self,
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
shared_secret: Vec<u8>,
|
shared_secret: Vec<u8>, // decrypted
|
||||||
) -> Result<(), EncryptionError> {
|
) -> Result<(), EncryptionError> {
|
||||||
self.encrytion = true;
|
self.encrytion = true;
|
||||||
let shared_secret = server
|
let shared_secret = server
|
||||||
|
|||||||
@ -95,7 +95,7 @@ fn main() -> io::Result<()> {
|
|||||||
Interest::READABLE.add(Interest::WRITABLE),
|
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 => {
|
token => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user