This commit is contained in:
irvingouj@Devolutions 2025-07-05 22:55:38 +00:00 committed by GitHub
commit f99fd85994
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1207 additions and 365 deletions

15
Cargo.lock generated
View file

@ -2398,6 +2398,7 @@ dependencies = [
"ironrdp-connector",
"ironrdp-core",
"ironrdp-pdu",
"ironrdp-vmconnect",
"tracing",
]
@ -2438,6 +2439,7 @@ dependencies = [
"ironrdp-rdpsnd-native",
"ironrdp-tls",
"ironrdp-tokio",
"ironrdp-vmconnect",
"proc-exit",
"raw-window-handle",
"semver",
@ -2815,6 +2817,18 @@ dependencies = [
"url",
]
[[package]]
name = "ironrdp-vmconnect"
version = "0.1.0"
dependencies = [
"arbitrary",
"ironrdp-connector",
"ironrdp-core",
"ironrdp-pdu",
"sspi",
"tracing",
]
[[package]]
name = "ironrdp-web"
version = "0.0.0"
@ -2833,6 +2847,7 @@ dependencies = [
"ironrdp-core",
"ironrdp-futures",
"ironrdp-rdcleanpath",
"ironrdp-vmconnect",
"js-sys",
"png",
"resize",

View file

@ -17,6 +17,7 @@ test = false
[dependencies]
ironrdp-connector = { path = "../ironrdp-connector", version = "0.5" } # public
ironrdp-vmconnect.path = "../ironrdp-vmconnect" # public
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5" } # public
tracing = { version = "0.1", features = ["log"] }

View file

@ -1,9 +1,9 @@
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::{
custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
ServerName, State as _,
custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError,
ConnectorResult, SecurityConnector, ServerName,
};
use ironrdp_core::WriteBuf;
@ -14,7 +14,10 @@ use crate::{single_sequence_step, AsyncNetworkClient};
pub struct ShouldUpgrade;
#[instrument(skip_all)]
pub async fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
pub async fn connect_begin<S>(
framed: &mut Framed<S>,
connector: &mut dyn ConnectorCore,
) -> ConnectorResult<ShouldUpgrade>
where
S: Sync + FramedRead + FramedWrite,
{
@ -29,7 +32,7 @@ where
Ok(ShouldUpgrade)
}
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
pub fn skip_connect_begin(connector: &dyn SecurityConnector) -> ShouldUpgrade {
assert!(connector.should_perform_security_upgrade());
ShouldUpgrade
}
@ -38,22 +41,26 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
pub struct Upgraded;
#[instrument(skip_all)]
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut dyn SecurityConnector) -> Upgraded {
trace!("Marked as upgraded");
connector.mark_security_upgrade_as_done();
Upgraded
}
#[instrument(skip_all)]
pub async fn connect_finalize<S>(
#[non_exhaustive]
pub struct CredSSPFinished {
pub(crate) write_buf: WriteBuf,
}
pub async fn perform_credssp<S>(
_: Upgraded,
connector: &mut dyn ConnectorCore,
framed: &mut Framed<S>,
mut connector: ClientConnector,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: Option<&mut dyn AsyncNetworkClient>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
) -> ConnectorResult<CredSSPFinished>
where
S: FramedRead + FramedWrite,
{
@ -62,7 +69,7 @@ where
if connector.should_perform_credssp() {
perform_credssp_step(
framed,
&mut connector,
connector,
&mut buf,
server_name,
server_public_key,
@ -72,6 +79,19 @@ where
.await?;
}
Ok(CredSSPFinished { write_buf: buf })
}
#[instrument(skip_all)]
pub async fn connect_finalize<S>(
CredSSPFinished { write_buf: mut buf }: CredSSPFinished,
framed: &mut Framed<S>,
mut connector: ClientConnector,
) -> ConnectorResult<ConnectionResult>
where
S: FramedRead + FramedWrite,
{
buf.clear();
let result = loop {
single_sequence_step(framed, &mut connector, &mut buf).await?;
@ -108,7 +128,7 @@ async fn resolve_generator(
#[instrument(level = "trace", skip_all)]
async fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
connector: &mut dyn ConnectorCore,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
@ -120,70 +140,70 @@ where
{
assert!(connector.should_perform_credssp());
let selected_protocol = match connector.state {
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
};
let selected_protocol = connector
.selected_protocol()
.ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?;
let (mut sequence, mut ts_request) = CredsspSequence::init(
connector.config.credentials.clone(),
connector.config.domain.as_deref(),
selected_protocol,
server_name,
server_public_key,
kerberos_config,
)?;
{
let (mut sequence, mut ts_request) = connector.init_credssp(
connector.config().credentials.clone(),
connector.config().domain.as_deref(),
selected_protocol,
server_name,
server_public_key,
kerberos_config,
)?;
loop {
let client_state = {
let mut generator = sequence.process_ts_request(ts_request);
loop {
let client_state = {
let mut generator = sequence.process_ts_request(ts_request);
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!("resolve without network client", e))?
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!("resolve without network client", e))?
}
}; // drop generator
buf.clear();
let written = sequence.handle_process_result(client_state, buf)?;
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}; // drop generator
buf.clear();
let written = sequence.handle_process_result(client_state, buf)?;
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
break;
};
if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
debug!(
connector.state = connector.state().name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
break;
};
trace!(length = pdu.len(), "PDU received");
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
trace!(length = pdu.len(), "PDU received");
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
ts_request = next_request;
} else {
break;
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
ts_request = next_request;
} else {
break;
}
}
}
connector.mark_credssp_as_done();
Ok(())

View file

@ -9,6 +9,7 @@ pub use bytes;
mod connector;
mod framed;
mod session;
mod vmconnector;
use core::future::Future;
use core::pin::Pin;
@ -18,7 +19,7 @@ use ironrdp_connector::ConnectorResult;
pub use self::connector::*;
pub use self::framed::*;
// pub use self::session::*;
pub use self::vmconnector::*;
pub trait AsyncNetworkClient {
fn send<'a>(

View file

@ -0,0 +1,56 @@
use crate::{single_sequence_step, CredSSPFinished, Framed, FramedRead, FramedWrite};
use ironrdp_connector::{ClientConnector, ConnectorResult};
use ironrdp_pdu::pcb::PcbVersion;
use ironrdp_vmconnect::VmClientConnector;
#[non_exhaustive]
pub struct PcbSent;
pub async fn send_pcb<S>(framed: &mut Framed<S>, payload: String) -> ConnectorResult<PcbSent>
where
S: Sync + FramedRead + FramedWrite,
{
let pcb_pdu = ironrdp_pdu::pcb::PreconnectionBlob {
id: 0,
version: PcbVersion::V2,
v2_payload: Some(payload),
};
let buf = ironrdp_core::encode_vec(&pcb_pdu)
.map_err(|e| ironrdp_connector::custom_err!("encode PreconnectionBlob PDU", e))?;
framed
.write_all(&buf)
.await
.map_err(|e| ironrdp_connector::custom_err!("write PCB PDU", e))?;
Ok(PcbSent)
}
pub fn mark_pcb_sent_by_rdclean_path() -> PcbSent {
PcbSent
}
pub fn vm_connector_take_over(_: PcbSent, connector: ClientConnector) -> ConnectorResult<VmClientConnector> {
VmClientConnector::take_over(connector)
}
pub async fn run_until_handover(
credssp_finished: &mut CredSSPFinished,
framed: &mut Framed<impl FramedRead + FramedWrite>,
mut connector: VmClientConnector,
) -> ConnectorResult<ClientConnector> {
let result = loop {
single_sequence_step(framed, &mut connector, &mut credssp_finished.write_buf).await?;
if connector.should_hand_over() {
break connector.hand_over();
}
};
info!("Handover to client connector");
credssp_finished.write_buf.clear();
Ok(result)
}

View file

@ -1,12 +1,12 @@
use std::io::{Read, Write};
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig};
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::sspi::network_client::NetworkClient;
use ironrdp_connector::{
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
Sequence as _, ServerName, State as _,
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError,
ConnectorResult, SecurityConnector, Sequence, ServerName,
};
use ironrdp_core::WriteBuf;
@ -16,7 +16,7 @@ use crate::framed::Framed;
pub struct ShouldUpgrade;
#[instrument(skip_all)]
pub fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
pub fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut dyn ConnectorCore) -> ConnectorResult<ShouldUpgrade>
where
S: Sync + Read + Write,
{
@ -31,7 +31,7 @@ where
Ok(ShouldUpgrade)
}
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
pub fn skip_connect_begin(connector: &mut dyn SecurityConnector) -> ShouldUpgrade {
assert!(connector.should_perform_security_upgrade());
ShouldUpgrade
}
@ -113,7 +113,7 @@ fn resolve_generator(
#[instrument(level = "trace", skip_all)]
fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
connector: &mut dyn ConnectorCore,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
@ -125,14 +125,13 @@ where
{
assert!(connector.should_perform_credssp());
let selected_protocol = match connector.state {
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
};
let selected_protocol = connector
.selected_protocol()
.ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?;
let (mut sequence, mut ts_request) = CredsspSequence::init(
connector.config.credentials.clone(),
connector.config.domain.as_deref(),
let (mut sequence, mut ts_request) = connector.init_credssp(
connector.config().credentials.clone(),
connector.config().domain.as_deref(),
selected_protocol,
server_name,
server_public_key,
@ -161,7 +160,7 @@ where
};
debug!(
connector.state = connector.state.name(),
connector.state = connector.state().name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);
@ -186,7 +185,7 @@ where
pub fn single_sequence_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
connector: &mut dyn Sequence,
buf: &mut WriteBuf,
) -> ConnectorResult<()>
where
@ -196,7 +195,7 @@ where
let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
connector.state = connector.state().name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);

View file

@ -49,6 +49,7 @@ ironrdp-tls = { path = "../ironrdp-tls", version = "0.1" }
ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.5", features = ["reqwest"] }
ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath"
ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy"
ironrdp-vmconnect.path = "../ironrdp-vmconnect"
# Windowing and rendering
winit = { version = "0.30", features = ["rwh_06"] }

View file

@ -28,6 +28,8 @@ pub struct Config {
/// server, which will be used for proxying DVC messages to/from user-defined DVC logic
/// implemented as named pipe clients (either in the same process or in a different process).
pub dvc_pipe_proxies: Vec<DvcProxyInfo>,
pub pcb: Option<PreconnectionBlobPayload>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@ -280,6 +282,36 @@ struct Args {
/// e.g. PipeName will automatically be prefixed with `\\.\pipe\` on Windows.
#[clap(long)]
dvc_proxy: Vec<DvcProxyInfo>,
/// The ID for the HyperV VM server to connect to
#[clap(long, conflicts_with("pcb"))]
vmconnect: Option<uuid::Uuid>,
/// Preconnection Blob payload to use
#[clap(long)]
pcb: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PreconnectionBlobPayload {
General(String),
VmConnect(String),
}
impl PreconnectionBlobPayload {
pub fn general(&self) -> Option<&str> {
match self {
PreconnectionBlobPayload::General(pcb) => Some(pcb),
PreconnectionBlobPayload::VmConnect(_) => None,
}
}
pub fn vmconnect(&self) -> Option<&str> {
match self {
PreconnectionBlobPayload::VmConnect(vm_id) => Some(vm_id),
PreconnectionBlobPayload::General(_) => None,
}
}
}
impl Config {
@ -393,6 +425,15 @@ impl Config {
.zip(args.rdcleanpath_token)
.map(|(url, auth_token)| RDCleanPathConfig { url, auth_token });
let pcb = match (args.vmconnect, args.pcb) {
(Some(_), Some(_)) => {
unreachable!("Cannot use both `--vmconnect` and `--pcb` at the same time");
}
(Some(vm_id), None) => Some(PreconnectionBlobPayload::VmConnect(vm_id.to_string())),
(None, Some(pcb)) => Some(PreconnectionBlobPayload::General(pcb)),
(None, None) => None,
};
Ok(Self {
log_file: args.log_file,
destination,
@ -400,6 +441,7 @@ impl Config {
clipboard_type,
rdcleanpath,
dvc_pipe_proxies: args.dvc_proxy,
pcb,
})
}
}

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory};
use ironrdp::connector::connection_activation::ConnectionActivationState;
use ironrdp::connector::{ConnectionResult, ConnectorResult};
use ironrdp::connector::{ClientConnector, ConnectionResult, ConnectorCore, ConnectorResult};
use ironrdp::displaycontrol::client::DisplayControlClient;
use ironrdp::displaycontrol::pdu::MonitorLayoutEntry;
use ironrdp::graphics::image_processing::PixelFormat;
@ -17,15 +17,21 @@ use ironrdp_core::WriteBuf;
use ironrdp_dvc_pipe_proxy::DvcNamedPipeProxy;
use ironrdp_rdpsnd_native::cpal;
use ironrdp_tokio::reqwest::ReqwestNetworkClient;
use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite};
use ironrdp_tokio::{
mark_pcb_sent_by_rdclean_path, perform_credssp, run_until_handover, send_pcb, single_sequence_step_read,
split_tokio_framed, vm_connector_take_over, CredSSPFinished, Framed, FramedRead, FramedWrite, TokioStream,
Upgraded,
};
use ironrdp_vmconnect::VmClientConnector;
use rdpdr::NoopRdpdrBackend;
use smallvec::SmallVec;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use winit::event_loop::EventLoopProxy;
use x509_cert::der::asn1::OctetString;
use crate::config::{Config, RDCleanPathConfig};
use crate::config::{Config, PreconnectionBlobPayload, RDCleanPathConfig};
#[derive(Debug)]
pub enum RdpOutputEvent {
@ -194,49 +200,67 @@ async fn connect(
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
}
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
let mut connector = ClientConnector::new(config.connector.clone(), client_addr)
.with_static_channel(drdynvc)
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
if let Some(builder) = cliprdr_factory {
let backend = builder.build_cliprdr_backend();
let cliprdr = cliprdr::Cliprdr::new(backend);
connector.attach_static_channel(cliprdr);
}
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?;
let mut connector: Box<dyn ConnectorCore> =
if let Some(PreconnectionBlobPayload::VmConnect(vmconnect)) = &config.pcb {
let pcb_sent = send_pcb(&mut framed, vmconnect.to_owned()).await?;
let connector = vm_connector_take_over(pcb_sent, connector)?;
Box::new(connector)
} else {
Box::new(connector)
};
debug!("TLS upgrade");
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, connector.as_mut()).await?;
let (mut upgraded_framed, server_public_key) = upgrade(framed, config.destination.name()).await?;
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut());
// Ensure there is no leftover
let (initial_stream, leftover_bytes) = framed.into_inner();
let server_name = (&config.destination).into();
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
.await
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector);
let erased_stream = Box::new(upgraded_stream) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
let connection_result = ironrdp_tokio::connect_finalize(
let mut credssp_finished = perform_credssp(
upgraded,
connector.as_mut(),
&mut upgraded_framed,
connector,
(&config.destination).into(),
server_name,
server_public_key,
Some(&mut ReqwestNetworkClient::new()),
None,
)
.await?;
let connector = downcast_back_to_client_connector(connector, &mut credssp_finished, &mut upgraded_framed).await?;
let connection_result = ironrdp_tokio::connect_finalize(credssp_finished, &mut upgraded_framed, connector).await?;
debug!(?connection_result);
Ok((connection_result, upgraded_framed))
return Ok((connection_result, upgraded_framed));
async fn upgrade(
framed: Framed<TokioStream<TcpStream>>,
server_name: &str,
) -> ConnectorResult<(
Framed<TokioStream<Box<dyn AsyncReadWrite + Sync + Send + Unpin + 'static>>>,
Vec<u8>,
)> {
let (initial_stream, leftover_bytes) = framed.into_inner();
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, server_name)
.await
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
let erased_stream = Box::new(upgraded_stream) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
Ok((upgraded_framed, server_public_key))
}
}
async fn connect_ws(
@ -285,7 +309,7 @@ async fn connect_ws(
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
}
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
let mut connector = ClientConnector::new(config.connector.clone(), client_addr)
.with_static_channel(drdynvc)
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
@ -300,42 +324,48 @@ async fn connect_ws(
let destination = format!("{}:{}", config.destination.name(), config.destination.port());
let (upgraded, server_public_key) = connect_rdcleanpath(
let (upgraded, server_public_key, mut connector) = connect_rdcleanpath(
&mut framed,
&mut connector,
connector,
destination,
rdcleanpath.auth_token.clone(),
None,
&config.pcb,
)
.await?;
let connection_result = ironrdp_tokio::connect_finalize(
let (ws, leftover_bytes) = framed.into_inner();
let erased_stream = Box::new(ws) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
let server_name = (&config.destination).into();
let mut credssp_done = perform_credssp(
upgraded,
&mut framed,
connector,
(&config.destination).into(),
connector.as_mut(),
&mut upgraded_framed,
server_name,
server_public_key,
Some(&mut ReqwestNetworkClient::new()),
None,
)
.await?;
let (ws, leftover_bytes) = framed.into_inner();
let erased_stream = Box::new(ws) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut upgraded_framed).await?;
let connection_result = ironrdp_tokio::connect_finalize(credssp_done, &mut upgraded_framed, connector).await?;
Ok((connection_result, upgraded_framed))
}
async fn connect_rdcleanpath<S>(
framed: &mut ironrdp_tokio::Framed<S>,
connector: &mut connector::ClientConnector,
framed: &mut Framed<S>,
mut connector: ClientConnector,
destination: String,
proxy_auth_token: String,
pcb: Option<String>,
) -> ConnectorResult<(ironrdp_tokio::Upgraded, Vec<u8>)>
pcb: &Option<PreconnectionBlobPayload>,
) -> ConnectorResult<(Upgraded, Vec<u8>, Box<dyn ConnectorCore>)>
where
S: ironrdp_tokio::FramedRead + FramedWrite,
S: FramedRead + FramedWrite,
{
use ironrdp::connector::Sequence as _;
use x509_cert::der::Decode as _;
@ -360,79 +390,104 @@ where
let mut buf = WriteBuf::new();
info!("Begin connection procedure");
debug!(?pcb, "Begin connection procedure");
{
// RDCleanPath request
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
return Err(connector::general_err!("invalid connector state (send request)"));
};
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
return Err(connector::general_err!("invalid connector state (send request)"));
debug_assert!(connector.next_pdu_hint().is_none());
let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box<dyn ConnectorCore>) =
if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb {
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
None,
destination,
proxy_auth_token,
Some(vm_id.to_owned()),
)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect");
let pcb_sent = mark_pcb_sent_by_rdclean_path();
let connector = vm_connector_take_over(pcb_sent, connector)?;
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
} else {
let written = connector.step_no_input(&mut buf)?;
let x224_pdu_len = written.size().expect("written size");
debug_assert_eq!(x224_pdu_len, buf.filled_len());
let x224_pdu = buf.filled().to_vec();
let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general());
// RDCleanPath request
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
Some(x224_pdu),
destination,
proxy_auth_token,
general_pcb.map(str::to_string),
)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
};
debug_assert!(connector.next_pdu_hint().is_none());
let rdcleanpath_request = rdcleanpath_request
.to_der()
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
let written = connector.step_no_input(&mut buf)?;
let x224_pdu_len = written.size().expect("written size");
debug_assert_eq!(x224_pdu_len, buf.filled_len());
let x224_pdu = buf.filled().to_vec();
framed
.write_all(&rdcleanpath_request)
.await
.map_err(|e| connector::custom_err!("couldnt write RDCleanPath request", e))?;
let rdcleanpath_req =
ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request");
let rdcleanpath_req = rdcleanpath_req
.to_der()
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
let rdcleanpath_result = framed
.read_by_hint(&RDCLEANPATH_HINT)
.await
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
framed
.write_all(&rdcleanpath_req)
.await
.map_err(|e| connector::custom_err!("couldnt write RDCleanPath request", e))?;
let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result)
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU");
let (x224_connection_response, server_cert_chain) = match rdcleanpath_result
.into_enum()
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
{
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
return Err(connector::general_err!(
"received an unexpected RDCleanPath type (request)",
));
}
ironrdp_rdcleanpath::RDCleanPath::Response {
x224_connection_response,
server_cert_chain,
server_addr: _,
} => (x224_connection_response, server_cert_chain),
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
return Err(connector::custom_err!("received an RDCleanPath error", error));
}
};
buf.clear();
if let Some(x224_connection_response) = x224_connection_response {
// let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state() else {
// return Err(connector::general_err!("invalid connector state (wait confirm)"));
// };
debug_assert!(connector.next_pdu_hint().is_some());
// Write the X.224 connection response PDU
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
debug_assert!(written.is_nothing());
}
{
// RDCleanPath response
let server_public_key = extract_server_public_key(server_cert_chain)?;
let rdcleanpath_res = framed
.read_by_hint(&RDCLEANPATH_HINT)
.await
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
let should_upgrade = ironrdp_tokio::skip_connect_begin(connector.as_mut());
let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res)
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut());
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
let (x224_connection_response, server_cert_chain) = match rdcleanpath_res
.into_enum()
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
{
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
return Err(connector::general_err!(
"received an unexpected RDCleanPath type (request)",
));
}
ironrdp_rdcleanpath::RDCleanPath::Response {
x224_connection_response,
server_cert_chain,
server_addr: _,
} => (x224_connection_response, server_cert_chain),
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
return Err(connector::custom_err!("received an RDCleanPath error", error));
}
};
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
return Err(connector::general_err!("invalid connector state (wait confirm)"));
};
debug_assert!(connector.next_pdu_hint().is_some());
buf.clear();
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
debug_assert!(written.is_nothing());
return Ok((upgraded, server_public_key, connector));
fn extract_server_public_key(server_cert_chain: Vec<OctetString>) -> ConnectorResult<Vec<u8>> {
let server_cert = server_cert_chain
.into_iter()
.next()
@ -449,13 +504,7 @@ where
.ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))?
.to_owned();
let should_upgrade = ironrdp_tokio::skip_connect_begin(connector);
// At this point, proxy established the TLS session.
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector);
Ok((upgraded, server_public_key))
Ok(server_public_key)
}
}
@ -642,3 +691,25 @@ async fn active_session(
Ok(RdpControlFlow::TerminatedGracefully(disconnect_reason))
}
pub async fn downcast_back_to_client_connector(
connector: Box<dyn ConnectorCore>, // `ConnectorCore: Any`
credssp_finished: &mut CredSSPFinished,
framed: &mut Framed<impl FramedRead + FramedWrite>,
) -> ConnectorResult<ClientConnector> {
let connector: Box<dyn core::any::Any> = connector;
let client = match connector.downcast::<VmClientConnector>() {
Ok(vm_connector) => run_until_handover(credssp_finished, framed, *vm_connector).await?,
Err(err) => match err.downcast::<ClientConnector>() {
Ok(c) => *c,
Err(_) => {
return Err(connector::general_err!(
"connector is neither ClientConnector nor VmClientConnector"
))
}
},
};
Ok(client)
}

View file

@ -11,10 +11,11 @@ use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor};
use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState};
use crate::credssp::{self};
use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache};
use crate::{
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State,
Written,
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, CredsspSequenceFactory,
DesktopSize, SecurityConnector, Sequence, State, Written,
};
#[derive(Debug)]
@ -153,27 +154,63 @@ impl ClientConnector {
{
self.static_channels.insert(channel);
}
}
pub fn should_perform_security_upgrade(&self) -> bool {
impl SecurityConnector for ClientConnector {
fn should_perform_security_upgrade(&self) -> bool {
matches!(self.state, ClientConnectorState::EnhancedSecurityUpgrade { .. })
}
pub fn mark_security_upgrade_as_done(&mut self) {
fn mark_security_upgrade_as_done(&mut self) {
assert!(self.should_perform_security_upgrade());
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(!self.should_perform_security_upgrade());
}
pub fn should_perform_credssp(&self) -> bool {
fn should_perform_credssp(&self) -> bool {
matches!(self.state, ClientConnectorState::Credssp { .. })
}
pub fn mark_credssp_as_done(&mut self) {
fn mark_credssp_as_done(&mut self) {
assert!(self.should_perform_credssp());
let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(!self.should_perform_credssp());
assert_eq!(res, Written::Nothing);
}
fn selected_protocol(&self) -> Option<nego::SecurityProtocol> {
match &self.state {
ClientConnectorState::Credssp { selected_protocol } => Some(*selected_protocol),
_ => None,
}
}
fn config(&self) -> &Config {
&self.config
}
}
impl CredsspSequenceFactory for ClientConnector {
fn init_credssp(
&self,
credentials: crate::Credentials,
domain: Option<&str>,
protocol: nego::SecurityProtocol,
server_name: crate::ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<credssp::KerberosConfig>,
) -> ConnectorResult<(Box<dyn credssp::CredsspSequenceTrait>, sspi::credssp::TsRequest)> {
let (sequence, ts_request) = credssp::CredsspSequence::init(
credentials,
domain,
protocol,
server_name,
server_public_key,
kerberos_config,
)?;
Ok((Box::new(sequence), ts_request))
}
}
impl Sequence for ClientConnector {
@ -330,6 +367,8 @@ impl Sequence for ClientConnector {
let written = encode_x224_packet(&connect_initial, output)?;
trace!(written, "Written");
(
Written::from_size(written)?,
ClientConnectorState::BasicSettingsExchangeWaitResponse { connect_initial },

View file

@ -73,21 +73,104 @@ pub struct CredsspSequence {
}
#[derive(Debug, PartialEq)]
pub(crate) enum CredsspState {
pub enum CredsspState {
Ongoing,
EarlyUserAuthResult,
Finished,
}
impl CredsspSequence {
pub fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
match self.state {
pub trait CredsspSequenceTrait {
fn credssp_state(&self) -> &CredsspState;
fn set_credssp_state(&mut self, state: CredsspState);
fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
match self.credssp_state() {
CredsspState::Ongoing => Some(&CREDSSP_TS_REQUEST_HINT),
CredsspState::EarlyUserAuthResult => Some(&CREDSSP_EARLY_USER_AUTH_RESULT_HINT),
CredsspState::Finished => None,
}
}
fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult<Option<credssp::TsRequest>> {
match self.credssp_state() {
CredsspState::Ongoing => {
let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
debug!(?message, "Received");
Ok(Some(message))
}
CredsspState::EarlyUserAuthResult => {
let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input)
.map_err(|e| custom_err!("EarlyUserAuthResult", e))?;
debug!(message = ?early_user_auth_result, "Received");
match early_user_auth_result {
credssp::EarlyUserAuthResult::Success => {
self.set_credssp_state(CredsspState::Finished);
Ok(None)
}
credssp::EarlyUserAuthResult::AccessDenied => {
Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied))
}
}
}
_ => Err(general_err!(
"attempted to feed server request to CredSSP sequence in an unexpected state"
)),
}
}
fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_>;
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written>;
}
impl CredsspSequenceTrait for CredsspSequence {
fn credssp_state(&self) -> &CredsspState {
&self.state
}
fn set_credssp_state(&mut self, state: CredsspState) {
self.state = state;
}
fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> {
self.client.process(request)
}
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
let (size, next_state) = match self.state {
CredsspState::Ongoing => {
let (ts_request_from_client, next_state) = match result {
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
ClientState::FinalMessage(ts_request) => (
ts_request,
if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) {
CredsspState::EarlyUserAuthResult
} else {
CredsspState::Finished
},
),
};
debug!(message = ?ts_request_from_client, "Send");
let written = write_credssp_request(ts_request_from_client, output)?;
Ok((Written::from_size(written)?, next_state))
}
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
}?;
self.state = next_state;
Ok(size)
}
}
impl CredsspSequence {
/// `server_name` must be the actual target server hostname (as opposed to the proxy)
pub fn init(
credentials: Credentials,
@ -169,71 +252,6 @@ impl CredsspSequence {
Ok((sequence, initial_request))
}
/// Returns Some(ts_request) when a TS request is received from server,
/// and None when an early user auth result PDU is received instead.
pub fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult<Option<credssp::TsRequest>> {
match self.state {
CredsspState::Ongoing => {
let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
debug!(?message, "Received");
Ok(Some(message))
}
CredsspState::EarlyUserAuthResult => {
let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input)
.map_err(|e| custom_err!("EarlyUserAuthResult", e))?;
debug!(message = ?early_user_auth_result, "Received");
match early_user_auth_result {
credssp::EarlyUserAuthResult::Success => {
self.state = CredsspState::Finished;
Ok(None)
}
credssp::EarlyUserAuthResult::AccessDenied => {
Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied))
}
}
}
_ => Err(general_err!(
"attempted to feed server request to CredSSP sequence in an unexpected state"
)),
}
}
pub fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> {
self.client.process(request)
}
pub fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
let (size, next_state) = match self.state {
CredsspState::Ongoing => {
let (ts_request_from_client, next_state) = match result {
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
ClientState::FinalMessage(ts_request) => (
ts_request,
if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) {
CredsspState::EarlyUserAuthResult
} else {
CredsspState::Finished
},
),
};
debug!(message = ?ts_request_from_client, "Send");
let written = write_credssp_request(ts_request_from_client, output)?;
Ok((Written::from_size(written)?, next_state))
}
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
}?;
self.state = next_state;
Ok(size)
}
}
fn extract_user_name(cert: &Certificate) -> Option<String> {

View file

@ -22,18 +22,20 @@ use core::fmt;
use std::sync::Arc;
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
use ironrdp_pdu::nego::NegoRequestData;
use ironrdp_pdu::nego::{NegoRequestData, SecurityProtocol};
use ironrdp_pdu::rdp::capability_sets::{self, BitmapCodecs};
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
use ironrdp_pdu::x224::X224;
use ironrdp_pdu::{gcc, x224, PduHint};
pub use sspi;
use sspi::credssp::TsRequest;
pub use self::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
pub use self::connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
pub use self::connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
pub use self::server_name::ServerName;
use crate::credssp::{CredsspSequenceTrait, KerberosConfig};
pub use crate::license_exchange::LicenseCache;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -382,3 +384,39 @@ where
Ok(written)
}
pub trait SecurityConnector {
fn should_perform_security_upgrade(&self) -> bool;
fn mark_security_upgrade_as_done(&mut self);
fn should_perform_credssp(&self) -> bool;
fn selected_protocol(&self) -> Option<SecurityProtocol>;
fn mark_credssp_as_done(&mut self);
fn config(&self) -> &Config;
}
pub trait CredsspSequenceFactory {
fn init_credssp(
&self,
credentials: Credentials,
domain: Option<&str>,
protocol: SecurityProtocol,
server_name: ServerName,
server_public_key: Vec<u8>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<(Box<dyn CredsspSequenceTrait>, TsRequest)>;
}
pub trait ConnectorCore: Sequence + SecurityConnector + CredsspSequenceFactory + Any {
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Sequence + SecurityConnector + CredsspSequenceFactory + 'static> ConnectorCore for T {
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}

View file

@ -198,7 +198,7 @@ impl RDCleanPathPdu {
}
pub fn new_request(
x224_pdu: Vec<u8>,
x224_pdu: Option<Vec<u8>>,
destination: String,
proxy_auth: String,
pcb: Option<String>,
@ -208,19 +208,19 @@ impl RDCleanPathPdu {
destination: Some(destination),
proxy_auth: Some(proxy_auth),
preconnection_blob: pcb,
x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?,
..Self::default()
})
}
pub fn new_response(
server_addr: String,
x224_pdu: Vec<u8>,
x224_pdu: Option<Vec<u8>>,
x509_chain: impl IntoIterator<Item = Vec<u8>>,
) -> der::Result<Self> {
Ok(Self {
version: VERSION_1,
x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?,
server_cert_chain: Some(
x509_chain
.into_iter()
@ -271,10 +271,10 @@ pub enum RDCleanPath {
proxy_auth: String,
server_auth: Option<String>,
preconnection_blob: Option<String>,
x224_connection_request: OctetString,
x224_connection_request: Option<OctetString>,
},
Response {
x224_connection_response: OctetString,
x224_connection_response: Option<OctetString>,
server_cert_chain: Vec<OctetString>,
server_addr: String,
},
@ -308,15 +308,11 @@ impl TryFrom<RDCleanPathPdu> for RDCleanPath {
proxy_auth: pdu.proxy_auth.ok_or(MissingRDCleanPathField("proxy_auth"))?,
server_auth: pdu.server_auth,
preconnection_blob: pdu.preconnection_blob,
x224_connection_request: pdu
.x224_connection_pdu
.ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
x224_connection_request: pdu.x224_connection_pdu,
}
} else if let Some(server_addr) = pdu.server_addr {
Self::Response {
x224_connection_response: pdu
.x224_connection_pdu
.ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
x224_connection_response: pdu.x224_connection_pdu,
server_cert_chain: pdu
.server_cert_chain
.ok_or(MissingRDCleanPathField("server_cert_chain"))?,
@ -345,7 +341,7 @@ impl From<RDCleanPath> for RDCleanPathPdu {
proxy_auth: Some(proxy_auth),
server_auth,
preconnection_blob,
x224_connection_pdu: Some(x224_connection_request),
x224_connection_pdu: x224_connection_request,
..Default::default()
},
RDCleanPath::Response {
@ -354,7 +350,7 @@ impl From<RDCleanPath> for RDCleanPathPdu {
server_addr,
} => Self {
version: VERSION_1,
x224_connection_pdu: Some(x224_connection_response),
x224_connection_pdu: x224_connection_response,
server_cert_chain: Some(server_cert_chain),
server_addr: Some(server_addr),
..Default::default()

View file

@ -3,7 +3,7 @@ use rstest::rstest;
fn request() -> RDCleanPathPdu {
RDCleanPathPdu::new_request(
vec![0xDE, 0xAD, 0xBE, 0xFF],
Some(vec![0xDE, 0xAD, 0xBE, 0xFF]),
"destination".to_owned(),
"proxy auth".to_owned(),
Some("PCB".to_owned()),
@ -20,7 +20,7 @@ const REQUEST_DER: &[u8] = &[
fn response_success() -> RDCleanPathPdu {
RDCleanPathPdu::new_response(
"192.168.7.95".to_owned(),
vec![0xDE, 0xAD, 0xBE, 0xFF],
Some(vec![0xDE, 0xAD, 0xBE, 0xFF]),
[
vec![0xDE, 0xAD, 0xBE, 0xFF],
vec![0xDE, 0xAD, 0xBE, 0xFF],

View file

@ -209,17 +209,22 @@ where
.expect("TLS upgrade");
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector);
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new(upgraded_stream);
let connection_result = ironrdp_async::connect_finalize(
let credssp_done = ironrdp_async::perform_credssp(
upgraded,
&mut connector,
&mut upgraded_framed,
connector,
"localhost".into(),
server_public_key,
None,
None,
)
.await
.expect("finalize connection");
.expect("perform credssp");
let connection_result = ironrdp_async::connect_finalize(credssp_done, &mut upgraded_framed, connector)
.await
.expect("finalize connection");
let active_stage = ActiveStage::new(connection_result);
let (active_stage, mut upgraded_framed) = clientfn(active_stage, upgraded_framed, display_tx).await;

View file

@ -0,0 +1,24 @@
[package]
name = "ironrdp-vmconnect"
version = "0.1.0"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = [
"std",
] } # public
arbitrary = { version = "1", features = ["derive"], optional = true } # public
sspi = "0.15" # public
tracing = { version = "0.1", features = ["log"] }
ironrdp-connector = { path = "../ironrdp-connector" } # public
[lints]
workspace = true

View file

@ -0,0 +1,60 @@
use ironrdp_connector::general_err;
use ironrdp_pdu::nego::NegoRequestData;
use sspi::Username;
#[derive(Debug, Clone)]
pub struct Credentials {
pub(crate) username: String,
pub(crate) password: String,
}
impl Credentials {
pub(crate) fn to_sspi_auth_identity(&self, domain: Option<&str>) -> Result<sspi::AuthIdentity, sspi::Error> {
Ok(sspi::AuthIdentity {
username: Username::new(&self.username, domain)?,
password: self.password.clone().into(),
})
}
}
impl TryFrom<&ironrdp_connector::Credentials> for Credentials {
type Error = ironrdp_connector::ConnectorError;
fn try_from(value: &ironrdp_connector::Credentials) -> Result<Self, Self::Error> {
let ironrdp_connector::Credentials::UsernamePassword { username, password } = value else {
return Err(general_err!("Invalid credentials type for VM connection",));
};
Ok(Credentials {
username: username.to_owned(),
password: password.to_owned(),
})
}
}
#[derive(Debug, Clone)]
pub struct VmConnectorConfig {
pub request_data: Option<NegoRequestData>,
pub credentials: Credentials,
}
impl TryFrom<&ironrdp_connector::Config> for VmConnectorConfig {
type Error = ironrdp_connector::ConnectorError;
fn try_from(value: &ironrdp_connector::Config) -> Result<Self, Self::Error> {
let request_data = value.request_data.clone();
let ironrdp_connector::Credentials::UsernamePassword { username, password } = &value.credentials else {
return Err(general_err!("Invalid credentials type for VM connection",));
};
let credentials = Credentials {
username: username.to_owned(),
password: password.to_owned(),
};
Ok(VmConnectorConfig {
request_data,
credentials,
})
}
}

View file

@ -0,0 +1,250 @@
use core::mem;
use ironrdp_core::{decode, WriteBuf};
use ironrdp_pdu::nego::SecurityProtocol;
use ironrdp_pdu::x224::X224;
use ironrdp_pdu::{nego, PduHint};
use ironrdp_connector::{
general_err, reason_err, ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorExt as _,
ConnectorResult, CredsspSequenceFactory, Sequence, State, Written,
};
use crate::config::VmConnectorConfig;
pub const HYPERV_SECURITY_PROTOCOL: SecurityProtocol = SecurityProtocol::HYBRID_EX
.union(SecurityProtocol::SSL)
.union(SecurityProtocol::HYBRID);
#[derive(Default, Debug)]
#[non_exhaustive]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum VmConnectorState {
#[default]
Consumed,
EnhancedSecurityUpgrade,
Credssp,
ConnectionInitiationSendRequest,
ConnectionInitiationWaitConfirm,
Handover {
selected_protocol: SecurityProtocol,
},
}
impl State for VmConnectorState {
fn name(&self) -> &'static str {
match self {
Self::Consumed => "Consumed",
Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest",
Self::ConnectionInitiationWaitConfirm => "ConnectionInitiationWaitResponse",
Self::EnhancedSecurityUpgrade => "EnhancedSecurityUpgrade",
Self::Credssp => "Credssp",
Self::Handover { .. } => "Handover",
}
}
fn is_terminal(&self) -> bool {
matches!(self, Self::Handover { .. })
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
}
#[derive(Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct VmClientConnector {
pub config: VmConnectorConfig,
pub state: VmConnectorState,
client_connector: ClientConnector, // hold it hostage, can't do anything with it until VMConnector handover
}
impl Sequence for VmClientConnector {
fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
match &self.state {
VmConnectorState::Consumed => None,
VmConnectorState::ConnectionInitiationSendRequest => None,
VmConnectorState::ConnectionInitiationWaitConfirm => Some(&ironrdp_pdu::X224_HINT),
VmConnectorState::EnhancedSecurityUpgrade => None,
VmConnectorState::Credssp => None,
VmConnectorState::Handover { .. } => None,
}
}
fn state(&self) -> &dyn State {
&self.state
}
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written> {
let (written, next_state) = match mem::take(&mut self.state) {
// Invalid state
VmConnectorState::Consumed => {
return Err(general_err!("connector sequence state is consumed (this is a bug)",))
}
//== Connection Initiation ==//
// Exchange supported security protocols and a few other connection flags.
VmConnectorState::EnhancedSecurityUpgrade => (Written::Nothing, VmConnectorState::Credssp),
VmConnectorState::Credssp => (Written::Nothing, VmConnectorState::ConnectionInitiationSendRequest),
VmConnectorState::ConnectionInitiationSendRequest => {
debug!("Connection Initiation");
let connection_request = nego::ConnectionRequest {
nego_data: self
.config
.request_data
.clone()
.or_else(|| Some(nego::NegoRequestData::cookie(self.config.credentials.username.clone()))),
flags: nego::RequestFlags::empty(),
protocol: HYPERV_SECURITY_PROTOCOL,
};
debug!(message = ?connection_request, "Send");
let written =
ironrdp_core::encode_buf(&X224(connection_request), output).map_err(ConnectorError::encode)?;
(
Written::from_size(written)?,
VmConnectorState::ConnectionInitiationWaitConfirm,
)
}
VmConnectorState::ConnectionInitiationWaitConfirm => {
let connection_confirm = decode::<X224<nego::ConnectionConfirm>>(input)
.map_err(ConnectorError::decode)
.map(|p| p.0)?;
debug!(message = ?connection_confirm, "Received");
let (flags, selected_protocol) = match connection_confirm {
nego::ConnectionConfirm::Response { flags, protocol } => (flags, protocol),
nego::ConnectionConfirm::Failure { code } => {
error!(?code, "Received connection failure code");
return Err(reason_err!("Initiation", "{code}"));
}
};
info!(?selected_protocol, ?flags, "Server confirmed connection");
(Written::Nothing, VmConnectorState::Handover { selected_protocol })
}
VmConnectorState::Handover { .. } => {
return Err(general_err!(
"connector sequence state is already in handover (this is a bug)",
));
}
};
self.state = next_state;
Ok(written)
}
}
impl VmClientConnector {
pub fn take_over(connector: ClientConnector) -> ConnectorResult<Self> {
assert!(
matches!(connector.state, ClientConnectorState::ConnectionInitiationSendRequest),
"Invalid connector state for VM connection, expected ConnectionInitiationSendRequest, got: {}",
connector.state.name()
);
debug!("Taking over VM connector");
let vm_connector_config = VmConnectorConfig::try_from(&connector.config)?;
let vm_connector = VmClientConnector {
config: vm_connector_config,
state: VmConnectorState::EnhancedSecurityUpgrade,
client_connector: connector,
};
Ok(vm_connector)
}
pub fn should_hand_over(&self) -> bool {
matches!(self.state, VmConnectorState::Handover { .. })
}
pub fn hand_over(self) -> ClientConnector {
let VmConnectorState::Handover { selected_protocol } = self.state else {
panic!(
"Invalid state for handover, expected Handover, got: {}",
self.state.name()
);
};
let VmClientConnector {
mut client_connector, ..
} = self;
client_connector.state = ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol };
client_connector
}
}
impl VmClientConnector {
pub fn should_perform_credssp(&self) -> bool {
matches!(self.state, VmConnectorState::Credssp)
}
}
impl ironrdp_connector::SecurityConnector for VmClientConnector {
fn should_perform_security_upgrade(&self) -> bool {
matches!(self.state, VmConnectorState::EnhancedSecurityUpgrade)
}
fn mark_security_upgrade_as_done(&mut self) {
assert!(self.should_perform_security_upgrade());
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(!self.should_perform_security_upgrade());
}
fn should_perform_credssp(&self) -> bool {
matches!(self.state, VmConnectorState::Credssp)
}
fn selected_protocol(&self) -> Option<SecurityProtocol> {
if self.should_perform_credssp() {
Some(HYPERV_SECURITY_PROTOCOL)
} else {
None
}
}
fn mark_credssp_as_done(&mut self) {
assert!(self.should_perform_credssp());
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
debug_assert!(!self.should_perform_credssp());
}
fn config(&self) -> &ironrdp_connector::Config {
self.client_connector.config()
}
}
impl CredsspSequenceFactory for VmClientConnector {
fn init_credssp(
&self,
credentials: ironrdp_connector::Credentials,
domain: Option<&str>,
_protocol: SecurityProtocol,
server_name: ironrdp_connector::ServerName,
server_public_key: Vec<u8>,
_kerberos_config: Option<ironrdp_connector::credssp::KerberosConfig>,
) -> ConnectorResult<(
Box<dyn ironrdp_connector::credssp::CredsspSequenceTrait>,
sspi::credssp::TsRequest,
)> {
let credentials = crate::config::Credentials::try_from(&credentials)?;
let (credssp, ts_request) =
crate::credssp::VmCredsspSequence::init(credentials, domain, server_name, server_public_key)?;
let credssp: Box<dyn ironrdp_connector::credssp::CredsspSequenceTrait> = Box::new(credssp);
Ok((credssp, ts_request))
}
}

View file

@ -0,0 +1,114 @@
use ironrdp_connector::credssp::{CredsspSequenceTrait, CredsspState};
use ironrdp_connector::{custom_err, general_err};
use ironrdp_core::WriteBuf;
use sspi::credssp::{self, ClientState, CredSspClient};
use ironrdp_connector::{ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written};
use crate::config::Credentials;
// pub type CredsspProcessGenerator<'a> = Generator<'a, NetworkRequest, sspi::Result<Vec<u8>>, sspi::Result<ClientState>>;
#[derive(Debug)]
pub struct VmCredsspSequence {
client: CredSspClient,
state: CredsspState,
}
impl CredsspSequenceTrait for VmCredsspSequence {
fn credssp_state(&self) -> &CredsspState {
&self.state
}
fn set_credssp_state(&mut self, state: CredsspState) {
self.state = state;
}
fn process_ts_request(
&mut self,
request: credssp::TsRequest,
) -> ironrdp_connector::credssp::CredsspProcessGenerator<'_> {
self.client.process(request)
}
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
let (size, next_state) = match self.state {
CredsspState::Ongoing => {
let (ts_request_from_client, next_state) = match result {
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
ClientState::FinalMessage(ts_request) => (ts_request, CredsspState::Finished),
};
debug!(message = ?ts_request_from_client, "Send");
let written = write_credssp_request(ts_request_from_client, output)?;
Ok((Written::from_size(written)?, next_state))
}
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
}?;
self.state = next_state;
Ok(size)
}
}
/// The main difference between this and the `credssp::CredsspSequence` is that this sequence uses NTLM only
/// No Kerberos or Negotiate, as Hyper-V does not support it
impl VmCredsspSequence {
/// `server_name` must be the actual target server hostname (as opposed to the proxy)
pub fn init(
credentials: Credentials,
domain: Option<&str>,
server_name: ServerName,
server_public_key: Vec<u8>,
) -> ConnectorResult<(Self, credssp::TsRequest)> {
let credentials: sspi::Credentials = credentials
.to_sspi_auth_identity(domain)
.map_err(|e| custom_err!("Invalid username", e))?
.into();
let server_name = server_name.into_inner();
let service_principal_name = format!("TERMSRV/{}", &server_name);
let credssp_config = Box::<sspi::ntlm::NtlmConfig>::default();
debug!(?credssp_config);
let client = CredSspClient::new(
server_public_key,
credentials,
credssp::CredSspMode::WithCredentials,
credssp::ClientMode::Ntlm(sspi::ntlm::NtlmConfig {
client_computer_name: Some(server_name),
}),
service_principal_name,
)
.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?;
let sequence = Self {
client,
state: CredsspState::Ongoing,
};
let initial_request = credssp::TsRequest::default();
Ok((sequence, initial_request))
}
}
fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf) -> ConnectorResult<usize> {
let length = usize::from(ts_request.buffer_len());
let unfilled_buffer = output.unfilled_to(length);
ts_request
.encode_ts_request(unfilled_buffer)
.map_err(|e| custom_err!("TsRequest", e))?;
output.advance(length);
Ok(length)
}

View file

@ -0,0 +1,7 @@
#[macro_use]
extern crate tracing;
pub mod config;
pub mod connector;
pub mod credssp;
pub use connector::*;

View file

@ -38,6 +38,7 @@ ironrdp-cliprdr-format.path = "../ironrdp-cliprdr-format"
ironrdp-futures.path = "../ironrdp-futures"
ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath"
iron-remote-desktop.path = "../iron-remote-desktop"
ironrdp-vmconnect.path = "../ironrdp-vmconnect"
# WASM
wasm-bindgen = "0.2"

View file

@ -17,7 +17,7 @@ use ironrdp::cliprdr::backend::ClipboardMessage;
use ironrdp::cliprdr::CliprdrClient;
use ironrdp::connector::connection_activation::ConnectionActivationState;
use ironrdp::connector::credssp::KerberosConfig;
use ironrdp::connector::{self, ClientConnector, Credentials};
use ironrdp::connector::{self, ClientConnector, ConnectorCore, Credentials};
use ironrdp::displaycontrol::client::DisplayControlClient;
use ironrdp::dvc::DrdynvcClient;
use ironrdp::graphics::image_processing::PixelFormat;
@ -27,12 +27,13 @@ use ironrdp::pdu::rdp::client_info::PerformanceFlags;
use ironrdp::session::image::DecodedImage;
use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason};
use ironrdp_core::WriteBuf;
use ironrdp_futures::{single_sequence_step_read, FramedWrite};
use ironrdp_futures::{single_sequence_step_read, FramedRead, FramedWrite};
use rgb::AsPixels as _;
use tap::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::spawn_local;
use web_sys::HtmlCanvasElement;
use x509_cert::der::asn1::OctetString;
use crate::canvas::Canvas;
use crate::clipboard;
@ -55,7 +56,7 @@ struct SessionBuilderInner {
password: Option<String>,
proxy_address: Option<String>,
auth_token: Option<String>,
pcb: Option<String>,
pcb: Option<PreconnectionBlobPayload>,
kdc_proxy_url: Option<String>,
client_name: String,
desktop_size: DesktopSize,
@ -70,6 +71,21 @@ struct SessionBuilderInner {
use_display_control: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PreconnectionBlobPayload {
General(String),
VmConnect(String),
}
impl PreconnectionBlobPayload {
pub(crate) fn general(&self) -> Option<&str> {
match self {
PreconnectionBlobPayload::General(pcb) => Some(pcb),
PreconnectionBlobPayload::VmConnect(_) => None,
}
}
}
impl Default for SessionBuilderInner {
fn default() -> Self {
Self {
@ -213,7 +229,8 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
fn extension(&self, ext: Extension) -> Self {
iron_remote_desktop::extension_match! {
match ext;
|pcb: String| { self.0.borrow_mut().pcb = Some(pcb) };
|pcb: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::General(pcb)) };
|vmconnect: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::VmConnect(vmconnect)) };
|kdc_proxy_url: String| { self.0.borrow_mut().kdc_proxy_url = Some(kdc_proxy_url) };
|display_control: bool| { self.0.borrow_mut().use_display_control = display_control };
}
@ -905,7 +922,7 @@ struct ConnectParams {
config: connector::Config,
proxy_auth_token: String,
destination: String,
pcb: Option<String>,
pcb: Option<PreconnectionBlobPayload>,
kdc_proxy_url: Option<String>,
clipboard_backend: Option<WasmClipboardBackend>,
use_display_control: bool,
@ -940,13 +957,13 @@ async fn connect(
);
}
let (upgraded, server_public_key) =
connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?;
let (upgraded, server_public_key, mut connector) =
connect_rdcleanpath(&mut framed, connector, destination.clone(), proxy_auth_token, &pcb).await?;
let connection_result = ironrdp_futures::connect_finalize(
let mut credssp_done = ironrdp_futures::perform_credssp(
upgraded,
connector.as_mut(),
&mut framed,
connector,
(&destination).into(),
server_public_key,
Some(&mut WasmNetworkClient),
@ -961,6 +978,9 @@ async fn connect(
)
.await?;
let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut framed).await?;
let connection_result = ironrdp_futures::connect_finalize(credssp_done, &mut framed, connector).await?;
let ws = framed.into_inner_no_leftover();
Ok((connection_result, ws))
@ -968,13 +988,13 @@ async fn connect(
async fn connect_rdcleanpath<S>(
framed: &mut ironrdp_futures::Framed<S>,
connector: &mut ClientConnector,
mut connector: ClientConnector,
destination: String,
proxy_auth_token: String,
pcb: Option<String>,
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), IronError>
pcb: &Option<PreconnectionBlobPayload>,
) -> connector::ConnectorResult<(ironrdp_futures::Upgraded, Vec<u8>, Box<dyn ConnectorCore>)>
where
S: ironrdp_futures::FramedRead + FramedWrite,
S: FramedRead + FramedWrite,
{
use ironrdp::connector::Sequence as _;
use x509_cert::der::Decode as _;
@ -999,99 +1019,121 @@ where
let mut buf = WriteBuf::new();
info!("Begin connection procedure");
debug!(?pcb, "Begin connection procedure");
{
// RDCleanPath request
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
return Err(connector::general_err!("invalid connector state (send request)"));
};
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
return Err(anyhow::Error::msg("invalid connector state (send request)").into());
debug_assert!(connector.next_pdu_hint().is_none());
let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box<dyn ConnectorCore>) =
if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb {
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
None,
destination,
proxy_auth_token,
Some(vm_id.to_owned()),
)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect");
let pcb_sent = ironrdp_futures::mark_pcb_sent_by_rdclean_path();
let connector = ironrdp_futures::vm_connector_take_over(pcb_sent, connector)?;
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
} else {
let written = connector.step_no_input(&mut buf)?;
let x224_pdu_len = written.size().expect("written size");
debug_assert_eq!(x224_pdu_len, buf.filled_len());
let x224_pdu = buf.filled().to_vec();
let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general());
// RDCleanPath request
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
Some(x224_pdu),
destination,
proxy_auth_token,
general_pcb.map(str::to_string),
)
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
};
debug_assert!(connector.next_pdu_hint().is_none());
let rdcleanpath_request = rdcleanpath_request
.to_der()
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
let written = connector.step_no_input(&mut buf)?;
let x224_pdu_len = written.size().expect("written size");
debug_assert_eq!(x224_pdu_len, buf.filled_len());
let x224_pdu = buf.filled().to_vec();
framed
.write_all(&rdcleanpath_request)
.await
.map_err(|e| connector::custom_err!("couldnt write RDCleanPath request", e))?;
let rdcleanpath_req =
ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb)
.context("new RDCleanPath request")?;
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request");
let rdcleanpath_req = rdcleanpath_req.to_der().context("RDCleanPath request encode")?;
let rdcleanpath_result = framed
.read_by_hint(&RDCLEANPATH_HINT)
.await
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
framed
.write_all(&rdcleanpath_req)
.await
.context("couldnt write RDCleanPath request")?;
let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result)
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU");
let (x224_connection_response, server_cert_chain) = match rdcleanpath_result
.into_enum()
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
{
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
return Err(connector::general_err!(
"received an unexpected RDCleanPath type (request)",
));
}
ironrdp_rdcleanpath::RDCleanPath::Response {
x224_connection_response,
server_cert_chain,
server_addr: _,
} => (x224_connection_response, server_cert_chain),
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
return Err(connector::custom_err!("received an RDCleanPath error", error));
}
};
buf.clear();
if let Some(x224_connection_response) = x224_connection_response {
// let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state() else {
// return Err(connector::general_err!("invalid connector state (wait confirm)"));
// };
debug_assert!(connector.next_pdu_hint().is_some());
// Write the X.224 connection response PDU
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
debug_assert!(written.is_nothing());
}
{
// RDCleanPath response
let server_public_key = extract_server_public_key(server_cert_chain)?;
let rdcleanpath_res = framed
.read_by_hint(&RDCLEANPATH_HINT)
.await
.context("read RDCleanPath request")?;
let should_upgrade = ironrdp_futures::skip_connect_begin(connector.as_mut());
let rdcleanpath_res =
ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res).context("RDCleanPath response decode")?;
let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector.as_mut());
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
let (x224_connection_response, server_cert_chain) =
match rdcleanpath_res.into_enum().context("invalid RDCleanPath PDU")? {
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
return Err(anyhow::Error::msg("received an unexpected RDCleanPath type (request)").into());
}
ironrdp_rdcleanpath::RDCleanPath::Response {
x224_connection_response,
server_cert_chain,
server_addr: _,
} => (x224_connection_response, server_cert_chain),
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
return Err(
IronError::from(anyhow::Error::new(error).context("received an RDCleanPath error"))
.with_kind(IronErrorKind::RDCleanPath),
);
}
};
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
return Err(anyhow::Error::msg("invalid connector state (wait confirm)").into());
};
debug_assert!(connector.next_pdu_hint().is_some());
buf.clear();
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
debug_assert!(written.is_nothing());
return Ok((upgraded, server_public_key, connector));
fn extract_server_public_key(server_cert_chain: Vec<OctetString>) -> connector::ConnectorResult<Vec<u8>> {
let server_cert = server_cert_chain
.into_iter()
.next()
.context("server cert chain missing from rdcleanpath response")?;
.ok_or_else(|| connector::general_err!("server cert chain missing from rdcleanpath response"))?;
let cert = x509_cert::Certificate::from_der(server_cert.as_bytes())
.context("failed to decode x509 certificate sent by proxy")?;
.map_err(|e| connector::custom_err!("server cert chain missing from rdcleanpath response", e))?;
let server_public_key = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.context("subject public key BIT STRING is not aligned")?
.ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))?
.to_owned();
let should_upgrade = ironrdp_futures::skip_connect_begin(connector);
// At this point, proxy established the TLS session.
let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector);
Ok((upgraded, server_public_key))
Ok(server_public_key)
}
}
@ -1100,3 +1142,25 @@ where
fn f64_to_u16_saturating_cast(value: f64) -> u16 {
value as u16
}
async fn downcast_back_to_client_connector(
connector: Box<dyn ConnectorCore>, // `ConnectorCore: Any`
credssp_finished: &mut ironrdp_futures::CredSSPFinished,
framed: &mut ironrdp_futures::Framed<impl FramedRead + FramedWrite>,
) -> connector::ConnectorResult<ClientConnector> {
let connector: Box<dyn core::any::Any> = connector;
let client = match connector.downcast::<ironrdp_vmconnect::VmClientConnector>() {
Ok(vm_connector) => ironrdp_futures::run_until_handover(credssp_finished, framed, *vm_connector).await?,
Err(err) => match err.downcast::<ClientConnector>() {
Ok(c) => *c,
Err(_) => {
return Err(connector::general_err!(
"connector is neither ClientConnector nor VmClientConnector"
))
}
},
};
Ok(client)
}

View file

@ -8,7 +8,7 @@ pub mod ffi {
use core::fmt::Write;
use diplomat_runtime::DiplomatWriteable;
use ironrdp::connector::Sequence as _;
use ironrdp::connector::{SecurityConnector, Sequence as _};
use ironrdp::displaycontrol::client::DisplayControlClient;
use tracing::info;

View file

@ -4,6 +4,7 @@ pub mod network;
#[diplomat::bridge]
pub mod ffi {
use ironrdp::connector::credssp::CredsspSequenceTrait;
use ironrdp::connector::ClientConnectorState;
use super::network::ffi::{ClientState, CredsspProcessGenerator};

View file

@ -32,3 +32,7 @@ export function displayControl(enable: boolean): Extension {
export function kdcProxyUrl(url: string): Extension {
return new Extension('kdc_proxy_url', url);
}
export function vmConnect(vm_id: string): Extension {
return new Extension('vmconnect', vm_id);
}

View file

@ -2,7 +2,13 @@
import { currentSession, userInteractionService } from '../../services/session.service';
import type { UserInteraction } from '../../../static/iron-remote-desktop';
import type { Session } from '../../models/session';
import { preConnectionBlob, displayControl, kdcProxyUrl, init } from '../../../static/iron-remote-desktop-rdp';
import {
preConnectionBlob,
displayControl,
kdcProxyUrl,
init,
vmConnect,
} from '../../../static/iron-remote-desktop-rdp';
import { toast } from '$lib/messages/message-store';
import { showLogin } from '$lib/login/login-store';
import { onMount } from 'svelte';
@ -16,6 +22,7 @@
let kdc_proxy_url = '';
let desktopSize = { width: 1280, height: 720 };
let pcb = '';
let vmconnect = '';
let pop_up = false;
let enable_clipboard = true;
@ -131,6 +138,10 @@
configBuilder.withExtension(preConnectionBlob(pcb));
}
if (vmconnect !== '') {
configBuilder.withExtension(vmConnect(vmconnect));
}
if (kdc_proxy_url !== '') {
configBuilder.withExtension(kdcProxyUrl(kdc_proxy_url));
}
@ -202,6 +213,10 @@
<input id="pcb" type="text" bind:value={pcb} />
<label for="pcb">Pre Connection Blob</label>
</div>
<div class="field label border">
<input id="pcb" type="text" bind:value={vmconnect} />
<label for="pcb">HyperV VmConnect ID</label>
</div>
<div class="field label border">
<input id="desktopSizeW" type="text" bind:value={desktopSize.width} />
<label for="desktopSizeW">Desktop Width</label>