Initial support digest auth scheme

This commit is contained in:
Jorge Alejandro Jimenez Luna 2023-06-22 13:09:36 -04:00
parent 6767076a6b
commit 86429ee8eb
No known key found for this signature in database
GPG key ID: 58F10568E03E6001
6 changed files with 321 additions and 67 deletions

View file

@ -23,6 +23,9 @@ prctl = "1.0"
smoltcp = { version = "0.9.1", git = "https://github.com/smoltcp-rs/smoltcp", features = ["std", "phy-tuntap_interface"] } smoltcp = { version = "0.9.1", git = "https://github.com/smoltcp-rs/smoltcp", features = ["std", "phy-tuntap_interface"] }
thiserror = "1.0" thiserror = "1.0"
url = "2.3" url = "2.3"
digest_auth = "0.3.1"
httparse = "1.8.0"
unicase = "2.6.0"
[target.'cfg(target_os="android")'.dependencies] [target.'cfg(target_os="android")'.dependencies]
android_logger = "0.13" android_logger = "0.13"

View file

@ -48,6 +48,12 @@ pub enum Error {
#[error("std::num::ParseIntError {0:?}")] #[error("std::num::ParseIntError {0:?}")]
IntParseError(#[from] std::num::ParseIntError), IntParseError(#[from] std::num::ParseIntError),
#[error("httparse::Error {0}")]
HttpError(#[from] httparse::Error),
#[error("digest_auth::Error {0}")]
DigestAuthError(#[from] digest_auth::Error),
} }
impl From<&str> for Error { impl From<&str> for Error {

View file

@ -1,24 +1,41 @@
use crate::error::Error; use crate::error::Error;
use crate::tun2proxy::{ use crate::tun2proxy::{
Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, Connection, ConnectionManager, Destination, Direction, IncomingDataEvent, IncomingDirection,
OutgoingDataEvent, OutgoingDirection, TcpProxy, OutgoingDataEvent, OutgoingDirection, TcpProxy,
}; };
use crate::Credentials; use crate::Credentials;
use base64::Engine; use base64::Engine;
use httparse::Response;
use smoltcp::wire::IpProtocol; use smoltcp::wire::IpProtocol;
use std::collections::VecDeque; use std::cell::RefCell;
use std::collections::hash_map::RandomState;
use std::collections::{HashMap, VecDeque};
use std::iter::FromIterator;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
use std::str;
use unicase::UniCase;
#[derive(Eq, PartialEq, Debug)]
#[allow(dead_code)]
enum AuthenticationScheme {
None,
Basic,
Digest,
}
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
enum HttpState { enum HttpState {
SendRequest, SendRequest,
ExpectStatusCode, ExpectResponseHeaders,
ExpectResponse, ExpectResponse,
Reset,
Established, Established,
} }
pub(crate) type DigestState = digest_auth::WwwAuthenticateHeader;
pub struct HttpConnection { pub struct HttpConnection {
state: HttpState, state: HttpState,
client_inbuf: VecDeque<u8>, client_inbuf: VecDeque<u8>,
@ -27,82 +44,264 @@ pub struct HttpConnection {
server_outbuf: VecDeque<u8>, server_outbuf: VecDeque<u8>,
data_buf: VecDeque<u8>, data_buf: VecDeque<u8>,
crlf_state: u8, crlf_state: u8,
counter: usize,
skip: usize,
digest_state: Rc<RefCell<Option<DigestState>>>,
before: bool,
credentials: Option<Credentials>,
destination: Destination,
} }
impl HttpConnection { static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate";
fn new(connection: &Connection, manager: Rc<dyn ConnectionManager>) -> Self { static PROXY_AUTHORIZATION: &str = "Proxy-Authorization";
let mut server_outbuf: VecDeque<u8> = VecDeque::new(); static CONNECTION: &str = "Connection";
{ static TRANSFER_ENCODING: &str = "Transfer-Encoding";
let credentials = manager.get_credentials(); static CONTENT_LENGTH: &str = "Content-Length";
server_outbuf.extend(b"CONNECT ".iter());
server_outbuf.extend(connection.dst.to_string().as_bytes());
server_outbuf.extend(b" HTTP/1.1\r\nHost: ".iter());
server_outbuf.extend(connection.dst.to_string().as_bytes());
server_outbuf.extend(b"\r\n".iter());
if let Some(credentials) = credentials {
server_outbuf.extend(b"Proxy-Authorization: Basic ");
let mut auth_plain = credentials.username.clone();
auth_plain.extend(b":".iter());
auth_plain.extend(&credentials.password);
let auth_b64 = base64::engine::general_purpose::STANDARD.encode(auth_plain);
server_outbuf.extend(auth_b64.as_bytes().iter());
server_outbuf.extend(b"\r\n".iter());
}
server_outbuf.extend(b"\r\n".iter());
}
Self { impl HttpConnection {
state: HttpState::ExpectStatusCode, fn new(
connection: &Connection,
manager: Rc<dyn ConnectionManager>,
digest_state: Rc<RefCell<Option<DigestState>>>,
) -> Result<Self, Error> {
let mut res = Self {
state: HttpState::ExpectResponseHeaders,
client_inbuf: Default::default(), client_inbuf: Default::default(),
server_inbuf: Default::default(), server_inbuf: Default::default(),
client_outbuf: Default::default(), client_outbuf: Default::default(),
server_outbuf, server_outbuf: Default::default(),
data_buf: Default::default(), data_buf: Default::default(),
crlf_state: Default::default(), skip: 0,
counter: 0,
crlf_state: 0,
digest_state,
before: false,
credentials: manager.get_credentials().clone(),
destination: connection.dst.clone(),
};
res.send_tunnel_request()?;
Ok(res)
}
fn send_tunnel_request(&mut self) -> Result<(), Error> {
self.server_outbuf.extend(b"CONNECT ");
self.server_outbuf
.extend(self.destination.to_string().as_bytes());
self.server_outbuf.extend(b" HTTP/1.1\r\nHost: ");
self.server_outbuf
.extend(self.destination.to_string().as_bytes());
self.server_outbuf.extend(b"\r\n");
self.send_auth_data(if self.digest_state.borrow().is_none() {
AuthenticationScheme::Basic
} else {
AuthenticationScheme::Digest
})?;
self.server_outbuf.extend(b"\r\n");
Ok(())
}
fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<(), Error> {
let Some(credentials) = &self.credentials else {
return Ok(());
};
match scheme {
AuthenticationScheme::Digest => {
let uri = format!("{}:{}", self.destination.host, self.destination.port);
let context = digest_auth::AuthContext::new_with_method(
&credentials.username,
&credentials.password,
&uri,
Option::<&'_ [u8]>::None,
digest_auth::HttpMethod::CONNECT,
);
let mut state = self.digest_state.borrow_mut();
let response = state.as_mut().unwrap().respond(&context)?;
self.server_outbuf.extend(
format!(
"{}: {}\r\n",
PROXY_AUTHORIZATION,
response.to_header_string()
)
.as_bytes(),
);
}
AuthenticationScheme::Basic => {
let cred = format!("{}:{}", credentials.username, credentials.password);
let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred);
self.server_outbuf
.extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes());
}
AuthenticationScheme::None => {}
} }
Ok(())
} }
fn state_change(&mut self) -> Result<(), Error> { fn state_change(&mut self) -> Result<(), Error> {
let http_len = "HTTP/1.1 200".len();
match self.state { match self.state {
HttpState::ExpectStatusCode if self.server_inbuf.len() > http_len => { HttpState::ExpectResponseHeaders => {
let status_line: Vec<u8> = while self.counter < self.server_inbuf.len() {
self.server_inbuf.range(0..http_len + 1).copied().collect(); let b = self.server_inbuf[self.counter];
let slice = &status_line.as_slice()[0.."HTTP/1.1 2".len()];
if slice != b"HTTP/1.1 2" && slice != b"HTTP/1.0 2"
|| self.server_inbuf[http_len] != b' '
{
let status_str = String::from_utf8_lossy(&status_line.as_slice()[0..http_len]);
let e =
format!("Expected success status code. Server replied with {status_str}.");
return Err(e.into());
}
self.state = HttpState::ExpectResponse;
return self.state_change();
}
HttpState::ExpectResponse => {
let mut counter = 0usize;
for b_ref in self.server_inbuf.iter() {
let b = *b_ref;
if b == b'\n' { if b == b'\n' {
self.crlf_state += 1; self.crlf_state += 1;
} else if b != b'\r' { } else if b != b'\r' {
self.crlf_state = 0; self.crlf_state = 0;
} }
counter += 1;
self.counter += 1;
if self.crlf_state == 2 { if self.crlf_state == 2 {
self.server_inbuf.drain(0..counter); break;
self.server_outbuf.append(&mut self.data_buf);
self.data_buf.clear();
self.state = HttpState::Established;
return self.state_change();
} }
} }
self.server_inbuf.drain(0..counter); if self.crlf_state != 2 {
// Waiting for the end of the headers yet
return Ok(());
}
self.counter = 0;
self.crlf_state = 0;
let mut headers = [httparse::EMPTY_HEADER; 16];
let mut res = Response::new(&mut headers);
// First make the buffer contiguous
let slice = self.server_inbuf.make_contiguous();
let status = res.parse(slice)?;
if status.is_partial() {
// TODO: Optimize in order to detect 200
return Ok(());
}
let len = status.unwrap();
let status_code = res.code.unwrap();
let version = res.version.unwrap();
if status_code == 200 {
// Connection successful
self.state = HttpState::Established;
self.server_inbuf.clear();
self.server_outbuf.append(&mut self.data_buf);
self.data_buf.clear();
return self.state_change();
}
if status_code != 407 {
let e =
format!("Expected success status code. Server replied with {status_code} [Reason: {}].", res.reason.unwrap());
return Err(e.into());
}
let headers_map: HashMap<UniCase<&str>, &[u8], RandomState> =
HashMap::from_iter(headers.map(|x| (UniCase::new(x.name), x.value)));
let Some(auth_data) = headers_map.get(&UniCase::new(PROXY_AUTHENTICATE)) else {
return Err("Proxy requires auth but doesn't send it datails".into());
};
if !auth_data[..6].eq_ignore_ascii_case(b"digest") {
// Fail to auth and the scheme isn't in the
// supported auth method schemes
return Err("Bad credentials".into());
}
// Analize challenge params
let data = str::from_utf8(auth_data)?;
let state = digest_auth::parse(data)?;
if self.before && !state.stale {
return Err("Bad credentials".into());
}
// Update the digest state
self.digest_state.replace(Some(state));
self.before = true;
let closed = match headers_map.get(&UniCase::new(CONNECTION)) {
Some(conn_header) => conn_header.eq_ignore_ascii_case(b"close"),
None => false,
};
if closed || version == 0 {
// Close mio stream connection and reset it
// Reset all the buffers
self.server_inbuf.clear();
self.server_outbuf.clear();
self.send_tunnel_request()?;
self.state = HttpState::Reset;
return Ok(());
}
// The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must
// compute the lenght of the response in order to detect the next frame (response)
// [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length)
// Transfer-Encoding isn't supported yet
if let Some(_) = headers_map.get(&UniCase::new(TRANSFER_ENCODING)) {
unimplemented!("Header Transfer-Encoding not supported");
}
let content_length = match headers_map.get(&UniCase::new(CONTENT_LENGTH)) {
Some(v) => {
let value = str::from_utf8(v)?;
// https://www.rfc-editor.org/rfc/rfc9110#section-5.6.1
match value.parse::<usize>() {
Ok(x) => x,
Err(_) => {
let mut it = value.split(',').map(|x| x.parse::<usize>());
let f = it.next().unwrap()?;
for k in it {
if k? != f {
return Err("Malformed response".into());
}
}
f
}
}
}
None => {
// Close the connection by information miss
self.server_inbuf.clear();
self.server_outbuf.clear();
self.send_tunnel_request()?;
self.state = HttpState::Reset;
return Ok(());
}
};
// Handshake state
self.state = HttpState::ExpectResponse;
self.skip = content_length + len;
return self.state_change();
}
HttpState::ExpectResponse => {
if self.skip > 0 {
let cnt = self.skip.min(self.server_inbuf.len());
self.server_inbuf.drain(..cnt);
self.skip -= cnt;
}
if self.skip == 0 {
// Expected to the server_inbuff to be empty
// self.server_outbuf.append(&mut self.data_buf);
// self.data_buf.clear();
self.send_tunnel_request()?;
self.state = HttpState::ExpectResponseHeaders;
return self.state_change();
}
} }
HttpState::Established => { HttpState::Established => {
self.client_outbuf.extend(self.server_inbuf.iter()); self.client_outbuf.extend(self.server_inbuf.iter());
@ -110,6 +309,9 @@ impl HttpConnection {
self.server_inbuf.clear(); self.server_inbuf.clear();
self.client_inbuf.clear(); self.client_inbuf.clear();
} }
HttpState::Reset => {
self.state = HttpState::ExpectResponseHeaders;
}
_ => {} _ => {}
} }
Ok(()) Ok(())
@ -175,11 +377,16 @@ impl TcpProxy for HttpConnection {
}, },
} }
} }
fn reset_connection(&self) -> bool {
self.state == HttpState::Reset
}
} }
pub(crate) struct HttpManager { pub(crate) struct HttpManager {
server: SocketAddr, server: SocketAddr,
credentials: Option<Credentials>, credentials: Option<Credentials>,
digest_state: Rc<RefCell<Option<DigestState>>>,
} }
impl ConnectionManager for HttpManager { impl ConnectionManager for HttpManager {
@ -195,7 +402,11 @@ impl ConnectionManager for HttpManager {
if connection.proto != IpProtocol::Tcp { if connection.proto != IpProtocol::Tcp {
return Ok(None); return Ok(None);
} }
Ok(Some(Box::new(HttpConnection::new(connection, manager)))) Ok(Some(Box::new(HttpConnection::new(
connection,
manager,
self.digest_state.clone(),
)?)))
} }
fn close_connection(&self, _: &Connection) {} fn close_connection(&self, _: &Connection) {}
@ -214,6 +425,7 @@ impl HttpManager {
Rc::new(Self { Rc::new(Self {
server, server,
credentials, credentials,
digest_state: Rc::new(RefCell::new(None)),
}) })
} }
} }

View file

@ -110,15 +110,15 @@ impl Options {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct Credentials { pub struct Credentials {
pub(crate) username: Vec<u8>, pub(crate) username: String,
pub(crate) password: Vec<u8>, pub(crate) password: String,
} }
impl Credentials { impl Credentials {
pub fn new(username: &str, password: &str) -> Self { pub fn new(username: &str, password: &str) -> Self {
Self { Self {
username: username.as_bytes().to_vec(), username: String::from(username),
password: password.as_bytes().to_vec(), password: String::from(password),
} }
} }
} }

View file

@ -156,10 +156,10 @@ impl SocksConnection {
} }
self.server_outbuf.extend(ip_vec); self.server_outbuf.extend(ip_vec);
if let Some(credentials) = credentials { if let Some(credentials) = credentials {
self.server_outbuf.extend(&credentials.username); self.server_outbuf.extend(credentials.username.as_bytes());
if !credentials.password.is_empty() { if !credentials.password.is_empty() {
self.server_outbuf.push_back(b':'); self.server_outbuf.push_back(b':');
self.server_outbuf.extend(&credentials.password); self.server_outbuf.extend(credentials.password.as_bytes());
} }
} }
self.server_outbuf.push_back(0); self.server_outbuf.push_back(0);
@ -250,10 +250,10 @@ impl SocksConnection {
let credentials = self.credentials.as_ref().unwrap_or(&tmp); let credentials = self.credentials.as_ref().unwrap_or(&tmp);
self.server_outbuf self.server_outbuf
.extend(&[1u8, credentials.username.len() as u8]); .extend(&[1u8, credentials.username.len() as u8]);
self.server_outbuf.extend(&credentials.username); self.server_outbuf.extend(credentials.username.as_bytes());
self.server_outbuf self.server_outbuf
.extend(&[credentials.password.len() as u8]); .extend(&[credentials.password.len() as u8]);
self.server_outbuf.extend(&credentials.password); self.server_outbuf.extend(credentials.password.as_bytes());
self.state = SocksState::ReceiveAuthResponse; self.state = SocksState::ReceiveAuthResponse;
self.state_change() self.state_change()
} }
@ -424,6 +424,10 @@ impl TcpProxy for SocksConnection {
}, },
} }
} }
fn reset_connection(&self) -> bool {
false
}
} }
pub struct SocksManager { pub struct SocksManager {

View file

@ -228,6 +228,7 @@ pub(crate) trait TcpProxy {
fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent;
fn connection_established(&self) -> bool; fn connection_established(&self) -> bool;
fn have_data(&mut self, dir: Direction) -> bool; fn have_data(&mut self, dir: Direction) -> bool;
fn reset_connection(&self) -> bool;
} }
pub(crate) trait ConnectionManager { pub(crate) trait ConnectionManager {
@ -291,7 +292,7 @@ impl<'a> TunToProxy<'a> {
let mut virt = VirtualTunDevice::new(tun.capabilities()); let mut virt = VirtualTunDevice::new(tun.capabilities());
let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?; let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?;
let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?; let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?;
let mut iface = Interface::new(config, &mut virt); let mut iface = Interface::new(config, &mut virt, smoltcp::time::Instant::now());
iface.update_ip_addrs(|ip_addrs| { iface.update_ip_addrs(|ip_addrs| {
ip_addrs.push(IpCidr::new(gateway4.into(), 0)).unwrap(); ip_addrs.push(IpCidr::new(gateway4.into(), 0)).unwrap();
ip_addrs.push(IpCidr::new(gateway6.into(), 0)).unwrap() ip_addrs.push(IpCidr::new(gateway6.into(), 0)).unwrap()
@ -699,6 +700,10 @@ impl<'a> TunToProxy<'a> {
return Ok(()); return Ok(());
} }
let connection = conn_ref.unwrap().clone(); let connection = conn_ref.unwrap().clone();
let server = self
.get_connection_manager(&connection)
.unwrap()
.get_server();
(|| -> Result<(), Error> { (|| -> Result<(), Error> {
if event.is_readable() || event.is_read_closed() { if event.is_readable() || event.is_read_closed() {
@ -737,6 +742,30 @@ impl<'a> TunToProxy<'a> {
return Ok(()); return Ok(());
} }
// The handler request for reset the server connection
if state.handler.reset_connection() {
// Closes the connection with the proxy
state.mio_stream.shutdown(Both)?;
info!("RESETED {}", connection);
// TODO: Improve the call upstairs
state.mio_stream = TcpStream::connect(server)?;
_ = self.poll.registry().deregister(&mut state.mio_stream);
self.poll.registry().register(
&mut state.mio_stream,
state.token,
Interest::WRITABLE,
)?;
state.wait_read = true;
state.wait_write = true;
state.close_state = 0;
return Ok(());
}
if read == 0 || event.is_read_closed() { if read == 0 || event.is_read_closed() {
state.wait_read = false; state.wait_read = false;
state.close_state |= SERVER_WRITE_CLOSED; state.close_state |= SERVER_WRITE_CLOSED;