refactor(credssp): follow up to #260 (#287)

This commit is contained in:
Benoît Cortier 2023-11-17 09:50:03 -05:00 committed by GitHub
parent 5530550ef3
commit 8fc213e699
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 172 additions and 206 deletions

View file

@ -1,16 +1,14 @@
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::{
credssp_sequence::{CredsspProcessGenerator, CredsspSequence},
custom_err,
sspi::{credssp::ClientState, generator::GeneratorState},
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _,
ServerName, State as _, Written,
custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
Sequence as _, ServerName, State as _,
};
use ironrdp_pdu::write_buf::WriteBuf;
use crate::{
framed::{Framed, FramedRead, FramedWrite},
AsyncNetworkClient,
};
use crate::framed::{Framed, FramedRead, FramedWrite};
use crate::AsyncNetworkClient;
#[non_exhaustive]
pub struct ShouldUpgrade;
@ -50,10 +48,10 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up
pub async fn connect_finalize<S>(
_: Upgraded,
framed: &mut Framed<S>,
mut connector: ClientConnector,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: Option<&mut dyn AsyncNetworkClient>,
mut connector: ClientConnector,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
@ -92,6 +90,7 @@ async fn resolve_generator(
network_client: &mut dyn AsyncNetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
loop {
match state {
GeneratorState::Suspended(request) => {
@ -99,13 +98,14 @@ async fn resolve_generator(
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break Ok(client_state.map_err(|e| custom_err!("cannot resolve generator state", e))?)
break client_state
.map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e)))
}
}
}
}
#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))]
#[instrument(level = "trace", skip_all)]
async fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
@ -119,10 +119,13 @@ where
S: FramedRead + FramedWrite,
{
assert!(connector.should_perform_credssp());
let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?;
while !credssp_sequence.is_done() {
buf.clear();
let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
@ -135,25 +138,23 @@ where
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
Some(pdu.to_vec())
} else {
None
};
if credssp_sequence.wants_request_from_server() {
credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?;
credssp_sequence.read_request_from_server(&pdu)?;
}
let client_state = {
let mut generator = credssp_sequence.process();
if let Some(network_client_ref) = network_client.as_deref_mut() {
trace!("resolving network");
resolve_generator(&mut generator, network_client_ref).await?
} else {
generator
.resolve_to_result()
.map_err(|e| custom_err!(" cannot resolve generator without a network client", e))?
.map_err(|e| custom_err!("resolve without network client", e))?
}
}; // drop generator
let written = credssp_sequence.handle_process_result(client_state, buf)?;
if let Some(response_len) = written.size() {
@ -165,7 +166,9 @@ where
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
connector.mark_credssp_as_done();
Ok(())
}
@ -179,7 +182,7 @@ where
{
buf.clear();
let written: Written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,

View file

@ -1,10 +1,11 @@
use std::io::{Read, Write};
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::sspi::network_client::NetworkClient;
use ironrdp_connector::{
credssp_sequence::{CredsspProcessGenerator, CredsspSequence},
custom_err,
sspi::{credssp::ClientState, generator::GeneratorState, network_client::NetworkClient},
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _,
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence as _,
ServerName, State as _,
};
use ironrdp_pdu::write_buf::WriteBuf;
@ -49,10 +50,10 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up
pub fn connect_finalize<S>(
_: Upgraded,
framed: &mut Framed<S>,
mut connector: ClientConnector,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: &mut impl NetworkClient,
mut connector: ClientConnector,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
@ -89,28 +90,27 @@ where
Ok(result)
}
#[instrument(level = "info", skip(generator, network_client))]
fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut impl NetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
let res = loop {
loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).unwrap();
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break client_state.map_err(|e| custom_err!("failed to resolve generator", e))?
break client_state
.map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e)))
}
}
};
debug!("client state = {:?}", &res);
Ok(res)
}
}
#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))]
#[instrument(level = "trace", skip_all)]
fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
@ -124,10 +124,13 @@ where
S: Read + Write,
{
assert!(connector.should_perform_credssp());
let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?;
while !credssp_sequence.is_done() {
buf.clear();
let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
@ -139,18 +142,15 @@ where
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
Some(pdu.to_vec())
} else {
None
};
if credssp_sequence.wants_request_from_server() {
credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?;
credssp_sequence.read_request_from_server(&pdu)?;
}
let client_state = {
let mut generator = credssp_sequence.process();
resolve_generator(&mut generator, network_client)?
}; // drop generator
let written = credssp_sequence.handle_process_result(client_state, buf)?;
if let Some(response_len) = written.size() {
@ -161,7 +161,9 @@ where
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
connector.mark_credssp_as_done();
Ok(())
}
@ -169,7 +171,7 @@ pub fn single_connect_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
buf: &mut WriteBuf,
) -> ConnectorResult<ironrdp_connector::Written>
) -> ConnectorResult<()>
where
S: Read + Write,
{
@ -201,5 +203,5 @@ where
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
Ok(written)
Ok(())
}

View file

@ -1,17 +1,19 @@
use sspi::{Error, ErrorKind};
use std::future::Future;
use std::net::{IpAddr, Ipv4Addr};
use std::{future::Future, pin::Pin};
use std::pin::Pin;
use ironrdp::connector::{custom_err, ConnectorResult};
use ironrdp_tokio::AsyncNetworkClient;
use reqwest::Client;
use sspi::{Error, ErrorKind};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpStream, UdpSocket};
use ironrdp::connector::{custom_err, general_err, ConnectorResult};
use reqwest::Client;
use url::Url;
use ironrdp_tokio::AsyncNetworkClient;
pub(crate) struct ReqwestNetworkClient {
client: Option<Client>,
}
impl AsyncNetworkClient for ReqwestNetworkClient {
fn send<'a>(
&'a mut self,
@ -38,22 +40,23 @@ impl ReqwestNetworkClient {
impl ReqwestNetworkClient {
async fn send_tcp(&self, url: &Url, data: &[u8]) -> ConnectorResult<Vec<u8>> {
let addr = format!("{}:{}", url.host_str().unwrap_or_default(), url.port().unwrap_or(88));
let mut stream = TcpStream::connect(addr)
.await
.map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e)))
.map_err(|e| custom_err!("sending KDC request over TCP ", e))?;
.map_err(|e| custom_err!("failed to send KDC request over TCP", e))?;
stream
.write(data)
.await
.map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e)))
.map_err(|e| custom_err!("Sending KDC request over TCP ", e))?;
.map_err(|e| custom_err!("failed to send KDC request over TCP", e))?;
let len = stream
.read_u32()
.await
.map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e)))
.map_err(|e| custom_err!("Sending KDC request over TCP ", e))?;
.map_err(|e| custom_err!("failed to send KDC request over TCP", e))?;
let mut buf = vec![0; len as usize + 4];
buf[0..4].copy_from_slice(&(len.to_be_bytes()));
@ -62,7 +65,7 @@ impl ReqwestNetworkClient {
.read_exact(&mut buf[4..])
.await
.map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e)))
.map_err(|e| custom_err!("Sending KDC request over TCP ", e))?;
.map_err(|e| custom_err!("failed to send KDC request over TCP", e))?;
Ok(buf)
}
@ -70,13 +73,14 @@ impl ReqwestNetworkClient {
async fn send_udp(&self, url: &Url, data: &[u8]) -> ConnectorResult<Vec<u8>> {
let udp_socket = UdpSocket::bind((IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.map_err(|e| custom_err!("Cannot bind udp socket", e))?;
.map_err(|e| custom_err!("cannot bind UDP socket", e))?;
let addr = format!("{}:{}", url.host_str().unwrap_or_default(), url.port().unwrap_or(88));
udp_socket
.send_to(data, addr)
.await
.map_err(|e| custom_err!("Error sending udp request", e))?;
.map_err(|e| custom_err!("failed to send UDP request", e))?;
// 48 000 bytes: default maximum token len in Windows
let mut buf = vec![0; 0xbb80];
@ -84,7 +88,7 @@ impl ReqwestNetworkClient {
let n = udp_socket
.recv(&mut buf)
.await
.map_err(|e| custom_err!("Error receiving UDP request", e))?;
.map_err(|e| custom_err!("failed to receive UDP request", e))?;
let mut reply_buf = Vec::with_capacity(n + 4);
reply_buf.extend_from_slice(&(n as u32).to_be_bytes());
@ -94,21 +98,17 @@ impl ReqwestNetworkClient {
}
async fn send_http(&mut self, url: &Url, data: &[u8]) -> ConnectorResult<Vec<u8>> {
if self.client.is_none() {
self.client = Some(Client::new()); // dont drop the cllient, keep-alive
}
let result_bytes = self
.client
.as_ref()
.ok_or_else(|| general_err!("Missing HTTP client, should never happen"))?
let client = self.client.get_or_insert_with(Client::new);
let result_bytes = client
.post(url.clone())
.body(data.to_vec())
.send()
.await
.map_err(|e| custom_err!("Sending KDC request over proxy", e))?
.map_err(|e| custom_err!("failed to send KDC request over proxy", e))?
.bytes()
.await
.map_err(|e| custom_err!("Receving KDC response", e))?
.map_err(|e| custom_err!("failed to receive KDC response", e))?
.to_vec();
Ok(result_bytes)

View file

@ -135,10 +135,10 @@ async fn connect(
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
&mut upgraded_framed,
connector,
(&config.destination).into(),
server_public_key,
Some(&mut network_client),
connector,
None,
)
.await?;

View file

@ -38,7 +38,7 @@ pub enum ClientConnectorState {
EnhancedSecurityUpgrade {
selected_protocol: nego::SecurityProtocol,
},
CredSsp {
Credssp {
selected_protocol: nego::SecurityProtocol,
},
BasicSettingsExchangeSendInitial {
@ -97,7 +97,7 @@ impl State for ClientConnectorState {
Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest",
Self::ConnectionInitiationWaitConfirm => "ConnectionInitiationWaitResponse",
Self::EnhancedSecurityUpgrade { .. } => "EnhancedSecurityUpgrade",
Self::CredSsp { .. } => "CredSsp",
Self::Credssp { .. } => "Credssp",
Self::BasicSettingsExchangeSendInitial { .. } => "BasicSettingsExchangeSendInitial",
Self::BasicSettingsExchangeWaitResponse { .. } => "BasicSettingsExchangeWaitResponse",
Self::ChannelConnection { .. } => "ChannelConnection",
@ -179,7 +179,7 @@ impl ClientConnector {
}
pub fn should_perform_credssp(&self) -> bool {
matches!(self.state, ClientConnectorState::CredSsp { .. })
matches!(self.state, ClientConnectorState::Credssp { .. })
}
pub fn mark_credssp_as_done(&mut self) {
@ -197,7 +197,7 @@ impl Sequence for ClientConnector {
ClientConnectorState::ConnectionInitiationSendRequest => None,
ClientConnectorState::ConnectionInitiationWaitConfirm => Some(&ironrdp_pdu::X224_HINT),
ClientConnectorState::EnhancedSecurityUpgrade { .. } => None,
ClientConnectorState::CredSsp { .. } => None,
ClientConnectorState::Credssp { .. } => None,
ClientConnectorState::BasicSettingsExchangeSendInitial { .. } => None,
ClientConnectorState::BasicSettingsExchangeWaitResponse { .. } => Some(&ironrdp_pdu::X224_HINT),
ClientConnectorState::ChannelConnection { channel_connection, .. } => channel_connection.next_pdu_hint(),
@ -280,7 +280,7 @@ impl Sequence for ClientConnector {
let next_state = if selected_protocol.contains(nego::SecurityProtocol::HYBRID)
|| selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX)
{
ClientConnectorState::CredSsp { selected_protocol }
ClientConnectorState::Credssp { selected_protocol }
} else {
debug!("Skipped CredSSP");
ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }
@ -290,7 +290,7 @@ impl Sequence for ClientConnector {
}
//== CredSSP ==//
ClientConnectorState::CredSsp { selected_protocol } => (
ClientConnectorState::Credssp { selected_protocol } => (
Written::Nothing,
ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol },
),

View file

@ -6,10 +6,37 @@ use sspi::negotiate::ProtocolConfig;
use sspi::Username;
use crate::{
ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorKind, ConnectorResult, KerberosConfig,
ServerName, Written,
ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written,
};
#[derive(Debug, Clone, Default)]
pub struct KerberosConfig {
pub kdc_proxy_url: Option<url::Url>,
pub hostname: Option<String>,
}
impl KerberosConfig {
pub fn new(kdc_proxy_url: Option<String>, hostname: Option<String>) -> ConnectorResult<Self> {
let kdc_proxy_url = kdc_proxy_url
.map(|url| url::Url::parse(&url))
.transpose()
.map_err(|e| custom_err!("invalid KDC URL", e))?;
Ok(Self {
kdc_proxy_url,
hostname,
})
}
}
impl From<KerberosConfig> for sspi::KerberosConfig {
fn from(val: KerberosConfig) -> Self {
sspi::KerberosConfig {
kdc_url: val.kdc_proxy_url,
client_computer_name: val.hostname,
}
}
}
#[derive(Clone, Copy, Debug)]
struct CredsspTsRequestHint;
@ -24,6 +51,7 @@ impl PduHint for CredsspTsRequestHint {
}
}
}
#[derive(Clone, Copy, Debug)]
struct CredsspEarlyUserAuthResultHint;
@ -63,6 +91,7 @@ impl CredsspSequence {
}
}
/// `server_name` must be the actual target server hostname (as opposed to the proxy)
pub fn new(
connector: &ClientConnector,
server_name: ServerName,
@ -76,8 +105,11 @@ impl CredsspSequence {
));
}
let username = Username::new(config.credentials.username(), config.domain.as_deref())
.map_err(|e| custom_err!("invalid username", e))?;
let credentials = sspi::AuthIdentity {
username: Username::parse(config.credentials.username()).map_err(|e| custom_err!("parsing username", e))?,
username,
password: config.credentials.secret().to_owned().into(),
};
@ -91,7 +123,7 @@ impl CredsspSequence {
} else {
credssp_config = Box::<sspi::ntlm::NtlmConfig>::default();
}
info!("using config : {:?}", &credssp_config);
debug!(?credssp_config);
let client = credssp::CredSspClient::new(
server_public_key,
@ -107,15 +139,13 @@ impl CredsspSequence {
.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?;
match connector.state {
ClientConnectorState::CredSsp { selected_protocol } => Ok(Self {
ClientConnectorState::Credssp { selected_protocol } => Ok(Self {
client,
next_request: Some(credssp::TsRequest::default()),
state: CredsspState::CredsspInitial,
selected_protocol,
}),
_ => Err(general_err!(
"Cannot perform cred ssp opeartions when ClientConnector is not in CredSsp state"
)),
_ => Err(general_err!("invalid connector state for CredSSP sequence")),
}
}
@ -123,38 +153,35 @@ impl CredsspSequence {
self.state == CredsspState::Finished
}
pub fn wants_request_from_server(&self) -> bool {
self.next_request.is_none()
}
pub fn read_request_from_server(&mut self, input: &[u8]) -> ConnectorResult<()> {
match self.state {
CredsspState::CredsspInitial | CredsspState::CredsspReplyNeeded => {
info!("read request from server: {:?}", input);
let message = credssp::TsRequest::from_buffer(input)
.map_err(|e| reason_err!("CredSSP", "TsRequest decode: {e}"))?;
CredsspState::CredsspReplyNeeded => {
let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
debug!(?message, "Received");
self.next_request = Some(message);
Ok(())
}
CredsspState::CredsspEarlyUserAuthResult => {
let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input)
.map_err(|e| custom_err!("credssp::EarlyUserAuthResult", e))?;
.map_err(|e| custom_err!("EarlyUserAuthResult", e))?;
debug!(message = ?early_user_auth_result, "Received");
let credssp::EarlyUserAuthResult::Success = early_user_auth_result else {
return Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied));
};
Ok(())
match early_user_auth_result {
credssp::EarlyUserAuthResult::Success => Ok(()),
credssp::EarlyUserAuthResult::AccessDenied => {
Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied))
}
}
}
_ => Err(general_err!("CredSsp Sequence is Finished")),
_ => Err(general_err!(
"attempted to feed server request to CredSSP sequence in an unexpected state"
)),
}
}
pub fn process(&mut self) -> CredsspProcessGenerator<'_> {
let request = self.next_request.take().expect("next request");
info!("Ts request = {:?}", &request);
let request = self.next_request.take().expect("next request"); // FIXME: error handling
self.client.process(request)
}
@ -191,9 +218,11 @@ impl CredsspSequence {
Ok((Written::from_size(written)?, next_state))
}
CredsspState::CredsspEarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
CredsspState::Finished => Err(general_err!("CredSSP Sequence if finished")),
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
}?;
self.state = next_state;
Ok(size)
}
}
@ -205,7 +234,7 @@ fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf)
ts_request
.encode_ts_request(unfilled_buffer)
.map_err(|e| reason_err!("CredSSP", "TsRequest encode: {e}"))?;
.map_err(|e| custom_err!("TsRequest", e))?;
output.advance(length);

View file

@ -12,7 +12,7 @@ pub mod legacy;
mod channel_connection;
mod connection;
mod connection_finalization;
pub mod credssp_sequence;
pub mod credssp;
mod license_exchange;
mod server_name;
@ -100,34 +100,6 @@ pub struct Config {
pub autologon: bool,
}
#[derive(Debug, Clone, Default)]
pub struct KerberosConfig {
pub kdc_proxy_url: Option<url::Url>,
pub hostname: Option<String>,
}
impl KerberosConfig {
pub fn new(kdc_proxy_url: Option<String>, hostname: Option<String>) -> ConnectorResult<Self> {
let kdc_proxy_url = kdc_proxy_url
.map(|url| url::Url::parse(&url))
.transpose()
.map_err(|e| custom_err!("invalid KDC URL", e))?;
Ok(Self {
kdc_proxy_url,
hostname,
})
}
}
impl From<KerberosConfig> for sspi::KerberosConfig {
fn from(val: KerberosConfig) -> Self {
sspi::KerberosConfig {
kdc_url: val.kdc_proxy_url,
client_computer_name: val.hostname,
}
}
}
ironrdp_pdu::assert_impl!(Config: Send, Sync);
pub trait State: Send + core::fmt::Debug + 'static {

View file

@ -32,7 +32,7 @@ ironrdp-rdcleanpath.workspace = true
# WASM
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["HtmlCanvasElement", "ReadableStreamDefaultReader"] }
web-sys = { version = "0.3", features = ["HtmlCanvasElement"] }
js-sys = "0.3"
gloo-net = { version = "0.4", default-features = false, features = ["websocket", "http"] }
gloo-timers = { version = "0.3", default-features = false, features = ["futures"] }
@ -66,4 +66,4 @@ smallvec = "1.11"
x509-cert = { version = "0.2", default-features = false, features = ["std"] }
tap = "1"
semver = "1"
url = "2.4.1"
url = "2.4"

View file

@ -3,76 +3,39 @@ use std::pin::Pin;
use futures_util::Future;
use ironrdp::connector::sspi::generator::NetworkRequest;
use ironrdp::connector::sspi::network_client::NetworkProtocol;
use ironrdp::connector::{general_err, reason_err, ConnectorResult};
use ironrdp::connector::{custom_err, reason_err, ConnectorResult};
use ironrdp_futures::AsyncNetworkClient;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::ReadableStream;
#[derive(Debug)]
pub(crate) struct WasmNetworkClient;
impl AsyncNetworkClient for WasmNetworkClient {
fn send<'a>(
&'a mut self,
network_request: &'a NetworkRequest,
) -> Pin<Box<dyn Future<Output = ConnectorResult<Vec<u8>>> + 'a>> {
Box::pin(async move {
trace!("network requwest = {:?}", &network_request);
debug!(?network_request.protocol, ?network_request.url);
match &network_request.protocol {
NetworkProtocol::Http | NetworkProtocol::Https => {
let body = js_sys::Uint8Array::from(&network_request.data[..]);
let stream = gloo_net::http::Request::post(network_request.url.as_str())
let response = gloo_net::http::Request::post(network_request.url.as_str())
.header("keep-alive", "true")
.body(body)
.map_err(|e| reason_err!("Error send KDC request", "{}", e))?
.map_err(|e| custom_err!("failed to send KDC request", e))?
.send()
.await
.map_err(|e| reason_err!("Error send KDC request", "{}", e))?
.body()
.ok_or_else(|| general_err!("No body in response"))?;
let res = read_stream(stream).await?;
.map_err(|e| custom_err!("failed to send KDC request", e))?
.binary()
.await
.map_err(|e| custom_err!("failed to retrieve HTTP response", e))?;
Ok(res)
Ok(response)
}
_ => Err(general_err!("KDC Url must always start with HTTP/HTTPS for Web")),
unsupported => Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")),
}
})
}
}
impl WasmNetworkClient {
pub(crate) fn new() -> Self {
Self
}
}
async fn read_stream(stream: ReadableStream) -> ConnectorResult<Vec<u8>> {
let mut bytes = Vec::new();
let reader = web_sys::ReadableStreamDefaultReader::new(&stream).map_err(|_| general_err!("error create reader"))?;
loop {
let result = JsFuture::from(reader.read())
.await
.map_err(|_e| general_err!("error read stream"))?;
// Cast the result into an object and check if the stream is done
let result_obj = result.dyn_into::<js_sys::Object>().unwrap();
let done = js_sys::Reflect::get(&result_obj, &"done".into())
.map_err(|_| general_err!("error read stream"))?
.as_bool()
.ok_or_else(|| general_err!("error resolve reader promise property: done"))?;
if done {
break;
}
// Extract the value (the chunk) from the result
let value = js_sys::Reflect::get(&result_obj, &"value".into()).unwrap();
// Convert value to Uint8Array, then to Vec<u8> and append to bytes
let chunk = js_sys::Uint8Array::new(&value);
bytes.extend_from_slice(&chunk.to_vec());
}
Ok(bytes)
}

View file

@ -10,7 +10,8 @@ use gloo_net::websocket;
use gloo_net::websocket::futures::WebSocket;
use ironrdp::cliprdr::backend::ClipboardMessage;
use ironrdp::cliprdr::Cliprdr;
use ironrdp::connector::{self, ClientConnector, Credentials, KerberosConfig};
use ironrdp::connector::credssp::KerberosConfig;
use ironrdp::connector::{self, ClientConnector, Credentials};
use ironrdp::graphics::image_processing::PixelFormat;
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
use ironrdp::pdu::write_buf::WriteBuf;
@ -140,6 +141,12 @@ impl SessionBuilder {
self.clone()
}
/// Optional
pub fn kdc_proxy_url(&self, kdc_proxy_url: Option<String>) -> SessionBuilder {
self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url;
self.clone()
}
/// Optional
pub fn desktop_size(&self, desktop_size: DesktopSize) -> SessionBuilder {
self.0.borrow_mut().desktop_size = desktop_size;
@ -176,12 +183,6 @@ impl SessionBuilder {
self.clone()
}
/// Optional
pub fn kdc_proxy_url(&self, kdc_proxy_url: Option<String>) -> SessionBuilder {
self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url;
self.clone()
}
/// Optional
pub fn remote_clipboard_changed_callback(&self, callback: js_sys::Function) -> SessionBuilder {
self.0.borrow_mut().remote_clipboard_changed_callback = Some(callback);
@ -209,6 +210,7 @@ impl SessionBuilder {
proxy_address,
auth_token,
pcb,
kdc_proxy_url,
client_name,
desktop_size,
render_canvas,
@ -216,7 +218,6 @@ impl SessionBuilder {
hide_pointer_callback_context,
show_pointer_callback,
show_pointer_callback_context,
kdc_proxy_url,
remote_clipboard_changed_callback,
remote_received_format_list_callback,
force_clipboard_update_callback,
@ -224,6 +225,7 @@ impl SessionBuilder {
{
let inner = self.0.borrow();
username = inner.username.clone().context("username missing")?;
destination = inner.destination.clone().context("destination missing")?;
server_domain = inner.server_domain.clone();
@ -231,11 +233,10 @@ impl SessionBuilder {
proxy_address = inner.proxy_address.clone().context("proxy_address missing")?;
auth_token = inner.auth_token.clone().context("auth_token missing")?;
pcb = inner.pcb.clone();
kdc_proxy_url = inner.kdc_proxy_url.clone();
client_name = inner.client_name.clone();
desktop_size = inner.desktop_size.clone();
kdc_proxy_url = inner.kdc_proxy_url.clone();
render_canvas = inner.render_canvas.clone().context("render_canvas missing")?;
hide_pointer_callback = inner
@ -310,8 +311,8 @@ impl SessionBuilder {
auth_token,
destination,
pcb,
clipboard.as_ref().map(|clip| clip.backend()),
kdc_proxy_url,
clipboard.as_ref().map(|clip| clip.backend()),
)
.await?;
@ -651,8 +652,8 @@ async fn connect(
proxy_auth_token: String,
destination: String,
pcb: Option<String>,
clipboard_backend: Option<WasmClipboardBackend>,
kdc_proxy_url: Option<String>,
clipboard_backend: Option<WasmClipboardBackend>,
) -> Result<(connector::ConnectionResult, WebSocketCompat), IronRdpError> {
let mut framed = ironrdp_futures::SingleThreadedFuturesFramed::new(ws);
@ -665,17 +666,13 @@ async fn connect(
let (upgraded, server_public_key) =
connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?;
info!("kdc url = {:?}", &kdc_proxy_url);
let mut network_client = WasmNetworkClient::new();
let connection_result = ironrdp_futures::connect_finalize(
upgraded,
&mut framed,
connector,
(&destination).into(),
server_public_key,
Some(&mut network_client),
connector,
Some(&mut WasmNetworkClient),
url::Url::parse(kdc_proxy_url.unwrap_or_default().as_str()) // if kdc_proxy_url does not exit, give url parser a empty string, it will fail anyway and map to a None
.ok()
.map(|url| KerberosConfig {

View file

@ -258,10 +258,10 @@ fn connect(
let connection_result = ironrdp_blocking::connect_finalize(
upgraded,
&mut upgraded_framed,
connector,
server_name.into(),
server_public_key,
&mut network_client,
connector,
None,
)
.context("finalize connection")?;

View file

@ -54,16 +54,16 @@ You can add some parameters for default initialization on the component `<iron-r
```ts
connect(
username: string,
password: string,
destination: string,
proxyAddress: string,
serverDomain: string,
authToken: string,
desktopSize?: DesktopSize,
preConnectionBlob?: string,
kdc_proxy_url?: string,
): Observable<NewSessionInfo>;
username: string,
password: string,
destination: string,
proxyAddress: string,
serverDomain: string,
authToken: string,
desktopSize?: DesktopSize,
preConnectionBlob?: string,
kdc_proxy_url?: string,
): Observable<NewSessionInfo>;
```
> `username` and `password` are the credentials to use on the remote host.