From 6dc4f1be4e0cec8a3f7063788085f7a4f48b5d9f Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:55:04 -0400 Subject: [PATCH 1/6] sqash everything --- Cargo.lock | 67 ++++- crates/ironrdp-async/Cargo.toml | 1 + crates/ironrdp-async/src/connector.rs | 154 +++++----- crates/ironrdp-async/src/lib.rs | 3 +- crates/ironrdp-async/src/vmconnector.rs | 56 ++++ crates/ironrdp-blocking/src/connector.rs | 31 +- crates/ironrdp-client/Cargo.toml | 1 + crates/ironrdp-client/src/config.rs | 42 +++ crates/ironrdp-client/src/rdp.rs | 279 +++++++++++------- crates/ironrdp-connector/Cargo.toml | 8 +- crates/ironrdp-connector/src/connection.rs | 51 +++- crates/ironrdp-connector/src/credssp.rs | 156 +++++----- crates/ironrdp-connector/src/lib.rs | 40 ++- crates/ironrdp-rdcleanpath/src/lib.rs | 24 +- .../tests/rdcleanpath.rs | 4 +- crates/ironrdp-testsuite-extra/tests/tests.rs | 11 +- crates/ironrdp-vmconnect/Cargo.toml | 24 ++ crates/ironrdp-vmconnect/src/config.rs | 60 ++++ crates/ironrdp-vmconnect/src/connector.rs | 250 ++++++++++++++++ crates/ironrdp-vmconnect/src/credssp.rs | 115 ++++++++ crates/ironrdp-vmconnect/src/lib.rs | 7 + crates/ironrdp-web/Cargo.toml | 1 + crates/ironrdp-web/src/session.rs | 218 +++++++++----- ffi/src/connector/mod.rs | 2 +- ffi/src/credssp/mod.rs | 1 + .../iron-remote-desktop-rdp/src/main.ts | 4 + .../src/lib/login/login.svelte | 11 +- 27 files changed, 1253 insertions(+), 368 deletions(-) create mode 100644 crates/ironrdp-async/src/vmconnector.rs create mode 100644 crates/ironrdp-vmconnect/Cargo.toml create mode 100644 crates/ironrdp-vmconnect/src/config.rs create mode 100644 crates/ironrdp-vmconnect/src/connector.rs create mode 100644 crates/ironrdp-vmconnect/src/credssp.rs create mode 100644 crates/ironrdp-vmconnect/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 207d6cb36..0f333abf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1525,7 +1525,7 @@ dependencies = [ "ironrdp", "ironrdp-cliprdr-native", "ironrdp-core", - "sspi", + "sspi 0.16.0", "thiserror 1.0.69", "tracing", "tracing-subscriber", @@ -2360,7 +2360,7 @@ dependencies = [ "opus", "pico-args", "rand 0.8.5", - "sspi", + "sspi 0.16.0", "tokio-rustls", "tracing", "tracing-subscriber", @@ -2398,6 +2398,7 @@ dependencies = [ "ironrdp-connector", "ironrdp-core", "ironrdp-pdu", + "ironrdp-vmconnect", "tracing", ] @@ -2448,6 +2449,7 @@ dependencies = [ "ironrdp-rdpsnd-native", "ironrdp-tls", "ironrdp-tokio", + "ironrdp-vmconnect", "proc-exit", "raw-window-handle", "semver", @@ -2512,7 +2514,7 @@ dependencies = [ "picky-asn1-der", "picky-asn1-x509", "rand_core 0.6.4", - "sspi", + "sspi 0.16.0", "tracing", "url", ] @@ -2836,11 +2838,23 @@ dependencies = [ "ironrdp-async", "ironrdp-connector", "reqwest", - "sspi", + "sspi 0.16.0", "tokio", "url", ] +[[package]] +name = "ironrdp-vmconnect" +version = "0.1.0" +dependencies = [ + "arbitrary", + "ironrdp-connector", + "ironrdp-core", + "ironrdp-pdu", + "sspi 0.15.14", + "tracing", +] + [[package]] name = "ironrdp-web" version = "0.0.0" @@ -2859,6 +2873,7 @@ dependencies = [ "ironrdp-core", "ironrdp-futures", "ironrdp-rdcleanpath", + "ironrdp-vmconnect", "js-sys", "png", "resize", @@ -5075,6 +5090,50 @@ dependencies = [ "der", ] +[[package]] +name = "sspi" +version = "0.15.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214ee905dcdd4b7ab11404b616e58dc6944d80fe8592fbdc13abc87d7e2bff0a" +dependencies = [ + "async-dnssd", + "async-recursion", + "bitflags 2.9.1", + "byteorder", + "cfg-if", + "crypto-mac", + "futures", + "hmac", + "lazy_static", + "md-5", + "md4", + "num-bigint-dig", + "num-derive", + "num-traits", + "oid", + "picky", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "rand 0.8.5", + "rsa", + "rustls", + "serde", + "serde_derive", + "sha1", + "sha2", + "time", + "tokio", + "tracing", + "url", + "uuid", + "windows 0.61.3", + "windows-registry", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "sspi" version = "0.16.0" diff --git a/crates/ironrdp-async/Cargo.toml b/crates/ironrdp-async/Cargo.toml index 77c51bfb1..93758918e 100644 --- a/crates/ironrdp-async/Cargo.toml +++ b/crates/ironrdp-async/Cargo.toml @@ -17,6 +17,7 @@ test = false [dependencies] ironrdp-connector = { path = "../ironrdp-connector", version = "0.6" } # 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"] } diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index 4d1b085dc..3ae6cab85 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -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(framed: &mut Framed, connector: &mut ClientConnector) -> ConnectorResult +pub async fn connect_begin( + framed: &mut Framed, + connector: &mut dyn ConnectorCore, +) -> ConnectorResult 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( +#[non_exhaustive] +pub struct CredSSPFinished { + pub(crate) write_buf: WriteBuf, +} + +pub async fn perform_credssp( _: Upgraded, + connector: &mut dyn ConnectorCore, framed: &mut Framed, - mut connector: ClientConnector, server_name: ServerName, server_public_key: Vec, network_client: Option<&mut dyn AsyncNetworkClient>, kerberos_config: Option, -) -> ConnectorResult +) -> ConnectorResult 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( + CredSSPFinished { write_buf: mut buf }: CredSSPFinished, + framed: &mut Framed, + mut connector: ClientConnector, +) -> ConnectorResult +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( framed: &mut Framed, - connector: &mut ClientConnector, + connector: &mut dyn ConnectorCore, buf: &mut WriteBuf, server_name: ServerName, server_public_key: Vec, @@ -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 (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, - )?; - - loop { - let client_state = { - let mut generator = sequence.process_ts_request(ts_request); + let selected_protocol = connector + .selected_protocol() + .ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?; - 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))? + { + 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); + + 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) - .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; - } - - let Some(next_pdu_hint) = sequence.next_pdu_hint() else { - break; - }; - - debug!( - connector.state = connector.state.name(), - hint = ?next_pdu_hint, - "Wait for PDU" - ); + 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))?; + 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"); + 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(()) diff --git a/crates/ironrdp-async/src/lib.rs b/crates/ironrdp-async/src/lib.rs index e2ed55cd3..ebece3af8 100644 --- a/crates/ironrdp-async/src/lib.rs +++ b/crates/ironrdp-async/src/lib.rs @@ -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>( diff --git a/crates/ironrdp-async/src/vmconnector.rs b/crates/ironrdp-async/src/vmconnector.rs new file mode 100644 index 000000000..85f4249d9 --- /dev/null +++ b/crates/ironrdp-async/src/vmconnector.rs @@ -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(framed: &mut Framed, payload: String) -> ConnectorResult +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::take_over(connector) +} + +pub async fn run_until_handover( + credssp_finished: &mut CredSSPFinished, + framed: &mut Framed, + mut connector: VmClientConnector, +) -> ConnectorResult { + 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) +} diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index 765560e6e..cc54b3338 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -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(framed: &mut Framed, connector: &mut ClientConnector) -> ConnectorResult +pub fn connect_begin(framed: &mut Framed, connector: &mut dyn ConnectorCore) -> ConnectorResult 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( framed: &mut Framed, - connector: &mut ClientConnector, + connector: &mut dyn ConnectorCore, buf: &mut WriteBuf, server_name: ServerName, server_public_key: Vec, @@ -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( framed: &mut Framed, - 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" ); diff --git a/crates/ironrdp-client/Cargo.toml b/crates/ironrdp-client/Cargo.toml index a9cfc9402..b08c9dd8b 100644 --- a/crates/ironrdp-client/Cargo.toml +++ b/crates/ironrdp-client/Cargo.toml @@ -52,6 +52,7 @@ ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy" ironrdp-propertyset.path = "../ironrdp-propertyset" ironrdp-rdpfile.path = "../ironrdp-rdpfile" ironrdp-cfg.path = "../ironrdp-cfg" +ironrdp-vmconnect.path = "../ironrdp-vmconnect" # Windowing and rendering winit = { version = "0.30", features = ["rwh_06"] } diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index 93f8a8301..6b3cd5330 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -29,6 +29,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, + + pub pcb: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -285,6 +287,36 @@ struct Args { /// `` will automatically be prefixed with `\\.\pipe\` on Windows. #[clap(long)] dvc_proxy: Vec, + + /// The ID for the HyperV VM server to connect to + #[clap(long, conflicts_with("pcb"))] + vmconnect: Option, + + /// Preconnection Blob payload to use + #[clap(long)] + pcb: Option, +} + +#[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 { @@ -427,6 +459,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, @@ -434,6 +475,7 @@ impl Config { clipboard_type, rdcleanpath, dvc_pipe_proxies: args.dvc_proxy, + pcb, }) } } diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index eba13ce5e..6b20be93f 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -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?; - - debug!("TLS upgrade"); - - // Ensure there is no leftover - let (initial_stream, leftover_bytes) = framed.into_inner(); - - 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 mut connector: Box = + 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) + }; - let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector); + 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()); - let erased_stream = Box::new(upgraded_stream) as Box; - let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); + let server_name = (&config.destination).into(); - 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>, + server_name: &str, + ) -> ConnectorResult<( + Framed>>, + Vec, + )> { + 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; + 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; + 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; - 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( - framed: &mut ironrdp_tokio::Framed, - connector: &mut connector::ClientConnector, + framed: &mut Framed, + mut connector: ClientConnector, destination: String, proxy_auth_token: String, - pcb: Option, -) -> ConnectorResult<(ironrdp_tokio::Upgraded, Vec)> + pcb: &Option, +) -> ConnectorResult<(Upgraded, Vec, Box)> 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_req, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + 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) + } 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) }; - debug_assert!(connector.next_pdu_hint().is_none()); + let rdcleanpath_req = rdcleanpath_req + .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(); - - 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))?; - - framed - .write_all(&rdcleanpath_req) - .await - .map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?; - } + framed + .write_all(&rdcleanpath_req) + .await + .map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?; - { - // RDCleanPath response + let rdcleanpath_res = framed + .read_by_hint(&RDCLEANPATH_HINT) + .await + .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = framed - .read_by_hint(&RDCLEANPATH_HINT) - .await - .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; + let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) + .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) - .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; + debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); - 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 (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)); - } - }; + 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()); + } - let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else { - return Err(connector::general_err!("invalid connector state (wait confirm)")); - }; + let server_public_key = extract_server_public_key(server_cert_chain)?; - debug_assert!(connector.next_pdu_hint().is_some()); + let should_upgrade = ironrdp_tokio::skip_connect_begin(connector.as_mut()); - buf.clear(); - let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?; + let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut()); - debug_assert!(written.is_nothing()); + return Ok((upgraded, server_public_key, connector)); + fn extract_server_public_key(server_cert_chain: Vec) -> ConnectorResult> { 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, // `ConnectorCore: Any` + credssp_finished: &mut CredSSPFinished, + framed: &mut Framed, +) -> ConnectorResult { + let connector: Box = connector; + + let client = match connector.downcast::() { + Ok(vm_connector) => run_until_handover(credssp_finished, framed, *vm_connector).await?, + Err(err) => match err.downcast::() { + Ok(c) => *c, + Err(_) => { + return Err(connector::general_err!( + "connector is neither ClientConnector nor VmClientConnector" + )) + } + }, + }; + + Ok(client) +} diff --git a/crates/ironrdp-connector/Cargo.toml b/crates/ironrdp-connector/Cargo.toml index b74ee82b3..c6e46492d 100644 --- a/crates/ironrdp-connector/Cargo.toml +++ b/crates/ironrdp-connector/Cargo.toml @@ -22,11 +22,15 @@ arbitrary = ["dep:arbitrary"] ironrdp-svc = { path = "../ironrdp-svc", version = "0.4" } # public ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public ironrdp-error = { path = "../ironrdp-error", version = "0.1" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = ["std"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = [ + "std", +] } # public arbitrary = { version = "1", features = ["derive"], optional = true } # public sspi = "0.16" # public url = "2.5" # public -rand_core = { version = "0.6", features = ["std"] } # TODO: dependency injection? +rand_core = { version = "0.6", features = [ + "std", +] } # TODO: dependency injection? tracing = { version = "0.1", features = ["log"] } picky-asn1-der = "0.5" picky-asn1-x509 = "0.14" diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 85693d3c3..b148e25e5 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -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 { + 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, + kerberos_config: Option, + ) -> ConnectorResult<(Box, 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 }, diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 0d2f8dec5..eb15fe356 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -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> { + 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; +} + +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 { + 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> { - 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 { - 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 { diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index f84f9826e..ad162a55f 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -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; + + 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, + kerberos_config: Option, + ) -> ConnectorResult<(Box, TsRequest)>; +} + +pub trait ConnectorCore: Sequence + SecurityConnector + CredsspSequenceFactory + Any { + fn into_any(self: Box) -> Box; +} + +impl ConnectorCore for T { + fn into_any(self: Box) -> Box { + self + } +} diff --git a/crates/ironrdp-rdcleanpath/src/lib.rs b/crates/ironrdp-rdcleanpath/src/lib.rs index db44d4033..7850c8cbb 100644 --- a/crates/ironrdp-rdcleanpath/src/lib.rs +++ b/crates/ironrdp-rdcleanpath/src/lib.rs @@ -198,7 +198,7 @@ impl RDCleanPathPdu { } pub fn new_request( - x224_pdu: Vec, + x224_pdu: Option>, destination: String, proxy_auth: String, pcb: Option, @@ -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, + x224_pdu: Option>, x509_chain: impl IntoIterator>, ) -> der::Result { 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, preconnection_blob: Option, - x224_connection_request: OctetString, + x224_connection_request: Option, }, Response { - x224_connection_response: OctetString, + x224_connection_response: Option, server_cert_chain: Vec, server_addr: String, }, @@ -308,15 +308,11 @@ impl TryFrom 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 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 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() diff --git a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs index 4c91f3b34..3ebc399b6 100644 --- a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs +++ b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs @@ -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], diff --git a/crates/ironrdp-testsuite-extra/tests/tests.rs b/crates/ironrdp-testsuite-extra/tests/tests.rs index 53cbfc118..3fcb238ef 100644 --- a/crates/ironrdp-testsuite-extra/tests/tests.rs +++ b/crates/ironrdp-testsuite-extra/tests/tests.rs @@ -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; diff --git a/crates/ironrdp-vmconnect/Cargo.toml b/crates/ironrdp-vmconnect/Cargo.toml new file mode 100644 index 000000000..5f7448806 --- /dev/null +++ b/crates/ironrdp-vmconnect/Cargo.toml @@ -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 diff --git a/crates/ironrdp-vmconnect/src/config.rs b/crates/ironrdp-vmconnect/src/config.rs new file mode 100644 index 000000000..3be773fba --- /dev/null +++ b/crates/ironrdp-vmconnect/src/config.rs @@ -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 { + 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 { + 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, + pub credentials: Credentials, +} + +impl TryFrom<&ironrdp_connector::Config> for VmConnectorConfig { + type Error = ironrdp_connector::ConnectorError; + + fn try_from(value: &ironrdp_connector::Config) -> Result { + 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, + }) + } +} diff --git a/crates/ironrdp-vmconnect/src/connector.rs b/crates/ironrdp-vmconnect/src/connector.rs new file mode 100644 index 000000000..f47cb9ad1 --- /dev/null +++ b/crates/ironrdp-vmconnect/src/connector.rs @@ -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 { + 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::>(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 { + 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 { + 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, + _kerberos_config: Option, + ) -> ConnectorResult<( + Box, + 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 = Box::new(credssp); + + Ok((credssp, ts_request)) + } +} diff --git a/crates/ironrdp-vmconnect/src/credssp.rs b/crates/ironrdp-vmconnect/src/credssp.rs new file mode 100644 index 000000000..0e2d4385a --- /dev/null +++ b/crates/ironrdp-vmconnect/src/credssp.rs @@ -0,0 +1,115 @@ +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>, sspi::Result>; + +#[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 { + 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, + ) -> 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::::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 { + 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) +} diff --git a/crates/ironrdp-vmconnect/src/lib.rs b/crates/ironrdp-vmconnect/src/lib.rs new file mode 100644 index 000000000..61bde2945 --- /dev/null +++ b/crates/ironrdp-vmconnect/src/lib.rs @@ -0,0 +1,7 @@ +#[macro_use] +extern crate tracing; + +pub mod config; +pub mod connector; +pub mod credssp; +pub use connector::*; diff --git a/crates/ironrdp-web/Cargo.toml b/crates/ironrdp-web/Cargo.toml index a2d0f9d26..d5dd827d7 100644 --- a/crates/ironrdp-web/Cargo.toml +++ b/crates/ironrdp-web/Cargo.toml @@ -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" diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index dc5842583..f0d76a2aa 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -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, proxy_address: Option, auth_token: Option, - pcb: Option, + pcb: Option, kdc_proxy_url: Option, 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, + pcb: Option, kdc_proxy_url: Option, clipboard_backend: Option, 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( framed: &mut ironrdp_futures::Framed, - connector: &mut ClientConnector, + mut connector: ClientConnector, destination: String, proxy_auth_token: String, - pcb: Option, -) -> Result<(ironrdp_futures::Upgraded, Vec), IronError> + pcb: &Option, +) -> connector::ConnectorResult<(ironrdp_futures::Upgraded, Vec, Box)> 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_req, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + 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_assert!(connector.next_pdu_hint().is_none()); + debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect"); - 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 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) + } 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) + }; - 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_req = rdcleanpath_req + .to_der() + .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; - framed - .write_all(&rdcleanpath_req) - .await - .context("couldn’t write RDCleanPath request")?; - } + framed + .write_all(&rdcleanpath_req) + .await + .map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?; - { - // RDCleanPath response + let rdcleanpath_res = framed + .read_by_hint(&RDCLEANPATH_HINT) + .await + .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = framed - .read_by_hint(&RDCLEANPATH_HINT) - .await - .context("read RDCleanPath request")?; + let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) + .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - let rdcleanpath_res = - ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res).context("RDCleanPath response decode")?; + debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); - 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 (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), - ); - } - }; + 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()); + } - let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else { - return Err(anyhow::Error::msg("invalid connector state (wait confirm)").into()); - }; + let server_public_key = extract_server_public_key(server_cert_chain)?; - debug_assert!(connector.next_pdu_hint().is_some()); + let should_upgrade = ironrdp_futures::skip_connect_begin(connector.as_mut()); - buf.clear(); - let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?; + let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector.as_mut()); - debug_assert!(written.is_nothing()); + return Ok((upgraded, server_public_key, connector)); + fn extract_server_public_key(server_cert_chain: Vec) -> connector::ConnectorResult> { 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, // `ConnectorCore: Any` + credssp_finished: &mut ironrdp_futures::CredSSPFinished, + framed: &mut ironrdp_futures::Framed, +) -> connector::ConnectorResult { + let connector: Box = connector; + + let client = match connector.downcast::() { + Ok(vm_connector) => ironrdp_futures::run_until_handover(credssp_finished, framed, *vm_connector).await?, + Err(err) => match err.downcast::() { + Ok(c) => *c, + Err(_) => { + return Err(connector::general_err!( + "connector is neither ClientConnector nor VmClientConnector" + )) + } + }, + }; + + Ok(client) +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index bede93715..b162921f4 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -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; diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index b912d2685..a1d4fc55f 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -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}; diff --git a/web-client/iron-remote-desktop-rdp/src/main.ts b/web-client/iron-remote-desktop-rdp/src/main.ts index e50b0e1de..69f50f069 100644 --- a/web-client/iron-remote-desktop-rdp/src/main.ts +++ b/web-client/iron-remote-desktop-rdp/src/main.ts @@ -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); +} diff --git a/web-client/iron-svelte-client/src/lib/login/login.svelte b/web-client/iron-svelte-client/src/lib/login/login.svelte index 22e725cdb..7f0d895ad 100644 --- a/web-client/iron-svelte-client/src/lib/login/login.svelte +++ b/web-client/iron-svelte-client/src/lib/login/login.svelte @@ -2,7 +2,7 @@ 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 +16,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 +132,10 @@ configBuilder.withExtension(preConnectionBlob(pcb)); } + if (vmconnect !== '') { + configBuilder.withExtension(vmConnect(vmconnect)); + } + if (kdc_proxy_url !== '') { configBuilder.withExtension(kdcProxyUrl(kdc_proxy_url)); } @@ -202,6 +207,10 @@ +
+ + +
From c653b51f0409e55bbff74b0e1c8328e09b730a89 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:57:02 -0400 Subject: [PATCH 2/6] fmt --- crates/ironrdp-vmconnect/src/credssp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ironrdp-vmconnect/src/credssp.rs b/crates/ironrdp-vmconnect/src/credssp.rs index 0e2d4385a..83e8c8207 100644 --- a/crates/ironrdp-vmconnect/src/credssp.rs +++ b/crates/ironrdp-vmconnect/src/credssp.rs @@ -55,7 +55,6 @@ impl CredsspSequenceTrait for VmCredsspSequence { } } - /// 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 { From 56ec5c7cc533043d4acdb9087be5cd810c284971 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 21:00:06 -0400 Subject: [PATCH 3/6] CI --- crates/ironrdp-connector/Cargo.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/ironrdp-connector/Cargo.toml b/crates/ironrdp-connector/Cargo.toml index c6e46492d..b74ee82b3 100644 --- a/crates/ironrdp-connector/Cargo.toml +++ b/crates/ironrdp-connector/Cargo.toml @@ -22,15 +22,11 @@ arbitrary = ["dep:arbitrary"] ironrdp-svc = { path = "../ironrdp-svc", version = "0.4" } # public ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public ironrdp-error = { path = "../ironrdp-error", version = "0.1" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = [ - "std", -] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = ["std"] } # public arbitrary = { version = "1", features = ["derive"], optional = true } # public sspi = "0.16" # public url = "2.5" # public -rand_core = { version = "0.6", features = [ - "std", -] } # TODO: dependency injection? +rand_core = { version = "0.6", features = ["std"] } # TODO: dependency injection? tracing = { version = "0.1", features = ["log"] } picky-asn1-der = "0.5" picky-asn1-x509 = "0.14" From c99216206b6d486baef7cbb397928f8ec6d94b8e Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 21:14:40 -0400 Subject: [PATCH 4/6] CI --- crates/ironrdp-client/src/rdp.rs | 14 +++++++------- crates/ironrdp-web/src/session.rs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 6b20be93f..cb92b345f 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -397,7 +397,7 @@ where }; debug_assert!(connector.next_pdu_hint().is_none()); - let (rdcleanpath_req, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb { let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( None, @@ -430,26 +430,26 @@ where (rdcleanpath_req, Box::new(connector) as Box) }; - let rdcleanpath_req = rdcleanpath_req + let rdcleanpath_request = rdcleanpath_request .to_der() .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; framed - .write_all(&rdcleanpath_req) + .write_all(&rdcleanpath_request) .await .map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?; - let rdcleanpath_res = framed + let rdcleanpath_result = framed .read_by_hint(&RDCLEANPATH_HINT) .await .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) + let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result) .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); + debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU"); - let (x224_connection_response, server_cert_chain) = match rdcleanpath_res + let (x224_connection_response, server_cert_chain) = match rdcleanpath_result .into_enum() .map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))? { diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index f0d76a2aa..06b395d62 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -1026,7 +1026,7 @@ where }; debug_assert!(connector.next_pdu_hint().is_none()); - let (rdcleanpath_req, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb { let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( None, @@ -1059,26 +1059,26 @@ where (rdcleanpath_req, Box::new(connector) as Box) }; - let rdcleanpath_req = rdcleanpath_req + let rdcleanpath_request = rdcleanpath_request .to_der() .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; framed - .write_all(&rdcleanpath_req) + .write_all(&rdcleanpath_request) .await .map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?; - let rdcleanpath_res = framed + let rdcleanpath_result = framed .read_by_hint(&RDCLEANPATH_HINT) .await .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) + let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result) .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); + debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU"); - let (x224_connection_response, server_cert_chain) = match rdcleanpath_res + let (x224_connection_response, server_cert_chain) = match rdcleanpath_result .into_enum() .map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))? { From 69264ff3e94bb8240755784aa0785ea52f91d13c Mon Sep 17 00:00:00 2001 From: irving ou Date: Sat, 5 Jul 2025 18:55:32 -0400 Subject: [PATCH 5/6] review fix --- web-client/iron-svelte-client/src/lib/login/login.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web-client/iron-svelte-client/src/lib/login/login.svelte b/web-client/iron-svelte-client/src/lib/login/login.svelte index 7f0d895ad..0afe5c67e 100644 --- a/web-client/iron-svelte-client/src/lib/login/login.svelte +++ b/web-client/iron-svelte-client/src/lib/login/login.svelte @@ -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, vmConnect } 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'; From 9996aa4614313e4bede689cb07c621fb3179dabb Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 9 Jul 2025 10:46:21 -0400 Subject: [PATCH 6/6] fix CI after rebase --- Cargo.lock | 54 +++-------------------- crates/ironrdp-connector/src/lib.rs | 4 +- crates/ironrdp-vmconnect/Cargo.toml | 2 +- crates/ironrdp-vmconnect/src/connector.rs | 2 +- crates/ironrdp-web/src/session.rs | 3 -- 5 files changed, 9 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f333abf4..f569fd9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1525,7 +1525,7 @@ dependencies = [ "ironrdp", "ironrdp-cliprdr-native", "ironrdp-core", - "sspi 0.16.0", + "sspi", "thiserror 1.0.69", "tracing", "tracing-subscriber", @@ -2360,7 +2360,7 @@ dependencies = [ "opus", "pico-args", "rand 0.8.5", - "sspi 0.16.0", + "sspi", "tokio-rustls", "tracing", "tracing-subscriber", @@ -2514,7 +2514,7 @@ dependencies = [ "picky-asn1-der", "picky-asn1-x509", "rand_core 0.6.4", - "sspi 0.16.0", + "sspi", "tracing", "url", ] @@ -2838,7 +2838,7 @@ dependencies = [ "ironrdp-async", "ironrdp-connector", "reqwest", - "sspi 0.16.0", + "sspi", "tokio", "url", ] @@ -2851,7 +2851,7 @@ dependencies = [ "ironrdp-connector", "ironrdp-core", "ironrdp-pdu", - "sspi 0.15.14", + "sspi", "tracing", ] @@ -5090,50 +5090,6 @@ dependencies = [ "der", ] -[[package]] -name = "sspi" -version = "0.15.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214ee905dcdd4b7ab11404b616e58dc6944d80fe8592fbdc13abc87d7e2bff0a" -dependencies = [ - "async-dnssd", - "async-recursion", - "bitflags 2.9.1", - "byteorder", - "cfg-if", - "crypto-mac", - "futures", - "hmac", - "lazy_static", - "md-5", - "md4", - "num-bigint-dig", - "num-derive", - "num-traits", - "oid", - "picky", - "picky-asn1", - "picky-asn1-der", - "picky-asn1-x509", - "picky-krb", - "rand 0.8.5", - "rsa", - "rustls", - "serde", - "serde_derive", - "sha1", - "sha2", - "time", - "tokio", - "tracing", - "url", - "uuid", - "windows 0.61.3", - "windows-registry", - "windows-sys 0.60.2", - "zeroize", -] - [[package]] name = "sspi" version = "0.16.0" diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index ad162a55f..6b32728e3 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -22,7 +22,7 @@ use core::fmt; use std::sync::Arc; use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf}; -use ironrdp_pdu::nego::{NegoRequestData, SecurityProtocol}; +use ironrdp_pdu::nego::SecurityProtocol; use ironrdp_pdu::rdp::capability_sets::{self, BitmapCodecs}; use ironrdp_pdu::rdp::client_info::PerformanceFlags; use ironrdp_pdu::x224::X224; @@ -177,7 +177,7 @@ pub struct Config { /// /// - A cookie containing the username for a username/password. /// - Nothing for a smart card. - pub request_data: Option, + pub request_data: Option, /// If true, the INFO_AUTOLOGON flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu) pub autologon: bool, /// If true, the INFO_NOAUDIOPLAYBACK flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu) diff --git a/crates/ironrdp-vmconnect/Cargo.toml b/crates/ironrdp-vmconnect/Cargo.toml index 5f7448806..691810a16 100644 --- a/crates/ironrdp-vmconnect/Cargo.toml +++ b/crates/ironrdp-vmconnect/Cargo.toml @@ -16,7 +16,7 @@ ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = [ ] } # public arbitrary = { version = "1", features = ["derive"], optional = true } # public -sspi = "0.15" # public +sspi = "0.16" # public tracing = { version = "0.1", features = ["log"] } ironrdp-connector = { path = "../ironrdp-connector" } # public diff --git a/crates/ironrdp-vmconnect/src/connector.rs b/crates/ironrdp-vmconnect/src/connector.rs index f47cb9ad1..c867925d1 100644 --- a/crates/ironrdp-vmconnect/src/connector.rs +++ b/crates/ironrdp-vmconnect/src/connector.rs @@ -235,7 +235,7 @@ impl CredsspSequenceFactory for VmClientConnector { server_public_key: Vec, _kerberos_config: Option, ) -> ConnectorResult<( - Box, + Box<(dyn ironrdp_connector::credssp::CredsspSequenceTrait + 'static)>, sspi::credssp::TsRequest, )> { let credentials = crate::config::Credentials::try_from(&credentials)?; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 06b395d62..51a5d7e0e 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -1099,9 +1099,6 @@ where 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)?;