From f79954cc871b61c0eabd885e292aa5435ae8c3e6 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 15 May 2025 17:16:22 -0400 Subject: [PATCH 01/10] WIP add PDU support --- crates/ironrdp-client/src/config.rs | 6 ++++++ crates/ironrdp-client/src/rdp.rs | 21 ++++++++++++++++++++- crates/ironrdp-testsuite-core/tests/pcb.rs | 11 ++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index 564a22fef..efcceb722 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -21,6 +21,7 @@ pub struct Config { pub connector: connector::Config, pub clipboard_type: ClipboardType, pub rdcleanpath: Option, + pub pcb: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -238,6 +239,10 @@ struct Args { /// The bitmap codecs to use (remotefx:on, ...) #[clap(long, value_parser, num_args = 1.., value_delimiter = ',')] codecs: Vec, + + /// The Preconnection Blob + #[clap(long)] + pcb: Option, } impl Config { @@ -357,6 +362,7 @@ impl Config { connector, clipboard_type, rdcleanpath, + pcb: args.pcb, }) } } diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 3f950d1cf..b56ef50d5 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -1,6 +1,7 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; +use ironrdp::core::Encode; use ironrdp::displaycontrol::client::DisplayControlClient; use ironrdp::displaycontrol::pdu::MonitorLayoutEntry; use ironrdp::graphics::image_processing::PixelFormat; @@ -8,7 +9,7 @@ use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; -use ironrdp_core::WriteBuf; +use ironrdp_core::{WriteBuf, WriteCursor}; use ironrdp_rdpsnd_native::cpal; use ironrdp_tokio::reqwest::ReqwestNetworkClient; use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite}; @@ -146,6 +147,24 @@ async fn connect( connector.attach_static_channel(cliprdr); } + if let Some(pcb) = &config.pcb { + let pdu = ironrdp::pdu::pcb::PreconnectionBlob { + id: 0, + version: ironrdp::pdu::pcb::PcbVersion::V2, + v2_payload: Some(pcb.to_owned()), + }; + + let mut encoded: Vec<_> = Vec::new(); + let mut cursor = WriteCursor::new(&mut encoded); + pdu.encode(&mut cursor) + .map_err(|e| connector::custom_err!("encode PreconnectionBlob", e))?; + + framed + .write_all(&encoded) + .await + .map_err(|e| connector::custom_err!("couldn’t write PreconnectionBlob", e))?; + } + let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?; debug!("TLS upgrade"); diff --git a/crates/ironrdp-testsuite-core/tests/pcb.rs b/crates/ironrdp-testsuite-core/tests/pcb.rs index c31dd812f..546a57335 100644 --- a/crates/ironrdp-testsuite-core/tests/pcb.rs +++ b/crates/ironrdp-testsuite-core/tests/pcb.rs @@ -84,6 +84,15 @@ encode_decode_test! { "004b00770056004d0048004300660059007400650036004900330066004c006100590031005f006200330053007200", "77005800490057006a006e00350041000000" )).expect("pcb_v2_with_jwt payload"); + v2_hyperv_guid : + PreconnectionBlob { + version: PcbVersion::V2, + id: 0, + v2_payload: Some(String::from("ff995b48-a34f-404a-938f-303e4bb5bf31")), + }, + hex::decode(concat!( + "5c0000000000000002000000000000002500660066003900390035006200340038002d0061003300340066002d0034003000340061002d0039003300380066002d003300300033006500340062006200350062006600330031000000" + )).expect("v2_hyperv_guid payload"); } const PRECONNECTION_PDU_V1_NULL_SIZE_BUF: [u8; 16] = [ @@ -165,4 +174,4 @@ fn pcb_v2_string_too_big() { } "#]] .assert_debug_eq(&e); -} +} \ No newline at end of file From 41ad74dbf130066eb68dff3208e02679ad19babf Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 28 May 2025 15:05:30 -0400 Subject: [PATCH 02/10] cargo conflict --- crates/ironrdp-client/src/config.rs | 40 ++++++++++++---------- crates/ironrdp-client/src/rdp.rs | 17 --------- crates/ironrdp-connector/src/connection.rs | 19 +++++++++- crates/ironrdp-connector/src/credssp.rs | 19 +++++----- crates/ironrdp-connector/src/lib.rs | 9 +++++ crates/ironrdp-core/src/cursor.rs | 6 ++++ crates/ironrdp-web/src/session.rs | 2 ++ ffi/src/connector/config.rs | 2 ++ 8 files changed, 70 insertions(+), 44 deletions(-) diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index efcceb722..9f4c26a91 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -21,7 +21,6 @@ pub struct Config { pub connector: connector::Config, pub clipboard_type: ClipboardType, pub rdcleanpath: Option, - pub pcb: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -258,21 +257,6 @@ impl Config { .pipe(Destination::new)? }; - let username = if let Some(username) = args.username { - username - } else { - inquire::Text::new("Username:").prompt().context("Username prompt")? - }; - - let password = if let Some(password) = args.password { - password - } else { - inquire::Password::new("Password:") - .without_confirmation() - .prompt() - .context("Password prompt")? - }; - let codecs: Vec<_> = args.codecs.iter().map(|s| s.as_str()).collect(); let codecs = match client_codecs_capabilities(&codecs) { Ok(codecs) => codecs, @@ -307,8 +291,28 @@ impl Config { args.clipboard_type }; + let credentials = if args.username.is_none() && args.password.is_none() { + Credentials::None + } else { + let username = args.username.unwrap_or_else(|| { + inquire::Text::new("Username:") + .prompt() + .context("Username prompt") + .unwrap_or_else(|_| "Administrator".to_owned()) + }); + + let password = args.password.unwrap_or_else(|| { + inquire::Password::new("Password:") + .prompt() + .context("Password prompt") + .unwrap_or_else(|_| "password".to_owned()) + }); + + Credentials::UsernamePassword { username, password } + }; + let connector = connector::Config { - credentials: Credentials::UsernamePassword { username, password }, + credentials, domain: args.domain, enable_tls: !args.no_tls, enable_credssp: !args.no_credssp, @@ -349,6 +353,7 @@ impl Config { request_data: None, pointer_software_rendering: true, performance_flags: PerformanceFlags::default(), + pcb: args.pcb, }; let rdcleanpath = args @@ -362,7 +367,6 @@ impl Config { connector, clipboard_type, rdcleanpath, - pcb: args.pcb, }) } } diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index b56ef50d5..4916bea7f 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -147,23 +147,6 @@ async fn connect( connector.attach_static_channel(cliprdr); } - if let Some(pcb) = &config.pcb { - let pdu = ironrdp::pdu::pcb::PreconnectionBlob { - id: 0, - version: ironrdp::pdu::pcb::PcbVersion::V2, - v2_payload: Some(pcb.to_owned()), - }; - - let mut encoded: Vec<_> = Vec::new(); - let mut cursor = WriteCursor::new(&mut encoded); - pdu.encode(&mut cursor) - .map_err(|e| connector::custom_err!("encode PreconnectionBlob", e))?; - - framed - .write_all(&encoded) - .await - .map_err(|e| connector::custom_err!("couldn’t write PreconnectionBlob", e))?; - } let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?; diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 1ee02fb29..4a6e91be1 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -214,7 +214,7 @@ impl Sequence for ClientConnector { //== Connection Initiation ==// // Exchange supported security protocols and a few other connection flags. - ClientConnectorState::ConnectionInitiationSendRequest => { + ClientConnectorState::ConnectionInitiationSendRequest => 'state: { debug!("Connection Initiation"); let mut security_protocol = nego::SecurityProtocol::empty(); @@ -240,6 +240,23 @@ impl Sequence for ClientConnector { return Err(reason_err!("Initiation", "standard RDP security is not supported",)); } + // If there's pcb, we send it in the first message. + if let Some(pcb) = &self.config.pcb { + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + v2_payload: Some(pcb.to_owned()), + }; + let written = ironrdp_core::encode_buf(&pcb, output).map_err(ConnectorError::encode)?; + + break 'state ( + Written::from_size(written)?, + ClientConnectorState::EnhancedSecurityUpgrade { + selected_protocol: security_protocol, + }, + ); + } + let connection_request = nego::ConnectionRequest { nego_data: self.config.request_data.clone().or_else(|| { self.config diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 0d2f8dec5..753a96cfd 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -5,7 +5,7 @@ use picky_asn1_x509::{oids, Certificate, ExtensionView, GeneralName}; use sspi::credssp::{self, ClientState, CredSspClient}; use sspi::generator::{Generator, NetworkRequest}; use sspi::negotiate::ProtocolConfig; -use sspi::Username; +use sspi::{AuthIdentity, Username}; use crate::{ConnectorError, ConnectorErrorKind, ConnectorResult, Credentials, ServerName, Written}; @@ -97,15 +97,17 @@ impl CredsspSequence { server_public_key: Vec, kerberos_config: Option, ) -> ConnectorResult<(Self, credssp::TsRequest)> { - let credentials: sspi::Credentials = match &credentials { + let credentials: Option = match &credentials { Credentials::UsernamePassword { username, password } => { let username = Username::new(username, domain).map_err(|e| custom_err!("invalid username", e))?; - sspi::AuthIdentity { - username, - password: password.to_owned().into(), - } - .into() + Some( + sspi::AuthIdentity { + username, + password: password.to_owned().into(), + } + .into(), + ) } Credentials::SmartCard { pin, config } => match config { Some(config) => { @@ -126,12 +128,13 @@ impl CredsspSequence { private_key_file_index: None, private_key: Some(key.into()), }; - sspi::Credentials::SmartCard(Box::new(identity)) + Some(sspi::Credentials::SmartCard(Box::new(identity))) } None => { return Err(general_err!("smart card configuration missing")); } }, + Credentials::None => None, }; let server_name = server_name.into_inner(); diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 8a4558df4..36b2f5a72 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -67,6 +67,7 @@ pub struct SmartCardIdentity { #[derive(Debug, Clone)] pub enum Credentials { + None, UsernamePassword { username: String, password: String, @@ -80,6 +81,7 @@ pub enum Credentials { impl Credentials { fn username(&self) -> Option<&str> { match self { + Self::None => None, Self::UsernamePassword { username, .. } => Some(username), Self::SmartCard { .. } => None, // Username is ultimately provided by the smart card certificate. } @@ -87,10 +89,15 @@ impl Credentials { fn secret(&self) -> &str { match self { + Self::None => "", Self::UsernamePassword { password, .. } => password, Self::SmartCard { pin, .. } => pin, } } + + fn is_none(&self) -> bool { + matches!(self, Self::None) + } } #[derive(Debug, Clone)] @@ -187,6 +194,8 @@ pub struct Config { pub no_server_pointer: bool, pub pointer_software_rendering: bool, pub performance_flags: PerformanceFlags, + + pub pcb: Option, } ironrdp_core::assert_impl!(Config: Send, Sync); diff --git a/crates/ironrdp-core/src/cursor.rs b/crates/ironrdp-core/src/cursor.rs index 4501377f8..2ddf8db45 100644 --- a/crates/ironrdp-core/src/cursor.rs +++ b/crates/ironrdp-core/src/cursor.rs @@ -578,6 +578,12 @@ impl<'a> WriteCursor<'a> { self.pos } + /// Returns the number of bytes written. + #[inline] + pub const fn bytes_written(&self) -> usize { + self.pos + } + /// Write an array of bytes to the buffer. #[inline] #[track_caller] diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 39b773a60..09fb32105 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -861,6 +861,8 @@ fn build_config( desktop_scale_factor: 0, hardware_id: None, license_cache: None, + // TODO: implement this + pcb: None, } } diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index ac49ae3c6..e7676ce66 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -200,6 +200,8 @@ pub mod ffi { desktop_scale_factor: 0, hardware_id: None, license_cache: None, + // TODO: implement this + pcb: None, }; tracing::debug!(config=?inner_config, "Built config"); Ok(Box::new(Config(inner_config))) From 64f4f3cc1a25a41c41a64231aa64291318881d4f Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 26 May 2025 15:46:03 -0400 Subject: [PATCH 03/10] WIP, not working --- crates/ironrdp-async/src/connector.rs | 1 + crates/ironrdp-blocking/src/connector.rs | 1 + crates/ironrdp-client/src/config.rs | 6 +- crates/ironrdp-client/src/rdp.rs | 5 +- crates/ironrdp-connector/src/connection.rs | 121 +++++++++++++++++---- crates/ironrdp-connector/src/credssp.rs | 51 ++++++--- crates/ironrdp-connector/src/lib.rs | 2 +- crates/ironrdp-pdu/src/rdp/client_info.rs | 5 + crates/ironrdp-web/src/session.rs | 2 +- ffi/src/connector/config.rs | 2 +- ffi/src/connector/state.rs | 2 +- ffi/src/credssp/mod.rs | 2 + 12 files changed, 152 insertions(+), 48 deletions(-) diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index 4d1b085dc..c50b97728 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -132,6 +132,7 @@ where server_name, server_public_key, kerberos_config, + connector.config.vmconnect.is_some(), )?; loop { diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index 765560e6e..c6cee9573 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -137,6 +137,7 @@ where server_name, server_public_key, kerberos_config, + connector.config.vmconnect.is_some(), )?; loop { diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index 9f4c26a91..c041f386a 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -239,9 +239,9 @@ struct Args { #[clap(long, value_parser, num_args = 1.., value_delimiter = ',')] codecs: Vec, - /// The Preconnection Blob + /// The Virtual Machine ID for Hyper-V, used as Pre Connection Blob #[clap(long)] - pcb: Option, + vmconnect: Option, } impl Config { @@ -353,7 +353,7 @@ impl Config { request_data: None, pointer_software_rendering: true, performance_flags: PerformanceFlags::default(), - pcb: args.pcb, + vmconnect: args.vmconnect, }; let rdcleanpath = args diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 4916bea7f..9bee6a903 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -147,10 +147,9 @@ async fn connect( connector.attach_static_channel(cliprdr); } - let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?; - debug!("TLS upgrade"); + debug!(destination = ?config.destination,"TLS upgrade"); // Ensure there is no leftover let (initial_stream, leftover_bytes) = framed.into_inner(); @@ -294,7 +293,7 @@ where { // RDCleanPath request - let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else { + let connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } = connector.state else { return Err(connector::general_err!("invalid connector state (send request)")); }; diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 4a6e91be1..90c885f06 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -36,7 +36,10 @@ pub enum ClientConnectorState { #[default] Consumed, - ConnectionInitiationSendRequest, + PreconnectionBlob, + ConnectionInitiationSendRequest { + selected_protocol: Option, + }, ConnectionInitiationWaitConfirm { requested_protocol: nego::SecurityProtocol, }, @@ -88,7 +91,8 @@ impl State for ClientConnectorState { fn name(&self) -> &'static str { match self { Self::Consumed => "Consumed", - Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest", + Self::PreconnectionBlob => "PreconnectionBlob", + Self::ConnectionInitiationSendRequest { .. } => "ConnectionInitiationSendRequest", Self::ConnectionInitiationWaitConfirm { .. } => "ConnectionInitiationWaitResponse", Self::EnhancedSecurityUpgrade { .. } => "EnhancedSecurityUpgrade", Self::Credssp { .. } => "Credssp", @@ -132,7 +136,7 @@ impl ClientConnector { pub fn new(config: Config, client_addr: SocketAddr) -> Self { Self { config, - state: ClientConnectorState::ConnectionInitiationSendRequest, + state: ClientConnectorState::PreconnectionBlob, client_addr, static_channels: StaticChannelSet::new(), } @@ -180,7 +184,8 @@ impl Sequence for ClientConnector { fn next_pdu_hint(&self) -> Option<&dyn PduHint> { match &self.state { ClientConnectorState::Consumed => None, - ClientConnectorState::ConnectionInitiationSendRequest => None, + ClientConnectorState::PreconnectionBlob => None, + ClientConnectorState::ConnectionInitiationSendRequest { .. } => None, ClientConnectorState::ConnectionInitiationWaitConfirm { .. } => Some(&ironrdp_pdu::X224_HINT), ClientConnectorState::EnhancedSecurityUpgrade { .. } => None, ClientConnectorState::Credssp { .. } => None, @@ -211,14 +216,28 @@ impl Sequence for ClientConnector { ClientConnectorState::Consumed => { return Err(general_err!("connector sequence state is consumed (this is a bug)",)) } + ClientConnectorState::PreconnectionBlob => 'state: { + let Some(connection_id) = self.config.vmconnect.as_ref() else { + break 'state ( + Written::Nothing, + ClientConnectorState::ConnectionInitiationSendRequest { + selected_protocol: None, + }, + ); + }; - //== Connection Initiation ==// - // Exchange supported security protocols and a few other connection flags. - ClientConnectorState::ConnectionInitiationSendRequest => 'state: { - debug!("Connection Initiation"); + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + // v2_payload: Some(connection_id.to_owned()), + v2_payload: Some(format!("{connection_id};EnhancedMode=1").into()), + }; + + debug!(message = ?pcb, "Send"); - let mut security_protocol = nego::SecurityProtocol::empty(); + let written = ironrdp_core::encode_buf(&pcb, output).map_err(ConnectorError::encode)?; + let mut security_protocol = nego::SecurityProtocol::empty(); if self.config.enable_tls { security_protocol.insert(nego::SecurityProtocol::SSL); } @@ -240,23 +259,66 @@ impl Sequence for ClientConnector { return Err(reason_err!("Initiation", "standard RDP security is not supported",)); } - // If there's pcb, we send it in the first message. - if let Some(pcb) = &self.config.pcb { - let pcb = ironrdp_pdu::pcb::PreconnectionBlob { - version: ironrdp_pdu::pcb::PcbVersion::V2, - id: 0, - v2_payload: Some(pcb.to_owned()), + break 'state ( + Written::from_size(written)?, + ClientConnectorState::EnhancedSecurityUpgrade { + selected_protocol: security_protocol, + }, + ); + } + //== Connection Initiation ==// + // Exchange supported security protocols and a few other connection flags. + ClientConnectorState::ConnectionInitiationSendRequest { selected_protocol } => 'state: { + debug!("Connection Initiation"); + + if self.config.vmconnect.is_some() { + let selected_protocol = selected_protocol.ok_or(reason_err!( + "Initiation", + "VMConnect requires a selected protocol at ConnectionInitiationSendRequest state", + ))?; + + let connection_request = nego::ConnectionRequest { + nego_data: None, + flags: nego::RequestFlags::empty(), + protocol: selected_protocol, }; - let written = ironrdp_core::encode_buf(&pcb, output).map_err(ConnectorError::encode)?; + + debug!(message = ?connection_request, "Send"); + + let written = + ironrdp_core::encode_buf(&X224(connection_request), output).map_err(ConnectorError::encode)?; break 'state ( Written::from_size(written)?, - ClientConnectorState::EnhancedSecurityUpgrade { - selected_protocol: security_protocol, + ClientConnectorState::ConnectionInitiationWaitConfirm { + requested_protocol: selected_protocol, }, ); } + let mut security_protocol = nego::SecurityProtocol::empty(); + + if self.config.enable_tls { + security_protocol.insert(nego::SecurityProtocol::SSL); + } + + if self.config.enable_credssp { + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/902b090b-9cb3-4efc-92bf-ee13373371e3 + // The spec is stating that `PROTOCOL_SSL` "SHOULD" also be set when using `PROTOCOL_HYBRID`. + // > PROTOCOL_HYBRID (0x00000002) + // > Credential Security Support Provider protocol (CredSSP) (section 5.4.5.2). + // > If this flag is set, then the PROTOCOL_SSL (0x00000001) flag SHOULD also be set + // > because Transport Layer Security (TLS) is a subset of CredSSP. + // However, crucially, it’s not strictly required (not "MUST"). + // In fact, we purposefully choose to not set `PROTOCOL_SSL` unless `enable_winlogon` is `true`. + // This tells the server that we are not going to accept downgrading NLA to TLS security. + security_protocol.insert(nego::SecurityProtocol::HYBRID | nego::SecurityProtocol::HYBRID_EX); + } + + if security_protocol.is_standard_rdp_security() { + return Err(reason_err!("Initiation", "standard RDP security is not supported",)); + } + let connection_request = nego::ConnectionRequest { nego_data: self.config.request_data.clone().or_else(|| { self.config @@ -306,7 +368,10 @@ impl Sequence for ClientConnector { ( Written::Nothing, - ClientConnectorState::EnhancedSecurityUpgrade { selected_protocol }, + match self.config.vmconnect.is_some() { + false => ClientConnectorState::EnhancedSecurityUpgrade { selected_protocol }, + true => ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }, + }, ) } @@ -328,10 +393,20 @@ impl Sequence for ClientConnector { } //== CredSSP ==// - ClientConnectorState::Credssp { selected_protocol } => ( - Written::Nothing, - ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }, - ), + ClientConnectorState::Credssp { selected_protocol } => 'state: { + if self.config.vmconnect.is_some() { + break 'state ( + Written::Nothing, + ClientConnectorState::ConnectionInitiationSendRequest { + selected_protocol: Some(selected_protocol), + }, + ); + } + ( + Written::Nothing, + ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }, + ) + } //== Basic Settings Exchange ==// // Exchange basic settings including Core Data, Security Data and Network Data. diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 753a96cfd..37952cef9 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -70,6 +70,7 @@ pub struct CredsspSequence { client: CredSspClient, state: CredsspState, selected_protocol: nego::SecurityProtocol, + vmconnect: bool, } #[derive(Debug, PartialEq)] @@ -96,18 +97,17 @@ impl CredsspSequence { server_name: ServerName, server_public_key: Vec, kerberos_config: Option, + vmconnect: bool, ) -> ConnectorResult<(Self, credssp::TsRequest)> { - let credentials: Option = match &credentials { + let credentials: sspi::Credentials = match &credentials { Credentials::UsernamePassword { username, password } => { let username = Username::new(username, domain).map_err(|e| custom_err!("invalid username", e))?; - Some( - sspi::AuthIdentity { - username, - password: password.to_owned().into(), - } - .into(), - ) + sspi::AuthIdentity { + username, + password: password.to_owned().into(), + } + .into() } Credentials::SmartCard { pin, config } => match config { Some(config) => { @@ -128,13 +128,17 @@ impl CredsspSequence { private_key_file_index: None, private_key: Some(key.into()), }; - Some(sspi::Credentials::SmartCard(Box::new(identity))) + sspi::Credentials::SmartCard(Box::new(identity)) } None => { return Err(general_err!("smart card configuration missing")); } }, - Credentials::None => None, + Credentials::None => sspi::AuthIdentity { + username: Username::new("", None).map_err(|e| custom_err!("invalid username", e))?, + password: String::new().into(), + } + .into(), }; let server_name = server_name.into_inner(); @@ -151,7 +155,7 @@ impl CredsspSequence { let client = CredSspClient::new( server_public_key, - credentials, + Some(credentials), credssp::CredSspMode::WithCredentials, credssp::ClientMode::Negotiate(sspi::NegotiateConfig { protocol_config: credssp_config, @@ -166,6 +170,7 @@ impl CredsspSequence { client, state: CredsspState::Ongoing, selected_protocol: protocol, + vmconnect, }; let initial_request = credssp::TsRequest::default(); @@ -213,12 +218,28 @@ impl CredsspSequence { 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 + // }, + // ), ClientState::FinalMessage(ts_request) => ( ts_request, - if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) { - CredsspState::EarlyUserAuthResult - } else { - CredsspState::Finished + // @Irving: I don't understand the security protocol, and how it interfares with the vmconnect + // So I'll just proceed to make VM Connect work for now, remind me about this when you see this + // comment in Pull Request + match ( + self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX), + self.vmconnect, + ) { + (true, false) => CredsspState::EarlyUserAuthResult, + // (false, false) => CredsspState::Finished, + // (true, true) => CredsspState::Finished, + // (false, true) => CredsspState::Finished, + _ => CredsspState::Finished, }, ), }; diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 36b2f5a72..8a6294f32 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -195,7 +195,7 @@ pub struct Config { pub pointer_software_rendering: bool, pub performance_flags: PerformanceFlags, - pub pcb: Option, + pub vmconnect: Option, } ironrdp_core::assert_impl!(Config: Send, Sync); diff --git a/crates/ironrdp-pdu/src/rdp/client_info.rs b/crates/ironrdp-pdu/src/rdp/client_info.rs index dff21f04f..f550df4e6 100644 --- a/crates/ironrdp-pdu/src/rdp/client_info.rs +++ b/crates/ironrdp-pdu/src/rdp/client_info.rs @@ -313,6 +313,9 @@ impl Encode for ExtendedClientOptionalInfo { dst.write_array(reconnect_cookie); } + dst.write_u16(0); // reserved1 + dst.write_u16(0); // reserved2 + Ok(()) } @@ -336,6 +339,8 @@ impl Encode for ExtendedClientOptionalInfo { size += RECONNECT_COOKIE_LENGTH_SIZE + RECONNECT_COOKIE_LEN; } + size += 2 * 2; // reserved1 and reserved2 + size } } diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 09fb32105..9a335adad 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -862,7 +862,7 @@ fn build_config( hardware_id: None, license_cache: None, // TODO: implement this - pcb: None, + vmconnect: None, } } diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index e7676ce66..fc11bf5a8 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -201,7 +201,7 @@ pub mod ffi { hardware_id: None, license_cache: None, // TODO: implement this - pcb: None, + vmconnect: None, }; tracing::debug!(config=?inner_config, "Built config"); Ok(Box::new(Config(inner_config))) diff --git a/ffi/src/connector/state.rs b/ffi/src/connector/state.rs index 97ccfeae1..70ff3deee 100644 --- a/ffi/src/connector/state.rs +++ b/ffi/src/connector/state.rs @@ -33,7 +33,7 @@ pub mod ffi { .ok_or_else(|| ValueConsumedError::for_item("ClientConnectorState"))? { ironrdp::connector::ClientConnectorState::Consumed => ClientConnectorStateType::Consumed, - ironrdp::connector::ClientConnectorState::ConnectionInitiationSendRequest => { + ironrdp::connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } => { ClientConnectorStateType::ConnectionInitiationSendRequest } ironrdp::connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } => { diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index b912d2685..0825de28b 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -68,6 +68,8 @@ pub mod ffi { server_name.into(), server_public_key.to_owned(), kerbero_configs.map(|config| config.0.clone()), + // @Irving: TODO, enable vmconnect for FFI + false, )?; Ok(Box::new(CredsspSequenceInitResult { From 9ebe578333684cab16ddf74d651163dbf2624017 Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 28 May 2025 17:11:45 -0400 Subject: [PATCH 04/10] clean up --- Cargo.lock | 2 -- Cargo.toml | 1 + crates/ironrdp-connector/src/credssp.rs | 2 +- crates/ironrdp-testsuite-extra/tests/tests.rs | 1 + crates/ironrdp-web/src/session.rs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb1bc6d35..4e00173de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4951,8 +4951,6 @@ dependencies = [ [[package]] name = "sspi" version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27fb016d04750b9ce0b4576bfcdb978cf26a234536f2f6a7697b703e0f5bc048" dependencies = [ "async-dnssd", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 004d84ddb..4128166fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,3 +149,4 @@ opt-level = 3 # FIXME: We need to catch up with Diplomat upstream again, but this is a significant amount of work. # In the meantime, we use this forked version which fixes an undefined behavior in the code expanded by the bridge macro. diplomat = { git = "https://github.com/CBenoit/diplomat", rev = "6dc806e80162b6b39509a04a2835744236cd2396" } +sspi = {path="../sspi-rs"} \ No newline at end of file diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 37952cef9..38f5c0db6 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -155,7 +155,7 @@ impl CredsspSequence { let client = CredSspClient::new( server_public_key, - Some(credentials), + credentials, credssp::CredSspMode::WithCredentials, credssp::ClientMode::Negotiate(sspi::NegotiateConfig { protocol_config: credssp_config, diff --git a/crates/ironrdp-testsuite-extra/tests/tests.rs b/crates/ironrdp-testsuite-extra/tests/tests.rs index 53cbfc118..2aa108218 100644 --- a/crates/ironrdp-testsuite-extra/tests/tests.rs +++ b/crates/ironrdp-testsuite-extra/tests/tests.rs @@ -302,5 +302,6 @@ fn default_client_config() -> connector::Config { no_server_pointer: true, pointer_software_rendering: true, performance_flags: Default::default(), + vmconnect: None, } } diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 9a335adad..1e1113207 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -991,7 +991,7 @@ where { // RDCleanPath request - let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else { + let connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } = connector.state else { return Err(anyhow::Error::msg("invalid connector state (send request)").into()); }; From 84745e5ef2d6b301f7f521616924d054915a3514 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 29 May 2025 13:51:45 -0400 Subject: [PATCH 05/10] clean up --- crates/ironrdp-client/src/config.rs | 34 +++++++++++-------------- crates/ironrdp-client/src/rdp.rs | 3 +-- crates/ironrdp-connector/src/credssp.rs | 7 +---- crates/ironrdp-connector/src/lib.rs | 7 ----- ffi/src/connector/config.rs | 8 ++++-- ffi/src/credssp/mod.rs | 4 +-- 6 files changed, 25 insertions(+), 38 deletions(-) diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index c041f386a..c7511433f 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -291,25 +291,21 @@ impl Config { args.clipboard_type }; - let credentials = if args.username.is_none() && args.password.is_none() { - Credentials::None - } else { - let username = args.username.unwrap_or_else(|| { - inquire::Text::new("Username:") - .prompt() - .context("Username prompt") - .unwrap_or_else(|_| "Administrator".to_owned()) - }); - - let password = args.password.unwrap_or_else(|| { - inquire::Password::new("Password:") - .prompt() - .context("Password prompt") - .unwrap_or_else(|_| "password".to_owned()) - }); - - Credentials::UsernamePassword { username, password } - }; + let username = args.username.unwrap_or_else(|| { + inquire::Text::new("Username:") + .prompt() + .context("Username prompt") + .unwrap_or_else(|_| "Administrator".to_owned()) + }); + + let password = args.password.unwrap_or_else(|| { + inquire::Password::new("Password:") + .prompt() + .context("Password prompt") + .unwrap_or_else(|_| "password".to_owned()) + }); + + let credentials = Credentials::UsernamePassword { username, password }; let connector = connector::Config { credentials, diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 9bee6a903..462eb3329 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -1,7 +1,6 @@ use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::{ConnectionResult, ConnectorResult}; -use ironrdp::core::Encode; use ironrdp::displaycontrol::client::DisplayControlClient; use ironrdp::displaycontrol::pdu::MonitorLayoutEntry; use ironrdp::graphics::image_processing::PixelFormat; @@ -9,7 +8,7 @@ use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult}; use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session}; -use ironrdp_core::{WriteBuf, WriteCursor}; +use ironrdp_core::WriteBuf; use ironrdp_rdpsnd_native::cpal; use ironrdp_tokio::reqwest::ReqwestNetworkClient; use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite}; diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 38f5c0db6..e87aeac40 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -5,7 +5,7 @@ use picky_asn1_x509::{oids, Certificate, ExtensionView, GeneralName}; use sspi::credssp::{self, ClientState, CredSspClient}; use sspi::generator::{Generator, NetworkRequest}; use sspi::negotiate::ProtocolConfig; -use sspi::{AuthIdentity, Username}; +use sspi::Username; use crate::{ConnectorError, ConnectorErrorKind, ConnectorResult, Credentials, ServerName, Written}; @@ -134,11 +134,6 @@ impl CredsspSequence { return Err(general_err!("smart card configuration missing")); } }, - Credentials::None => sspi::AuthIdentity { - username: Username::new("", None).map_err(|e| custom_err!("invalid username", e))?, - password: String::new().into(), - } - .into(), }; let server_name = server_name.into_inner(); diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 8a6294f32..cd8e1796a 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -67,7 +67,6 @@ pub struct SmartCardIdentity { #[derive(Debug, Clone)] pub enum Credentials { - None, UsernamePassword { username: String, password: String, @@ -81,7 +80,6 @@ pub enum Credentials { impl Credentials { fn username(&self) -> Option<&str> { match self { - Self::None => None, Self::UsernamePassword { username, .. } => Some(username), Self::SmartCard { .. } => None, // Username is ultimately provided by the smart card certificate. } @@ -89,15 +87,10 @@ impl Credentials { fn secret(&self) -> &str { match self { - Self::None => "", Self::UsernamePassword { password, .. } => password, Self::SmartCard { pin, .. } => pin, } } - - fn is_none(&self) -> bool { - matches!(self, Self::None) - } } #[derive(Debug, Clone)] diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index fc11bf5a8..922e262ea 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -42,6 +42,7 @@ pub mod ffi { pub no_audio_playback: Option, pub pointer_software_rendering: Option, pub performance_flags: Option, + pub vmconnect: Option, } #[diplomat::enum_convert(ironrdp::pdu::gcc::KeyboardType)] @@ -152,6 +153,10 @@ pub mod ffi { self.pointer_software_rendering = Some(pointer_software_rendering); } + pub fn set_vm_connect(&mut self, vmconnect: &str) { + self.vmconnect = Some(vmconnect.to_owned()); + } + pub fn build(&self) -> Result, Box> { let inner_config = ironrdp::connector::Config { credentials: self.credentials.clone().ok_or("credentials not set")?, @@ -200,8 +205,7 @@ pub mod ffi { desktop_scale_factor: 0, hardware_id: None, license_cache: None, - // TODO: implement this - vmconnect: None, + vmconnect: self.vmconnect.clone(), }; tracing::debug!(config=?inner_config, "Built config"); Ok(Box::new(Config(inner_config))) diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index 0825de28b..76bdd370b 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -54,6 +54,7 @@ pub mod ffi { server_name: &str, server_public_key: &[u8], kerbero_configs: Option<&KerberosConfig>, + vmconnect: bool, ) -> Result, Box> { let Some(connector) = connector.0.as_ref() else { return Err(ValueConsumedError::for_item("connector").into()); @@ -68,8 +69,7 @@ pub mod ffi { server_name.into(), server_public_key.to_owned(), kerbero_configs.map(|config| config.0.clone()), - // @Irving: TODO, enable vmconnect for FFI - false, + vmconnect, )?; Ok(Box::new(CredsspSequenceInitResult { From 4989e422929fcab0dc95d08fe999bae2e9d31966 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 29 May 2025 13:54:02 -0400 Subject: [PATCH 06/10] clean up --- Cargo.lock | 4 +++- Cargo.toml | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e00173de..102bbc29d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4950,7 +4950,9 @@ dependencies = [ [[package]] name = "sspi" -version = "0.15.6" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334391dc5b51751fd80c960b8a40dc8862b903ce41d3b848f154b72ac47ed119" dependencies = [ "async-dnssd", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 4128166fc..004d84ddb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,4 +149,3 @@ opt-level = 3 # FIXME: We need to catch up with Diplomat upstream again, but this is a significant amount of work. # In the meantime, we use this forked version which fixes an undefined behavior in the code expanded by the bridge macro. diplomat = { git = "https://github.com/CBenoit/diplomat", rev = "6dc806e80162b6b39509a04a2835744236cd2396" } -sspi = {path="../sspi-rs"} \ No newline at end of file From 43f09e618708808738d20d8537ef10164402e4e6 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 29 May 2025 14:00:21 -0400 Subject: [PATCH 07/10] clean up --- crates/ironrdp-connector/src/credssp.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index e87aeac40..f5df85e55 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -213,28 +213,12 @@ impl CredsspSequence { 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 - // }, - // ), ClientState::FinalMessage(ts_request) => ( ts_request, - // @Irving: I don't understand the security protocol, and how it interfares with the vmconnect - // So I'll just proceed to make VM Connect work for now, remind me about this when you see this - // comment in Pull Request - match ( - self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX), - self.vmconnect, - ) { - (true, false) => CredsspState::EarlyUserAuthResult, - // (false, false) => CredsspState::Finished, - // (true, true) => CredsspState::Finished, - // (false, true) => CredsspState::Finished, - _ => CredsspState::Finished, + if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) { + CredsspState::EarlyUserAuthResult + } else { + CredsspState::Finished }, ), }; From ba4537ced8123e647be554016efec69d9f3cda23 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 29 May 2025 14:02:32 -0400 Subject: [PATCH 08/10] fmt --- crates/ironrdp-connector/src/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 90c885f06..19f880ec2 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -232,7 +232,7 @@ impl Sequence for ClientConnector { // v2_payload: Some(connection_id.to_owned()), v2_payload: Some(format!("{connection_id};EnhancedMode=1").into()), }; - + debug!(message = ?pcb, "Send"); let written = ironrdp_core::encode_buf(&pcb, output).map_err(ConnectorError::encode)?; From 36a6217298cdd514c8cab02e99662edf9de97b4c Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 30 May 2025 12:03:17 -0400 Subject: [PATCH 09/10] WIP --- crates/ironrdp-web/src/session.rs | 41 ++++++++++++++++++++++++------- xtask/src/check.rs | 1 + 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 1e1113207..0a72b893d 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -55,7 +55,7 @@ struct SessionBuilderInner { password: Option, proxy_address: Option, auth_token: Option, - pcb: Option, + pcb: PreconnectionBlob, kdc_proxy_url: Option, client_name: String, desktop_size: DesktopSize, @@ -79,7 +79,7 @@ impl Default for SessionBuilderInner { password: None, proxy_address: None, auth_token: None, - pcb: None, + pcb: PreconnectionBlob::None, kdc_proxy_url: None, client_name: "ironrdp-web".to_owned(), desktop_size: DesktopSize { @@ -208,7 +208,7 @@ 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 = PreconnectionBlob::General(pcb) }; |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 }; } @@ -887,12 +887,29 @@ async fn writer_task(rx: mpsc::UnboundedReceiver>, rdp_writer: WriteHalf } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PreconnectionBlob { + None, + General(String), + VMConnect(String), +} + +impl Into> for PreconnectionBlob { + fn into(self) -> Option { + match self { + PreconnectionBlob::None => None, + PreconnectionBlob::General(blob) => Some(blob), + PreconnectionBlob::VMConnect(blob) => Some(blob), + } + } +} + struct ConnectParams { ws: WebSocket, config: connector::Config, proxy_auth_token: String, destination: String, - pcb: Option, + pcb: PreconnectionBlob, kdc_proxy_url: Option, clipboard_backend: Option, use_display_control: bool, @@ -958,7 +975,7 @@ async fn connect_rdcleanpath( connector: &mut ClientConnector, destination: String, proxy_auth_token: String, - pcb: Option, + pcb: PreconnectionBlob, ) -> Result<(ironrdp_futures::Upgraded, Vec), IronError> where S: ironrdp_futures::FramedRead + FramedWrite, @@ -991,9 +1008,15 @@ where { // RDCleanPath request - let connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } = connector.state else { - return Err(anyhow::Error::msg("invalid connector state (send request)").into()); - }; + match connector.state { + connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } + | connector::ClientConnectorState::PreconnectionBlob => { + // Valid states to send RDCleanPath request + } + _ => { + return Err(anyhow::Error::msg("invalid connector state (not send request)").into()); + } + } debug_assert!(connector.next_pdu_hint().is_none()); @@ -1003,7 +1026,7 @@ where let x224_pdu = buf.filled().to_vec(); let rdcleanpath_req = - ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb) + ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb.into()) .context("new RDCleanPath request")?; debug!(message = ?rdcleanpath_req, "Send RDCleanPath request"); let rdcleanpath_req = rdcleanpath_req.to_der().context("RDCleanPath request encode")?; diff --git a/xtask/src/check.rs b/xtask/src/check.rs index ed44a721e..bd3038827 100644 --- a/xtask/src/check.rs +++ b/xtask/src/check.rs @@ -9,6 +9,7 @@ pub fn fmt(sh: &Shell) -> anyhow::Result<()> { anyhow::bail!("Bad formatting, please run 'cargo +stable fmt --all'"); } + println!("All good!"); Ok(()) From 4376c6cf0bd95bc986cda39d379fe851ad461409 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 2 Jun 2025 11:11:17 -0400 Subject: [PATCH 10/10] WIP --- crates/ironrdp-client/src/rdp.rs | 37 ++++---- crates/ironrdp-connector/src/credssp.rs | 3 - crates/ironrdp-rdcleanpath/src/lib.rs | 22 +++-- .../tests/rdcleanpath.rs | 2 +- crates/ironrdp-web/src/session.rs | 92 +++++++++---------- .../src/lib/login/login.svelte | 4 +- 6 files changed, 83 insertions(+), 77 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 462eb3329..0169fc2f0 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -292,25 +292,30 @@ where { // RDCleanPath request - let connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } = connector.state else { - return Err(connector::general_err!("invalid connector state (send request)")); - }; + match connector.state { + connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } => { + 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_x224_request(x224_pdu, destination, proxy_auth_token) + .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))?; + rdcleanpath_req + } + connector::ClientConnectorState::PreconnectionBlob { .. } => {} + _ => { + return Err(connector::general_err!("invalid connector state (send request)")); + } + } debug_assert!(connector.next_pdu_hint().is_none()); - 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 diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index f5df85e55..0d2f8dec5 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -70,7 +70,6 @@ pub struct CredsspSequence { client: CredSspClient, state: CredsspState, selected_protocol: nego::SecurityProtocol, - vmconnect: bool, } #[derive(Debug, PartialEq)] @@ -97,7 +96,6 @@ impl CredsspSequence { server_name: ServerName, server_public_key: Vec, kerberos_config: Option, - vmconnect: bool, ) -> ConnectorResult<(Self, credssp::TsRequest)> { let credentials: sspi::Credentials = match &credentials { Credentials::UsernamePassword { username, password } => { @@ -165,7 +163,6 @@ impl CredsspSequence { client, state: CredsspState::Ongoing, selected_protocol: protocol, - vmconnect, }; let initial_request = credssp::TsRequest::default(); diff --git a/crates/ironrdp-rdcleanpath/src/lib.rs b/crates/ironrdp-rdcleanpath/src/lib.rs index 4294b5977..a6c4fb0e3 100644 --- a/crates/ironrdp-rdcleanpath/src/lib.rs +++ b/crates/ironrdp-rdcleanpath/src/lib.rs @@ -197,30 +197,34 @@ impl RDCleanPathPdu { } } - pub fn new_request( - x224_pdu: Vec, - destination: String, - proxy_auth: String, - pcb: Option, - ) -> der::Result { + pub fn new_x224_request(x224_pdu: Vec, destination: String, proxy_auth: String) -> der::Result { Ok(Self { version: VERSION_1, destination: Some(destination), proxy_auth: Some(proxy_auth), - preconnection_blob: pcb, x224_connection_pdu: Some(OctetString::new(x224_pdu)?), ..Self::default() }) } + pub fn new_pcb_request(preconnection_blob: String, destination: String, proxy_auth: String) -> der::Result { + Ok(Self { + version: VERSION_1, + destination: Some(destination), + proxy_auth: Some(proxy_auth), + preconnection_blob: Some(preconnection_blob), + ..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() diff --git a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs index 4c91f3b34..569d0d96b 100644 --- a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs +++ b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs @@ -2,7 +2,7 @@ use ironrdp_rdcleanpath::{DetectionResult, RDCleanPathPdu, VERSION_1}; use rstest::rstest; fn request() -> RDCleanPathPdu { - RDCleanPathPdu::new_request( + RDCleanPathPdu::new_x224_request( vec![0xDE, 0xAD, 0xBE, 0xFF], "destination".to_owned(), "proxy auth".to_owned(), diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 0a72b893d..168fe03fc 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -55,7 +55,7 @@ struct SessionBuilderInner { password: Option, proxy_address: Option, auth_token: Option, - pcb: PreconnectionBlob, + pcb: Option, kdc_proxy_url: Option, client_name: String, desktop_size: DesktopSize, @@ -79,7 +79,7 @@ impl Default for SessionBuilderInner { password: None, proxy_address: None, auth_token: None, - pcb: PreconnectionBlob::None, + pcb: None, kdc_proxy_url: None, client_name: "ironrdp-web".to_owned(), desktop_size: DesktopSize { @@ -208,7 +208,7 @@ 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 = PreconnectionBlob::General(pcb) }; + |pcb: String| { self.0.borrow_mut().pcb = Some(pcb) }; |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 }; } @@ -315,7 +315,6 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder { config, proxy_auth_token: auth_token, destination, - pcb, kdc_proxy_url, clipboard_backend: clipboard.as_ref().map(|clip| clip.backend()), use_display_control, @@ -887,29 +886,11 @@ async fn writer_task(rx: mpsc::UnboundedReceiver>, rdp_writer: WriteHalf } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PreconnectionBlob { - None, - General(String), - VMConnect(String), -} - -impl Into> for PreconnectionBlob { - fn into(self) -> Option { - match self { - PreconnectionBlob::None => None, - PreconnectionBlob::General(blob) => Some(blob), - PreconnectionBlob::VMConnect(blob) => Some(blob), - } - } -} - struct ConnectParams { ws: WebSocket, config: connector::Config, proxy_auth_token: String, destination: String, - pcb: PreconnectionBlob, kdc_proxy_url: Option, clipboard_backend: Option, use_display_control: bool, @@ -921,7 +902,6 @@ async fn connect( config, proxy_auth_token, destination, - pcb, kdc_proxy_url, clipboard_backend, use_display_control, @@ -945,7 +925,7 @@ async fn connect( } let (upgraded, server_public_key) = - connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?; + connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token).await?; let connection_result = ironrdp_futures::connect_finalize( upgraded, @@ -975,7 +955,6 @@ async fn connect_rdcleanpath( connector: &mut ClientConnector, destination: String, proxy_auth_token: String, - pcb: PreconnectionBlob, ) -> Result<(ironrdp_futures::Upgraded, Vec), IronError> where S: ironrdp_futures::FramedRead + FramedWrite, @@ -1005,36 +984,53 @@ where info!("Begin connection procedure"); + debug!(?connector, "Connector state before RDCleanPath request"); { - // RDCleanPath request + let rdcleanpath_req = match connector.state { + connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } => { + debug_assert!(connector.next_pdu_hint().is_none()); + + 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(); - match connector.state { - connector::ClientConnectorState::ConnectionInitiationSendRequest { .. } - | connector::ClientConnectorState::PreconnectionBlob => { - // Valid states to send RDCleanPath request + let rdcleanpath_req = + ironrdp_rdcleanpath::RDCleanPathPdu::new_x224_request(x224_pdu, destination, proxy_auth_token) + .context("new RDCleanPath request")?; + + rdcleanpath_req + } + connector::ClientConnectorState::PreconnectionBlob => { + debug_assert!(connector.next_pdu_hint().is_none()); + let written = connector.step_no_input(&mut buf)?; + let pcb_len = written.size().expect("written size"); + debug_assert_eq!(pcb_len, buf.filled_len()); + let preconnection_blob = buf.filled().to_vec(); + + let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_vmconnect_request( + preconnection_blob, + destination, + proxy_auth_token, + ) + .context("new RDCleanPath preconnection blob request")?; + + rdcleanpath_req } _ => { - return Err(anyhow::Error::msg("invalid connector state (not send request)").into()); + return Err(anyhow::Error::msg("invalid connector state (send request)").into()); } - } - - debug_assert!(connector.next_pdu_hint().is_none()); - - 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.into()) - .context("new RDCleanPath request")?; - debug!(message = ?rdcleanpath_req, "Send RDCleanPath request"); - let rdcleanpath_req = rdcleanpath_req.to_der().context("RDCleanPath request encode")?; + debug!(message = ?rdcleanpath_req, "Send RDCleanPath preconnection blob request"); + let rdcleanpath_req = rdcleanpath_req + .to_der() + .context("RDCleanPath preconnection blob request encode")?; framed .write_all(&rdcleanpath_req) .await - .context("couldn’t write RDCleanPath request")?; + .context("couldn’t write RDCleanPath preconnection blob request")?; } { @@ -1075,7 +1071,11 @@ where debug_assert!(connector.next_pdu_hint().is_some()); buf.clear(); - let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?; + + let written = match x224_connection_response { + Some(x224_connection_response) => connector.step(x224_connection_response.as_bytes(), &mut buf)?, + None => connector.step_no_input(&mut buf)?, + }; debug_assert!(written.is_nothing()); 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 7dc19cf05..0dc302a3d 100644 --- a/web-client/iron-svelte-client/src/lib/login/login.svelte +++ b/web-client/iron-svelte-client/src/lib/login/login.svelte @@ -11,12 +11,12 @@ let username = 'Administrator'; let password = 'DevoLabs123!'; let gatewayAddress = 'ws://localhost:7171/jet/rdp'; - let hostname = '10.10.0.3:3389'; + let hostname = import.meta.env.VITE_IRON_HOSTNAME ?? '10.10.0.3:3389'; let domain = ''; let authtoken = ''; let kdc_proxy_url = ''; let desktopSize = { width: 1280, height: 720 }; - let pcb = ''; + let pcb = import.meta.env.VITE_IRON_RDP_PCB ?? ''; let pop_up = false; let enable_clipboard = true;