global buf size mtu based config and protocol rename
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
use crate::crypto::session::SessionKeys;
|
||||
use crate::nrxp::errors::{ErrorAction, ErrorStage, TlsError};
|
||||
use crate::nrxp::parser::parser::Parser;
|
||||
use crate::tlseng::extension::ExtensionStack;
|
||||
use crate::tlseng::handshake::{ClientHello, HelloHeader, ServerHello};
|
||||
use crate::tlseng::profile::{BrowserProfile, ServerProfile};
|
||||
use crate::tlseng::tls_record::TlsRecord;
|
||||
use crate::tlseng::types::{ContentType, HelloType};
|
||||
use crate::tlseng::ApplicationData;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
pub trait TlsInterceptor {
|
||||
type Output;
|
||||
|
||||
fn start_process(buffer: &mut BytesMut) -> Result<Option<Self::Output>, TlsError> {
|
||||
match TlsRecord::parse(buffer) {
|
||||
Ok(Some(record)) => Self::handle_record(record),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError>;
|
||||
}
|
||||
|
||||
pub enum HandshakeMessage {
|
||||
Client {
|
||||
base: ClientHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
Server {
|
||||
base: ServerHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
}
|
||||
|
||||
impl HandshakeMessage {
|
||||
pub fn random(&self) -> [u8; 32] {
|
||||
match self {
|
||||
Self::Client { base, .. } => base.random,
|
||||
Self::Server { base, .. } => base.random,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extensions(&self) -> &ExtensionStack {
|
||||
match self {
|
||||
Self::Client { extensions, .. } => extensions,
|
||||
Self::Server { extensions, .. } => extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsInterceptor for HandshakeMessage {
|
||||
type Output = HandshakeMessage;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError> {
|
||||
if record.content_type != ContentType::Handshake {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Handshake("Expected Handshake record"),
|
||||
ErrorAction::Drop,
|
||||
record.serialize(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut payload = BytesMut::from(record.payload.as_ref());
|
||||
if let Some(header) = HelloHeader::parse(&mut payload)? {
|
||||
match header.header_type {
|
||||
HelloType::Client => {
|
||||
if let Some(hello) = ClientHello::parse(&mut payload)? {
|
||||
let ext =
|
||||
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
|
||||
.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Ext Err"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
return Ok(Some(HandshakeMessage::Client {
|
||||
base: hello,
|
||||
extensions: ext,
|
||||
}));
|
||||
}
|
||||
}
|
||||
HelloType::Server => {
|
||||
if let Some(hello) = ServerHello::parse(&mut payload)? {
|
||||
let ext =
|
||||
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
|
||||
.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Ext Err"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
return Ok(Some(HandshakeMessage::Server {
|
||||
base: hello,
|
||||
extensions: ext,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsInterceptor for ApplicationData {
|
||||
type Output = ApplicationData;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError> {
|
||||
if record.content_type != ContentType::ApplicationData {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::ApplicationData("Expected AppData record"),
|
||||
ErrorAction::Drop,
|
||||
record.serialize(),
|
||||
));
|
||||
}
|
||||
Ok(Some(ApplicationData {
|
||||
_len: record.payload.len(),
|
||||
payload: record.payload,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TlsBridge;
|
||||
|
||||
impl TlsBridge {
|
||||
pub fn unpack_handshake(buffer: &mut BytesMut) -> Result<Option<HandshakeMessage>, TlsError> {
|
||||
HandshakeMessage::start_process(buffer)
|
||||
}
|
||||
|
||||
pub fn unpack_app_data(buffer: &mut BytesMut) -> Result<Option<ApplicationData>, TlsError> {
|
||||
ApplicationData::start_process(buffer)
|
||||
}
|
||||
|
||||
pub fn wrap_client_hello(profile: &BrowserProfile, host: &str, keys: &SessionKeys) -> Bytes {
|
||||
ClientHello::make_client_hello(profile, host, keys)
|
||||
}
|
||||
|
||||
pub fn wrap_server_hello(
|
||||
client_msg: &HandshakeMessage,
|
||||
keys: &mut SessionKeys,
|
||||
profile: &ServerProfile,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
if let HandshakeMessage::Client { base, extensions } = client_msg {
|
||||
if base.session_id.len() != 32 {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Handshake("Invalid SessionID len"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut received_tag = [0u8; 16];
|
||||
received_tag.copy_from_slice(&base.session_id[16..32]);
|
||||
|
||||
if !keys.verify_auth_tag(&received_tag) {
|
||||
netrunner_logger::warn!("Unauthorized ClientHello: Auth Tag mismatch");
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Handshake("Auth Failed"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
keys.update_keys(base.random, extensions, true)
|
||||
.map_err(|e| {
|
||||
netrunner_logger::error!(error = %e, "Server failed key update");
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Key Exchange Failed"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let server_pub_key = keys.ecdh.public_key.to_bytes();
|
||||
|
||||
Ok(ServerHello::make_server_hello(
|
||||
base,
|
||||
&server_pub_key,
|
||||
keys.salt.get_local(),
|
||||
profile,
|
||||
))
|
||||
} else {
|
||||
Err(TlsError::new(
|
||||
ErrorStage::Handshake("Expected ClientHello for SH generation"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn pack_app_data(buffer: Bytes) -> Bytes {
|
||||
TlsRecord::build_application_data(buffer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::crypto::aead::AeadPacker;
|
||||
use crate::crypto::chacha::ChaChaCipher;
|
||||
use crate::crypto::session::SessionKeys;
|
||||
use crate::nrxp::codec::bridge::TlsBridge;
|
||||
use crate::nrxp::codec::frame::{Frame, FrameHeader, FrameType, Padding};
|
||||
use crate::nrxp::errors::{ErrorAction, ErrorStage, TlsError};
|
||||
use crate::nrxp::parser::parser::Parser;
|
||||
use crate::tlseng::profile::{BrowserProfile, ServerProfile};
|
||||
|
||||
pub struct Codec {
|
||||
crypto: ChaChaCipher,
|
||||
pub session_keys: SessionKeys,
|
||||
staging: BytesMut,
|
||||
}
|
||||
|
||||
impl Codec {
|
||||
pub fn new(is_initiator: bool) -> Self {
|
||||
Self {
|
||||
crypto: ChaChaCipher::new(),
|
||||
session_keys: SessionKeys::new(is_initiator),
|
||||
staging: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_client_handshake(
|
||||
&mut self,
|
||||
profile: &BrowserProfile,
|
||||
host: &str,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
Ok(TlsBridge::wrap_client_hello(
|
||||
profile,
|
||||
host,
|
||||
&self.session_keys,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn make_server_handshake(&mut self, buffer: &mut BytesMut) -> Result<Bytes, TlsError> {
|
||||
let client_msg = TlsBridge::unpack_handshake(buffer)?.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("No CH"),
|
||||
ErrorAction::Wait,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let server_hello_record = TlsBridge::wrap_server_hello(
|
||||
&client_msg,
|
||||
&mut self.session_keys,
|
||||
&ServerProfile::MODERN,
|
||||
)?;
|
||||
|
||||
let (w_key, w_iv, r_key, r_iv) = self.session_keys.get_aead_parameters();
|
||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||
|
||||
Ok(server_hello_record)
|
||||
}
|
||||
|
||||
pub fn process_handshake(&mut self, buffer: &mut BytesMut) -> Result<(), TlsError> {
|
||||
let mes_opt = TlsBridge::unpack_handshake(buffer)?;
|
||||
let mes = mes_opt.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Incomplete record"),
|
||||
ErrorAction::Wait,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let (w_key, w_iv, r_key, r_iv) = self
|
||||
.session_keys
|
||||
.update_keys(mes.random(), mes.extensions(), false)
|
||||
.map_err(|e| {
|
||||
netrunner_logger::error!(error = %e, "Client failed to update keys from ServerHello");
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Keys update error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn try_handshake(&mut self, buffer: &mut BytesMut) -> Result<bool, TlsError> {
|
||||
match self.process_handshake(buffer) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) if e.action == ErrorAction::Wait => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn outbound(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
frame_type: FrameType,
|
||||
payload: Bytes,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
let padding = Padding::generate_padding();
|
||||
|
||||
let tag = self.session_keys.generate_auth_tag();
|
||||
netrunner_logger::debug!(
|
||||
step = %(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() / 60),
|
||||
auth_key_hash = %hex::encode(&self.session_keys.auth_key[..4]),
|
||||
generated_tag = %hex::encode(&tag[..4]),
|
||||
"OUTBOUND: Generated auth tag"
|
||||
);
|
||||
|
||||
let header = FrameHeader {
|
||||
auth_tag: [0u8; 16],
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len: payload.len() as u16,
|
||||
padding_len: padding.len as u16,
|
||||
};
|
||||
|
||||
let frame = Frame {
|
||||
header,
|
||||
payload,
|
||||
padding: padding.data,
|
||||
};
|
||||
let mut frame_bytes = frame.into_bytes(&tag);
|
||||
|
||||
let encrypted_payload = self.crypto.encrypt(&mut frame_bytes).map_err(|e| {
|
||||
netrunner_logger::error!("Encryption failed: {:?}", e);
|
||||
TlsError::new(
|
||||
ErrorStage::Tls("Encryption failed"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(TlsBridge::pack_app_data(encrypted_payload))
|
||||
}
|
||||
|
||||
pub fn encrypt_data(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
frame_type: FrameType,
|
||||
data: Bytes,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
self.outbound(stream_id, frame_type, data)
|
||||
}
|
||||
|
||||
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
||||
if !self.staging.is_empty() {
|
||||
if let Some(frame) = self.try_parse_frame()? {
|
||||
return Ok(Some(frame));
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(app_data) = TlsBridge::unpack_app_data(buffer).map_err(|e| {
|
||||
self.staging.clear();
|
||||
e
|
||||
})? {
|
||||
let mut data_to_decrypt = BytesMut::from(app_data.payload);
|
||||
|
||||
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|_| {
|
||||
self.staging.clear();
|
||||
TlsError::new(
|
||||
ErrorStage::Tls("Decr error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if decrypted.len() < 16 {
|
||||
self.staging.clear();
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Packet too short for auth"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut received_tag = [0u8; 16];
|
||||
received_tag.copy_from_slice(&decrypted[..16]);
|
||||
|
||||
if !self.session_keys.verify_auth_tag(&received_tag) {
|
||||
netrunner_logger::error!(
|
||||
expected_hash = %hex::encode(&self.session_keys.auth_key[..4]),
|
||||
received = %hex::encode(&received_tag[..4]),
|
||||
"AUTH MISMATCH: Potential replay or MITM attack. Dropping connection."
|
||||
);
|
||||
self.staging.clear();
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Auth tag mismatch"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
self.staging.extend_from_slice(&decrypted);
|
||||
|
||||
if let Some(frame) = self.try_parse_frame()? {
|
||||
return Ok(Some(frame));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
||||
match Frame::parse(&mut self.staging) {
|
||||
Ok(Some(frame)) => Ok(Some(frame)),
|
||||
Ok(None) => Ok(None),
|
||||
Err(_) => {
|
||||
self.staging.clear();
|
||||
Err(TlsError::new(
|
||||
ErrorStage::Tls("Parse error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::nrxp::codec::MAX_PADDING_SIZE;
|
||||
|
||||
pub struct Padding {
|
||||
pub len: u16,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn generate_padding() -> Padding {
|
||||
let mut rng = rand::rng();
|
||||
let random_u32: u32 = rng.next_u32();
|
||||
let padding_len: u16 = (random_u32 % MAX_PADDING_SIZE) as u16;
|
||||
let mut padding = vec![0u8; padding_len as usize];
|
||||
rng.fill_bytes(&mut padding);
|
||||
Padding {
|
||||
len: padding_len,
|
||||
data: Bytes::from(padding),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FrameType {
|
||||
Connect = 0x00,
|
||||
Data = 0x01,
|
||||
Close = 0x02,
|
||||
Heartbeat = 0x03,
|
||||
UdpConnect = 0x04,
|
||||
UdpData = 0x05,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FrameHeader {
|
||||
pub auth_tag: [u8; 16],
|
||||
pub stream_id: u32,
|
||||
pub frame_type: FrameType,
|
||||
pub payload_len: u16,
|
||||
pub padding_len: u16,
|
||||
}
|
||||
|
||||
pub struct Frame {
|
||||
pub header: FrameHeader,
|
||||
pub payload: Bytes,
|
||||
pub padding: Bytes,
|
||||
}
|
||||
|
||||
const AUTH_TAG_SIZE: u16 = 16;
|
||||
const STREAM_ID_SIZE: u16 = 4;
|
||||
const FRAME_TYPE_SIZE: u16 = 1;
|
||||
const PAYLOAD_LEN_SIZE: u16 = 2;
|
||||
const PADDING_LEN_SIZE: u16 = 2;
|
||||
|
||||
pub const FRAME_HEADER_SIZE: u16 =
|
||||
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
|
||||
|
||||
impl Frame {
|
||||
pub fn into_bytes(self, auth_key: &[u8; 16]) -> BytesMut {
|
||||
let updated_padding = Padding::generate_padding();
|
||||
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + self.padding.len();
|
||||
let mut buf = BytesMut::with_capacity(total_size);
|
||||
|
||||
buf.put_slice(auth_key);
|
||||
buf.put_u32(self.header.stream_id);
|
||||
buf.put_u8(self.header.frame_type as u8);
|
||||
buf.put_u16(self.header.payload_len);
|
||||
buf.put_u16(updated_padding.len);
|
||||
|
||||
buf.put(self.payload);
|
||||
|
||||
buf.put(updated_padding.data);
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
mod bridge;
|
||||
pub mod codec;
|
||||
pub mod frame;
|
||||
pub mod socks;
|
||||
|
||||
pub const MAX_PADDING_SIZE: u32 = 255;
|
||||
@@ -0,0 +1,229 @@
|
||||
use std::fmt;
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
|
||||
use crate::nrxp::parser::parser::Parser;
|
||||
|
||||
pub const SOCKS5_VERSION: u8 = 0x05;
|
||||
pub const REPLY_SUCCESS: u8 = 0x00;
|
||||
pub const REPLY_AUTH_FAILURE: u8 = 0xFF;
|
||||
pub const SOCKS5_MIN_HEADER: usize = 4;
|
||||
pub const ATYP_IPV4: u8 = 0x01;
|
||||
pub const ATYP_DOMAIN: u8 = 0x03;
|
||||
pub const ATYP_IPV6: u8 = 0x04;
|
||||
pub const IPV4_SIZE: usize = 4;
|
||||
pub const IPV6_SIZE: usize = 16;
|
||||
pub const PORT_SIZE: usize = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SocksRequest {
|
||||
Handshake { methods: Vec<u8> },
|
||||
Connect { command: u8, target: SocksTarget },
|
||||
Unknown,
|
||||
}
|
||||
impl SocksRequest {
|
||||
pub async fn handle_handshake<S>(
|
||||
stream: &mut S,
|
||||
buf: &mut BytesMut,
|
||||
) -> Result<SocksTarget, String>
|
||||
where
|
||||
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
||||
{
|
||||
loop {
|
||||
if let Some(req) = Self::parse(buf)? {
|
||||
if let SocksRequest::Handshake { .. } = req {
|
||||
let mut reply = BytesMut::with_capacity(2);
|
||||
SocksReply::HandshakeSelect { method: 0x00 }.write_to(&mut reply);
|
||||
stream.write_all(&reply).await.map_err(|e| e.to_string())?;
|
||||
break;
|
||||
}
|
||||
return Err("Expected Handshake, got something else".into());
|
||||
}
|
||||
if stream.read_buf(buf).await.map_err(|e| e.to_string())? == 0 {
|
||||
return Err("Client closed during greeting".into());
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Some(req) = Self::parse(buf)? {
|
||||
if let SocksRequest::Connect { command, target } = req {
|
||||
if command != 0x01 {
|
||||
return Err(format!("Unsupported SOCKS command: 0x{:02X}", command));
|
||||
}
|
||||
return Ok(target);
|
||||
}
|
||||
}
|
||||
if stream.read_buf(buf).await.map_err(|e| e.to_string())? == 0 {
|
||||
return Err("Client closed during connect request".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn perform_client_handshake<S>(
|
||||
stream: &mut S,
|
||||
target_addr: &TargetAddress,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
||||
{
|
||||
let greeting = [SOCKS5_VERSION, 0x01, 0x00];
|
||||
stream
|
||||
.write_all(&greeting)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut method_selection = [0u8; 2];
|
||||
stream
|
||||
.read_exact(&mut method_selection)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if method_selection[0] != SOCKS5_VERSION || method_selection[1] != 0x00 {
|
||||
return Err(format!(
|
||||
"Proxy rejected auth method or version: {:02X?}",
|
||||
method_selection
|
||||
));
|
||||
}
|
||||
|
||||
let mut connect_req = BytesMut::with_capacity(32);
|
||||
connect_req.put_u8(SOCKS5_VERSION);
|
||||
connect_req.put_u8(0x01);
|
||||
connect_req.put_u8(0x00);
|
||||
|
||||
match target_addr {
|
||||
TargetAddress::Ipv4(ip, port) => {
|
||||
connect_req.put_u8(ATYP_IPV4);
|
||||
connect_req.put_slice(&ip.octets());
|
||||
connect_req.put_u16(*port);
|
||||
}
|
||||
TargetAddress::Ipv6(ip, port) => {
|
||||
connect_req.put_u8(ATYP_IPV6);
|
||||
connect_req.put_slice(&ip.octets());
|
||||
connect_req.put_u16(*port);
|
||||
}
|
||||
TargetAddress::Domain(host, port) => {
|
||||
connect_req.put_u8(ATYP_DOMAIN);
|
||||
|
||||
let host_bytes = host.as_bytes();
|
||||
connect_req.put_u8(host_bytes.len() as u8);
|
||||
connect_req.put_slice(host_bytes);
|
||||
connect_req.put_u16(*port);
|
||||
}
|
||||
}
|
||||
|
||||
stream
|
||||
.write_all(&connect_req)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut reply_header = [0u8; 4];
|
||||
stream
|
||||
.read_exact(&mut reply_header)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if reply_header[1] != REPLY_SUCCESS {
|
||||
return Err(format!(
|
||||
"Proxy failed to connect, code: {:02X}",
|
||||
reply_header[1]
|
||||
));
|
||||
}
|
||||
|
||||
let atyp = reply_header[3];
|
||||
let remain_len = match atyp {
|
||||
ATYP_IPV4 => IPV4_SIZE + PORT_SIZE,
|
||||
ATYP_IPV6 => IPV6_SIZE + PORT_SIZE,
|
||||
ATYP_DOMAIN => {
|
||||
let len = stream.read_u8().await.map_err(|e| e.to_string())?;
|
||||
len as usize + PORT_SIZE
|
||||
}
|
||||
_ => return Err("Unknown ATYP in proxy response".into()),
|
||||
};
|
||||
|
||||
let mut discard = vec![0u8; remain_len];
|
||||
stream
|
||||
.read_exact(&mut discard)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SocksReply {
|
||||
HandshakeSelect {
|
||||
method: u8,
|
||||
},
|
||||
ConnectResult {
|
||||
reply_code: u8,
|
||||
atyp: u8,
|
||||
addr: [u8; 4],
|
||||
port: u16,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TargetAddress {
|
||||
Ipv4(std::net::Ipv4Addr, u16),
|
||||
Domain(String, u16),
|
||||
Ipv6(std::net::Ipv6Addr, u16),
|
||||
}
|
||||
|
||||
impl fmt::Display for TargetAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TargetAddress::Ipv4(addr, port) => write!(f, "{}:{}", addr, port),
|
||||
TargetAddress::Domain(domain, port) => write!(f, "{}:{}", domain, port),
|
||||
TargetAddress::Ipv6(addr, port) => write!(f, "[{}]:{}", addr, port),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SocksTarget {
|
||||
pub addr: TargetAddress,
|
||||
}
|
||||
|
||||
impl SocksReply {
|
||||
pub fn write_to(self, buf: &mut BytesMut) {
|
||||
match self {
|
||||
SocksReply::HandshakeSelect { method } => {
|
||||
buf.put_u8(SOCKS5_VERSION);
|
||||
buf.put_u8(method);
|
||||
}
|
||||
SocksReply::ConnectResult {
|
||||
reply_code,
|
||||
atyp,
|
||||
addr,
|
||||
port,
|
||||
} => {
|
||||
buf.put_u8(SOCKS5_VERSION);
|
||||
buf.put_u8(reply_code);
|
||||
buf.put_u8(0x00);
|
||||
buf.put_u8(atyp);
|
||||
buf.put_slice(&addr);
|
||||
buf.put_u16(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SocksTarget {
|
||||
pub fn to_string(&self) -> String {
|
||||
match &self.addr {
|
||||
TargetAddress::Ipv4(ip, port) => {
|
||||
format!("{}:{}", ip, port)
|
||||
}
|
||||
|
||||
TargetAddress::Ipv6(ip, port) => {
|
||||
format!("[{}]:{}", ip, port)
|
||||
}
|
||||
|
||||
TargetAddress::Domain(domain, port) => {
|
||||
let clean_domain = domain.replace('\0', "");
|
||||
format!("{}:{}", clean_domain, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use bytes::Bytes;
|
||||
use netrunner_logger::{error, trace, warn};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorAction {
|
||||
Wait,
|
||||
Redirect,
|
||||
Drop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorStage {
|
||||
Tls(&'static str),
|
||||
Handshake(&'static str),
|
||||
ApplicationData(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TlsError {
|
||||
pub stage: ErrorStage,
|
||||
pub action: ErrorAction,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl TlsError {
|
||||
pub fn new(stage: ErrorStage, action: ErrorAction, data: Bytes) -> Self {
|
||||
Self {
|
||||
stage,
|
||||
action,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
fn log_error(&self) {
|
||||
let stage_name = match &self.stage {
|
||||
ErrorStage::Tls(_) => "TLS",
|
||||
ErrorStage::Handshake(_) => "Handshake",
|
||||
ErrorStage::ApplicationData(_) => "AppData",
|
||||
};
|
||||
|
||||
let message = match &self.stage {
|
||||
ErrorStage::Tls(m) | ErrorStage::Handshake(m) | ErrorStage::ApplicationData(m) => m,
|
||||
};
|
||||
|
||||
let data_preview = if !self.data.is_empty() {
|
||||
let limit = self.data.len().min(8);
|
||||
format!(
|
||||
"Hex: {:02x?}{}",
|
||||
&self.data[..limit],
|
||||
if self.data.len() > 8 { "..." } else { "" }
|
||||
)
|
||||
} else {
|
||||
"No data".to_string()
|
||||
};
|
||||
|
||||
match self.action {
|
||||
ErrorAction::Wait => {
|
||||
trace!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"{}", message
|
||||
);
|
||||
}
|
||||
ErrorAction::Redirect => {
|
||||
warn!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"⚠️ {}", message
|
||||
);
|
||||
}
|
||||
ErrorAction::Drop => {
|
||||
error!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"🚨 {}", message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_strategy(&self) -> ErrorAction {
|
||||
self.log_error();
|
||||
self.action
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod codec;
|
||||
pub mod errors;
|
||||
pub mod parser;
|
||||
@@ -0,0 +1,6 @@
|
||||
mod netr;
|
||||
pub mod parser;
|
||||
mod socks;
|
||||
mod tls;
|
||||
|
||||
use parser::Parser;
|
||||
@@ -0,0 +1,94 @@
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use crate::nrxp::{
|
||||
codec::frame::{Frame, FrameHeader, FrameType, FRAME_HEADER_SIZE},
|
||||
parser::parser::Parser,
|
||||
};
|
||||
|
||||
impl Parser for FrameHeader {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
bytes.len() >= FRAME_HEADER_SIZE as usize
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut header_chunk = bytes.split_to(FRAME_HEADER_SIZE as usize);
|
||||
|
||||
let mut auth_tag = [0u8; 16];
|
||||
header_chunk.copy_to_slice(&mut auth_tag);
|
||||
|
||||
let stream_id = header_chunk.get_u32();
|
||||
|
||||
let frame_type_byte = header_chunk.get_u8();
|
||||
let frame_type = match frame_type_byte {
|
||||
0x00 => FrameType::Connect,
|
||||
0x01 => FrameType::Data,
|
||||
0x02 => FrameType::Close,
|
||||
0x03 => FrameType::Heartbeat,
|
||||
_ => FrameType::Close,
|
||||
};
|
||||
|
||||
let payload_len = header_chunk.get_u16();
|
||||
let padding_len = header_chunk.get_u16();
|
||||
|
||||
Ok(Some(Self {
|
||||
auth_tag,
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len,
|
||||
padding_len,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for Frame {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||
return false;
|
||||
}
|
||||
|
||||
let p_len = u16::from_be_bytes([bytes[21], bytes[22]]) as usize;
|
||||
let pad_len = u16::from_be_bytes([bytes[23], bytes[24]]) as usize;
|
||||
|
||||
netrunner_logger::debug!(
|
||||
"CAN_PARSE: p_len={}, pad_len={}, total_needed={}, have={}",
|
||||
p_len,
|
||||
pad_len,
|
||||
25 + p_len + pad_len,
|
||||
bytes.len()
|
||||
);
|
||||
|
||||
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
|
||||
|
||||
let p_len = header.payload_len as usize;
|
||||
let pad_len = header.padding_len as usize;
|
||||
|
||||
if bytes.len() < p_len + pad_len {
|
||||
return Err("Buffer corrupted: length mismatch after header parse".into());
|
||||
}
|
||||
|
||||
let payload = bytes.split_to(p_len).freeze();
|
||||
let padding = bytes.split_to(pad_len).freeze();
|
||||
|
||||
Ok(Some(Self {
|
||||
header,
|
||||
payload,
|
||||
padding,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use bytes::BytesMut;
|
||||
|
||||
pub trait Parser {
|
||||
type Error;
|
||||
fn can_parse(bytes: &BytesMut) -> bool;
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use crate::nrxp::{codec::socks::*, parser::parser::Parser};
|
||||
|
||||
impl Parser for SocksTarget {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 4 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let atyp = bytes[3];
|
||||
match atyp {
|
||||
ATYP_IPV4 => bytes.len() >= 4 + 4 + 2,
|
||||
|
||||
ATYP_IPV6 => bytes.len() >= 4 + 16 + 2,
|
||||
|
||||
ATYP_DOMAIN => {
|
||||
if bytes.len() < 5 {
|
||||
return false;
|
||||
}
|
||||
let domain_len = bytes[4] as usize;
|
||||
bytes.len() >= 4 + 1 + domain_len + 2
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let atyp = bytes[3];
|
||||
|
||||
let total_len = match atyp {
|
||||
ATYP_IPV4 => SOCKS5_MIN_HEADER + IPV4_SIZE + PORT_SIZE,
|
||||
ATYP_DOMAIN => SOCKS5_MIN_HEADER + 1 + (bytes[4] as usize) + PORT_SIZE,
|
||||
ATYP_IPV6 => SOCKS5_MIN_HEADER + IPV6_SIZE + PORT_SIZE,
|
||||
_ => return Err("Unsupported address type".to_string()),
|
||||
};
|
||||
|
||||
let mut packet = bytes.split_to(total_len);
|
||||
packet.advance(4);
|
||||
|
||||
let addr = match atyp {
|
||||
ATYP_IPV4 => {
|
||||
let octets: [u8; 4] = packet.split_to(4)[..].try_into().unwrap();
|
||||
let port = packet.get_u16();
|
||||
TargetAddress::Ipv4(octets.into(), port)
|
||||
}
|
||||
ATYP_DOMAIN => {
|
||||
let len = packet.get_u8() as usize;
|
||||
let domain_bytes = packet.split_to(len);
|
||||
let domain = String::from_utf8(domain_bytes.to_vec())
|
||||
.map_err(|_| "Invalid UTF-8 domain".to_string())?;
|
||||
let port = packet.get_u16();
|
||||
TargetAddress::Domain(domain, port)
|
||||
}
|
||||
ATYP_IPV6 => {
|
||||
let octets: [u8; 16] = packet.split_to(16)[..].try_into().unwrap();
|
||||
let port = packet.get_u16();
|
||||
TargetAddress::Ipv6(octets.into(), port)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(Some(SocksTarget { addr }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for SocksRequest {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 2 || bytes[0] != SOCKS5_VERSION {
|
||||
return false;
|
||||
}
|
||||
|
||||
let nmethods = bytes[1] as usize;
|
||||
if bytes.len() >= 2 + nmethods {
|
||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if bytes.len() < 2 || bytes[0] != SOCKS5_VERSION {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||
let command = bytes[1];
|
||||
if let Some(target) = SocksTarget::parse(bytes)? {
|
||||
return Ok(Some(SocksRequest::Connect { command, target }));
|
||||
}
|
||||
}
|
||||
|
||||
let nmethods = bytes[1] as usize;
|
||||
let total_handshake = 2 + nmethods;
|
||||
|
||||
if bytes.len() >= total_handshake {
|
||||
let mut packet = bytes.split_to(total_handshake);
|
||||
packet.advance(2);
|
||||
let mut methods = vec![0u8; nmethods];
|
||||
packet.copy_to_slice(&mut methods);
|
||||
|
||||
return Ok(Some(SocksRequest::Handshake { methods }));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
use crate::{
|
||||
nrxp::{
|
||||
errors::{ErrorAction, ErrorStage, TlsError},
|
||||
parser::Parser,
|
||||
},
|
||||
tlseng::{
|
||||
extension::{Extension, ExtensionStack},
|
||||
handshake::*,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, HelloType, ProtocolVersion},
|
||||
ApplicationData,
|
||||
},
|
||||
utils::u24::{BufExt, U24},
|
||||
};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
impl Parser for TlsRecord {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 5 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let content_type = bytes[0];
|
||||
let is_valid_type = content_type == ContentType::Handshake as u8
|
||||
|| content_type == ContentType::ApplicationData as u8
|
||||
|| content_type == ContentType::Alert as u8;
|
||||
|
||||
if !is_valid_type {
|
||||
return false;
|
||||
}
|
||||
|
||||
let record_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
|
||||
|
||||
if content_type == ContentType::ApplicationData as u8 {
|
||||
if record_len < 17 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bytes.len() >= 5 + record_len
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<TlsRecord>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let raw_content_type = bytes.get_u8();
|
||||
let raw_version = bytes.get_u16();
|
||||
let record_len = bytes.get_u16() as usize;
|
||||
|
||||
let content_type = ContentType::try_from(raw_content_type)
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let version = ProtocolVersion::try_from(raw_version)
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let payload = bytes.split_to(record_len).freeze();
|
||||
|
||||
Ok(Some(TlsRecord::new(content_type, version, payload)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ApplicationData {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
!bytes.is_empty()
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
let _len = bytes.len();
|
||||
if _len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let payload = bytes.split_to(_len).freeze();
|
||||
Ok(Some(Self { _len, payload }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for HelloHeader {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 4 {
|
||||
return false;
|
||||
}
|
||||
bytes[0] == HelloType::Client as u8 || bytes[0] == HelloType::Server as u8
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let raw_type = bytes.get_u8();
|
||||
let header_type = HelloType::try_from(raw_type).map_err(|e| {
|
||||
TlsError::new(ErrorStage::Handshake(e), ErrorAction::Drop, Bytes::new())
|
||||
})?;
|
||||
|
||||
let len = bytes.get_u24();
|
||||
|
||||
Ok(Some(Self {
|
||||
header_type,
|
||||
_len: U24::from_u32(len),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ClientHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut reader = &bytes[..];
|
||||
|
||||
if reader.len() < 35 {
|
||||
return false;
|
||||
}
|
||||
reader.advance(34);
|
||||
|
||||
let sid_len = reader[0] as usize;
|
||||
reader.advance(1);
|
||||
if reader.len() < sid_len + 2 {
|
||||
return false;
|
||||
}
|
||||
reader.advance(sid_len);
|
||||
|
||||
let ciphers_len = u16::from_be_bytes([reader[0], reader[1]]) as usize;
|
||||
reader.advance(2);
|
||||
if reader.len() < ciphers_len + 1 {
|
||||
return false;
|
||||
}
|
||||
reader.advance(ciphers_len);
|
||||
|
||||
let comp_len = reader[0] as usize;
|
||||
reader.advance(1);
|
||||
if reader.len() < comp_len {
|
||||
return false;
|
||||
}
|
||||
reader.advance(comp_len);
|
||||
|
||||
if reader.len() >= 2 {
|
||||
let ext_len = u16::from_be_bytes([reader[0], reader[1]]) as usize;
|
||||
reader.advance(2);
|
||||
if reader.len() < ext_len {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let _version = ProtocolVersion::try_from(bytes.get_u16())
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let mut random = [0u8; 32];
|
||||
bytes.copy_to_slice(&mut random);
|
||||
|
||||
let sid_len = bytes.get_u8() as usize;
|
||||
let session_id = bytes.split_to(sid_len).freeze();
|
||||
|
||||
let c_len = bytes.get_u16() as usize;
|
||||
let mut cipher_suites = Vec::with_capacity(c_len / 2);
|
||||
let mut ciphers_data = bytes.split_to(c_len);
|
||||
while ciphers_data.has_remaining() {
|
||||
cipher_suites.push(ciphers_data.get_u16());
|
||||
}
|
||||
|
||||
let cmp_len = bytes.get_u8() as usize;
|
||||
bytes.advance(cmp_len);
|
||||
|
||||
let extensions = if bytes.remaining() >= 2 {
|
||||
let ext_len = bytes.get_u16() as usize;
|
||||
bytes.split_to(ext_len).freeze()
|
||||
} else {
|
||||
Bytes::new()
|
||||
};
|
||||
|
||||
Ok(Some(Self {
|
||||
_version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suites,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl Parser for ServerHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 34;
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
|
||||
offset += 3;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
bytes.len() >= offset
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
let mut offset = 34;
|
||||
if bytes.len() < offset + 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
|
||||
offset += 3;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
if bytes.len() < offset {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut msg = bytes.split_to(offset);
|
||||
|
||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let mut random = [0u8; 32];
|
||||
msg.copy_to_slice(&mut random);
|
||||
|
||||
let sid_len = msg.get_u8() as usize;
|
||||
let session_id = msg.split_to(sid_len).freeze();
|
||||
|
||||
let cipher_suite = msg.get_u16();
|
||||
msg.advance(1);
|
||||
|
||||
let extensions = if msg.remaining() >= 2 {
|
||||
let ext_len = msg.get_u16() as usize;
|
||||
msg.split_to(ext_len)
|
||||
} else {
|
||||
BytesMut::new()
|
||||
};
|
||||
|
||||
Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suite,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ExtensionStack {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 0;
|
||||
let data_len = bytes.len();
|
||||
|
||||
while offset + 4 <= data_len {
|
||||
let elen = u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]) as usize;
|
||||
offset += 4 + elen;
|
||||
}
|
||||
|
||||
offset <= data_len
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
let mut offset = 0;
|
||||
let data_len = bytes.len();
|
||||
|
||||
while offset + 4 <= data_len {
|
||||
let elen = u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]) as usize;
|
||||
offset += 4 + elen;
|
||||
}
|
||||
|
||||
if offset > data_len {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if offset != data_len {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Malformed extension stack: trailing data"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut extensions = Vec::new();
|
||||
while bytes.remaining() >= 4 {
|
||||
let etype = bytes.get_u16();
|
||||
let elen = bytes.get_u16() as usize;
|
||||
let data = bytes.split_to(elen).freeze();
|
||||
extensions.push(Extension::new(etype, data));
|
||||
}
|
||||
|
||||
Ok(Some(Self { extensions }))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user