feat(secure): a bunch of stuff
This commit is contained in:
parent
4f26aae1d7
commit
81f7a0a738
15 changed files with 480 additions and 49 deletions
|
|
@ -2,4 +2,6 @@ pub mod packet;
|
|||
pub mod router;
|
||||
pub mod socket;
|
||||
mod auth_module;
|
||||
mod sockaddr;
|
||||
mod sockaddr;
|
||||
pub mod secure;
|
||||
pub mod station_url;
|
||||
|
|
@ -365,6 +365,8 @@ impl PRUDPPacket {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
|
||||
|
||||
Self{
|
||||
header: PRUDPHeader{
|
||||
types_and_flags: flags,
|
||||
|
|
|
|||
101
src/prudp/secure.rs
Normal file
101
src/prudp/secure.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use std::io::Cursor;
|
||||
use hmac::digest::consts::U32;
|
||||
use log::error;
|
||||
use rc4::cipher::StreamCipherCoreWrapper;
|
||||
use rc4::{KeyInit, Rc4, Rc4Core, StreamCipher};
|
||||
use rc4::consts::U16;
|
||||
use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions};
|
||||
use crate::kerberos::{derive_key, TicketInternalData};
|
||||
use crate::nex::account::Account;
|
||||
use crate::prudp::packet::PRUDPHeader;
|
||||
use crate::prudp::socket::EncryptionPair;
|
||||
use crate::rmc::structures::RmcSerialize;
|
||||
|
||||
pub fn read_secure_connection_data(data: &[u8], act: &Account) -> Option<([u8; 32], u32, u32)>{
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
let mut ticket_data: Vec<u8> = Vec::deserialize(&mut cursor).ok()?;
|
||||
let mut request_data: Vec<u8> = Vec::deserialize(&mut cursor).ok()?;
|
||||
|
||||
let ticket_data_size = ticket_data.len();
|
||||
|
||||
let ticket_data = &mut ticket_data[0..ticket_data_size-0x10];
|
||||
|
||||
let server_key = derive_key(act.pid, act.kerbros_password);
|
||||
|
||||
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U16>> =
|
||||
Rc4::new_from_slice(&server_key).expect("unable to init rc4 keystream");
|
||||
|
||||
rc4.apply_keystream(ticket_data);
|
||||
|
||||
let ticket_data: &TicketInternalData = match bytemuck::try_from_bytes(ticket_data){
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("unable to read internal ticket data: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// todo: add ticket expiration
|
||||
|
||||
let TicketInternalData{
|
||||
session_key,
|
||||
pid: ticket_source_pid,
|
||||
issued_time
|
||||
} = *ticket_data;
|
||||
|
||||
// todo: add checking if tickets are signed with a valid md5-hmac
|
||||
let request_data_length = request_data.len();
|
||||
let request_data = &mut request_data[0.. request_data_length - 0x10];
|
||||
|
||||
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U32>> =
|
||||
Rc4::new_from_slice(&session_key).expect("unable to init rc4 keystream");
|
||||
|
||||
rc4.apply_keystream(request_data);
|
||||
|
||||
let mut reqest_data_cursor = Cursor::new(request_data);
|
||||
|
||||
let pid: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||
|
||||
if pid != ticket_source_pid{
|
||||
let ticket_created_on = issued_time.to_regular_time();
|
||||
|
||||
error!("someone tried to spoof their pid, ticket was created on: {}", ticket_created_on.to_rfc2822());
|
||||
return None;
|
||||
}
|
||||
|
||||
let _cid: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||
let response_check: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||
|
||||
|
||||
|
||||
Some((session_key, pid, response_check))
|
||||
}
|
||||
|
||||
type Rc4U32 = StreamCipherCoreWrapper<Rc4Core<U32>>;
|
||||
|
||||
pub fn generate_secure_encryption_pairs(mut session_key: [u8; 32], count: u8) -> Vec<EncryptionPair>{
|
||||
let mut vec = Vec::with_capacity(count as usize);
|
||||
|
||||
vec.push(EncryptionPair{
|
||||
send: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4")),
|
||||
recv: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4"))
|
||||
});
|
||||
|
||||
for _ in 1..=count{
|
||||
let modifier = session_key.len() + 1;
|
||||
|
||||
let key_length = session_key.len();
|
||||
|
||||
for (position, val) in (&mut session_key[0..key_length/2]).iter_mut().enumerate(){
|
||||
*val = val.wrapping_add((modifier - position) as u8);
|
||||
}
|
||||
|
||||
vec.push(EncryptionPair{
|
||||
send: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4")),
|
||||
recv: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4"))
|
||||
});
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
|
@ -30,8 +30,8 @@ type OnConnectHandlerFn = Box<dyn Fn(PRUDPPacket, u8) -> Pin<Box<dyn Future<Outp
|
|||
type OnDataHandlerFn = Box<dyn for<'a> Fn(PRUDPPacket, Arc<SocketData>, &'a mut MutexGuard<'_, ConnectionData>) -> Pin<Box<dyn Future<Output=()> + 'a + Send>> + Send + Sync>;
|
||||
|
||||
pub struct ActiveSecureConnectionData {
|
||||
pid: u32,
|
||||
session_key: [u8; 32],
|
||||
pub(crate) pid: u32,
|
||||
pub(crate) session_key: [u8; 32],
|
||||
}
|
||||
|
||||
pub struct SocketData {
|
||||
|
|
@ -41,6 +41,7 @@ pub struct SocketData {
|
|||
connections: RwLock<HashMap<PRUDPSockAddr, Arc<Mutex<ConnectionData>>>>,
|
||||
on_connect_handler: OnConnectHandlerFn,
|
||||
on_data_handler: OnDataHandlerFn,
|
||||
|
||||
}
|
||||
|
||||
pub struct EncryptionPair{
|
||||
|
|
@ -54,6 +55,7 @@ pub struct ActiveConnectionData {
|
|||
pub reliable_client_queue: VecDeque<PRUDPPacket>,
|
||||
pub encryption_pairs: Vec<EncryptionPair>,
|
||||
pub server_session_id: u8,
|
||||
pub connection_id: u32,
|
||||
pub active_secure_connection_data: Option<ActiveSecureConnectionData>
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +229,7 @@ impl SocketData {
|
|||
};
|
||||
|
||||
let Some((
|
||||
accepted,
|
||||
response_data,
|
||||
encryption_pairs,
|
||||
active_secure_connection_data
|
||||
)) = (self.on_connect_handler)(packet.clone(), *max_substream).await else {
|
||||
|
|
@ -242,11 +244,14 @@ impl SocketData {
|
|||
reliable_client_counter: 2,
|
||||
reliable_server_counter: 1,
|
||||
server_session_id: packet.header.session_id,
|
||||
active_secure_connection_data
|
||||
active_secure_connection_data,
|
||||
connection_id: random()
|
||||
});
|
||||
|
||||
let mut response_packet = packet.base_response_packet();
|
||||
|
||||
response_packet.payload = response_data;
|
||||
|
||||
response_packet.header.types_and_flags.set_types(CONNECT);
|
||||
response_packet.header.types_and_flags.set_flag(ACK);
|
||||
response_packet.header.types_and_flags.set_flag(HAS_SIZE);
|
||||
|
|
@ -286,14 +291,7 @@ impl SocketData {
|
|||
|
||||
response_packet.set_sizes();
|
||||
|
||||
let potential_session_key = connection
|
||||
.active_connection_data
|
||||
.as_ref()
|
||||
.unwrap().active_secure_connection_data
|
||||
.as_ref()
|
||||
.map(|s| s.session_key);
|
||||
|
||||
response_packet.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
||||
response_packet.calculate_and_assign_signature(self.access_key, None, Some(connection.server_signature));
|
||||
|
||||
let mut vec = Vec::new();
|
||||
response_packet.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||
|
|
@ -322,7 +320,14 @@ impl SocketData {
|
|||
ack.header.session_id = active_connection.server_session_id;
|
||||
|
||||
ack.set_sizes();
|
||||
ack.calculate_and_assign_signature(self.access_key, None, Some(connection.server_signature));
|
||||
let potential_session_key = connection
|
||||
.active_connection_data
|
||||
.as_ref()
|
||||
.unwrap().active_secure_connection_data
|
||||
.as_ref()
|
||||
.map(|s| s.session_key);
|
||||
|
||||
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
||||
|
||||
let mut vec = Vec::new();
|
||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||
|
|
@ -383,7 +388,13 @@ impl SocketData {
|
|||
ack.header.session_id = active_connection.server_session_id;
|
||||
|
||||
ack.set_sizes();
|
||||
ack.calculate_and_assign_signature(self.access_key, None, Some(*server_signature));
|
||||
|
||||
let potential_session_key = active_connection.
|
||||
active_secure_connection_data
|
||||
.as_ref()
|
||||
.map(|s| s.session_key);
|
||||
|
||||
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(*server_signature));
|
||||
|
||||
let mut vec = Vec::new();
|
||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||
|
|
@ -392,28 +403,27 @@ impl SocketData {
|
|||
}
|
||||
}
|
||||
DISCONNECT => {
|
||||
let ConnectionData{
|
||||
server_signature,
|
||||
active_connection_data,
|
||||
..
|
||||
} = &*connection;
|
||||
|
||||
let Some(active_connection) = active_connection_data.as_ref() else {
|
||||
let Some(active_connection) = &connection.active_connection_data else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
|
||||
info!("client disconnected");
|
||||
|
||||
let mut ack = packet.base_acknowledgement_packet();
|
||||
|
||||
ack.header.session_id = active_connection.server_session_id;
|
||||
|
||||
ack.set_sizes();
|
||||
|
||||
let potential_session_key = active_connection_data
|
||||
.as_ref()
|
||||
.unwrap().active_secure_connection_data
|
||||
let potential_session_key = active_connection.active_secure_connection_data
|
||||
.as_ref()
|
||||
.map(|s| s.session_key);
|
||||
|
||||
|
||||
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(*server_signature));
|
||||
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
||||
|
||||
let mut vec = Vec::new();
|
||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||
|
|
@ -450,7 +460,14 @@ impl ConnectionData{
|
|||
|
||||
packet.set_sizes();
|
||||
|
||||
packet.calculate_and_assign_signature(socket.access_key, None, Some(self.server_signature));
|
||||
let potential_session_key = self.active_connection_data
|
||||
.as_ref()
|
||||
.unwrap().active_secure_connection_data
|
||||
.as_ref()
|
||||
.map(|s| s.session_key);
|
||||
|
||||
|
||||
packet.calculate_and_assign_signature(socket.access_key, potential_session_key, Some(self.server_signature));
|
||||
|
||||
let mut vec = Vec::new();
|
||||
|
||||
|
|
|
|||
161
src/prudp/station_url.rs
Normal file
161
src/prudp/station_url.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use std::net::Ipv4Addr;
|
||||
use log::error;
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
use crate::prudp::station_url::Type::{PRUDP, PRUDPS, UDP};
|
||||
use crate::prudp::station_url::UrlOptions::{Address, ConnectionID, NatFiltering, NatMapping, NatType, Platform, PMP, Port, PrincipalID, RVConnectionID, StreamID, StreamType, UPNP};
|
||||
|
||||
pub enum Type{
|
||||
UDP,
|
||||
PRUDP,
|
||||
PRUDPS
|
||||
}
|
||||
|
||||
pub mod nat_types{
|
||||
pub const BEHIND_NAT: u8 = 1;
|
||||
pub const PUBLIC: u8 = 2;
|
||||
}
|
||||
|
||||
pub enum UrlOptions {
|
||||
Address(Ipv4Addr),
|
||||
Port(u16),
|
||||
StreamType(u8),
|
||||
StreamID(u8),
|
||||
ConnectionID(u8),
|
||||
PrincipalID(u32),
|
||||
NatType(u8),
|
||||
NatMapping(u8),
|
||||
NatFiltering(u8),
|
||||
UPNP(u8),
|
||||
RVConnectionID(u32),
|
||||
Platform(u8),
|
||||
PMP(u8),
|
||||
|
||||
}
|
||||
|
||||
pub struct StationUrl{
|
||||
pub url_type: Type,
|
||||
pub options: Vec<UrlOptions>
|
||||
}
|
||||
|
||||
impl StationUrl{
|
||||
fn read_options(options: &str) -> Option<Vec<UrlOptions>>{
|
||||
let mut options_out = Vec::new();
|
||||
|
||||
for option in options.split(';'){
|
||||
if option == "" { continue; }
|
||||
let mut option_parts = option.split('=');
|
||||
let option_name= option_parts.next()?.to_ascii_lowercase();
|
||||
let option_value = option_parts.next()?;
|
||||
|
||||
match option_name.as_ref(){
|
||||
"address" => {
|
||||
options_out.push(Address(option_value.parse().ok()?))
|
||||
},
|
||||
"port" => {
|
||||
options_out.push(Port(option_value.parse().ok()?))
|
||||
}
|
||||
"natf" => {
|
||||
options_out.push(NatFiltering(option_value.parse().ok()?))
|
||||
}
|
||||
"natm" => {
|
||||
options_out.push(NatMapping(option_value.parse().ok()?))
|
||||
}
|
||||
"sid" => {
|
||||
options_out.push(StreamID(option_value.parse().ok()?))
|
||||
}
|
||||
"upnp" => {
|
||||
options_out.push(UPNP(option_value.parse().ok()?))
|
||||
}
|
||||
"type" => {
|
||||
options_out.push(NatType(option_value.parse().ok()?))
|
||||
}
|
||||
"stream" => {
|
||||
options_out.push(StreamType(option_value.parse().ok()?))
|
||||
}
|
||||
"RVCID" => {
|
||||
options_out.push(RVConnectionID(option_value.parse().ok()?))
|
||||
}
|
||||
"pl" => {
|
||||
options_out.push(Platform(option_value.parse().ok()?))
|
||||
}
|
||||
"pmp" => {
|
||||
options_out.push(PMP(option_value.parse().ok()?))
|
||||
}
|
||||
_ => {
|
||||
error!("unimplemented option type, skipping: {}", option_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Some(options_out)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for StationUrl{
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, ()> {
|
||||
let (url_type, options) = value.split_at(value.find(":/").ok_or(())?);
|
||||
|
||||
let options = &options[2..];
|
||||
|
||||
let url_type = match url_type{
|
||||
"udp" => UDP,
|
||||
"prudp" => PRUDP,
|
||||
"prudps" => PRUDPS,
|
||||
_ => return Err(())
|
||||
};
|
||||
|
||||
let options = Self::read_options(options).ok_or(())?;
|
||||
|
||||
Ok(
|
||||
Self{
|
||||
url_type,
|
||||
options
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<String> for &'a StationUrl{
|
||||
fn into(self) -> String {
|
||||
let mut url = match self.url_type{
|
||||
UDP => "udp:/",
|
||||
PRUDP => "prudp:/",
|
||||
PRUDPS => "prudps:/"
|
||||
}.to_owned();
|
||||
|
||||
for option in &self.options{
|
||||
match option{
|
||||
Address(v) => write!(url, "address={}", v).expect("failed to write"),
|
||||
Port(v) => write!(url, "port={}", v).expect("failed to write"),
|
||||
StreamType(v) => write!(url, "stream={}", v).expect("failed to write"),
|
||||
StreamID(v) => write!(url, "sid={}", v).expect("failed to write"),
|
||||
ConnectionID(v) => write!(url, "CID={}", v).expect("failed to write"),
|
||||
PrincipalID(v) => write!(url, "PID={}", v).expect("failed to write"),
|
||||
NatType(v) => write!(url, "type={}", v).expect("failed to write"),
|
||||
NatMapping(v) => write!(url, "natm={}", v).expect("failed to write"),
|
||||
NatFiltering(v) => write!(url, "natf={}", v).expect("failed to write"),
|
||||
UPNP(v) => write!(url, "upnp={}", v).expect("failed to write"),
|
||||
RVConnectionID(v) => write!(url, "RVCID={}", v).expect("failed to write"),
|
||||
Platform(v) => write!(url, "pl={}", v).expect("failed to write"),
|
||||
PMP(v) => write!(url, "pmp={}", v).expect("failed to write"),
|
||||
}
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StationUrl{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let str: String = self.into();
|
||||
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue