mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-08-04 15:18:17 +00:00
parent
5530550ef3
commit
8fc213e699
12 changed files with 172 additions and 206 deletions
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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 },
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")?;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue