mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-08-04 15:18:17 +00:00
Merge 082acb5582
into 4dc5945019
This commit is contained in:
commit
f99fd85994
26 changed files with 1207 additions and 365 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -2398,6 +2398,7 @@ dependencies = [
|
|||
"ironrdp-connector",
|
||||
"ironrdp-core",
|
||||
"ironrdp-pdu",
|
||||
"ironrdp-vmconnect",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
@ -2438,6 +2439,7 @@ dependencies = [
|
|||
"ironrdp-rdpsnd-native",
|
||||
"ironrdp-tls",
|
||||
"ironrdp-tokio",
|
||||
"ironrdp-vmconnect",
|
||||
"proc-exit",
|
||||
"raw-window-handle",
|
||||
"semver",
|
||||
|
@ -2815,6 +2817,18 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ironrdp-vmconnect"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"ironrdp-connector",
|
||||
"ironrdp-core",
|
||||
"ironrdp-pdu",
|
||||
"sspi",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ironrdp-web"
|
||||
version = "0.0.0"
|
||||
|
@ -2833,6 +2847,7 @@ dependencies = [
|
|||
"ironrdp-core",
|
||||
"ironrdp-futures",
|
||||
"ironrdp-rdcleanpath",
|
||||
"ironrdp-vmconnect",
|
||||
"js-sys",
|
||||
"png",
|
||||
"resize",
|
||||
|
|
|
@ -17,6 +17,7 @@ test = false
|
|||
|
||||
[dependencies]
|
||||
ironrdp-connector = { path = "../ironrdp-connector", version = "0.5" } # public
|
||||
ironrdp-vmconnect.path = "../ironrdp-vmconnect" # public
|
||||
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public
|
||||
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5" } # public
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
|
||||
use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig};
|
||||
use ironrdp_connector::sspi::credssp::ClientState;
|
||||
use ironrdp_connector::sspi::generator::GeneratorState;
|
||||
use ironrdp_connector::{
|
||||
custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
|
||||
ServerName, State as _,
|
||||
custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError,
|
||||
ConnectorResult, SecurityConnector, ServerName,
|
||||
};
|
||||
use ironrdp_core::WriteBuf;
|
||||
|
||||
|
@ -14,7 +14,10 @@ use crate::{single_sequence_step, AsyncNetworkClient};
|
|||
pub struct ShouldUpgrade;
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
|
||||
pub async fn connect_begin<S>(
|
||||
framed: &mut Framed<S>,
|
||||
connector: &mut dyn ConnectorCore,
|
||||
) -> ConnectorResult<ShouldUpgrade>
|
||||
where
|
||||
S: Sync + FramedRead + FramedWrite,
|
||||
{
|
||||
|
@ -29,7 +32,7 @@ where
|
|||
Ok(ShouldUpgrade)
|
||||
}
|
||||
|
||||
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
|
||||
pub fn skip_connect_begin(connector: &dyn SecurityConnector) -> ShouldUpgrade {
|
||||
assert!(connector.should_perform_security_upgrade());
|
||||
ShouldUpgrade
|
||||
}
|
||||
|
@ -38,22 +41,26 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
|
|||
pub struct Upgraded;
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
|
||||
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut dyn SecurityConnector) -> Upgraded {
|
||||
trace!("Marked as upgraded");
|
||||
connector.mark_security_upgrade_as_done();
|
||||
Upgraded
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn connect_finalize<S>(
|
||||
#[non_exhaustive]
|
||||
pub struct CredSSPFinished {
|
||||
pub(crate) write_buf: WriteBuf,
|
||||
}
|
||||
|
||||
pub async fn perform_credssp<S>(
|
||||
_: Upgraded,
|
||||
connector: &mut dyn ConnectorCore,
|
||||
framed: &mut Framed<S>,
|
||||
mut connector: ClientConnector,
|
||||
server_name: ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
network_client: Option<&mut dyn AsyncNetworkClient>,
|
||||
kerberos_config: Option<KerberosConfig>,
|
||||
) -> ConnectorResult<ConnectionResult>
|
||||
) -> ConnectorResult<CredSSPFinished>
|
||||
where
|
||||
S: FramedRead + FramedWrite,
|
||||
{
|
||||
|
@ -62,7 +69,7 @@ where
|
|||
if connector.should_perform_credssp() {
|
||||
perform_credssp_step(
|
||||
framed,
|
||||
&mut connector,
|
||||
connector,
|
||||
&mut buf,
|
||||
server_name,
|
||||
server_public_key,
|
||||
|
@ -72,6 +79,19 @@ where
|
|||
.await?;
|
||||
}
|
||||
|
||||
Ok(CredSSPFinished { write_buf: buf })
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn connect_finalize<S>(
|
||||
CredSSPFinished { write_buf: mut buf }: CredSSPFinished,
|
||||
framed: &mut Framed<S>,
|
||||
mut connector: ClientConnector,
|
||||
) -> ConnectorResult<ConnectionResult>
|
||||
where
|
||||
S: FramedRead + FramedWrite,
|
||||
{
|
||||
buf.clear();
|
||||
let result = loop {
|
||||
single_sequence_step(framed, &mut connector, &mut buf).await?;
|
||||
|
||||
|
@ -108,7 +128,7 @@ async fn resolve_generator(
|
|||
#[instrument(level = "trace", skip_all)]
|
||||
async fn perform_credssp_step<S>(
|
||||
framed: &mut Framed<S>,
|
||||
connector: &mut ClientConnector,
|
||||
connector: &mut dyn ConnectorCore,
|
||||
buf: &mut WriteBuf,
|
||||
server_name: ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
|
@ -120,70 +140,70 @@ where
|
|||
{
|
||||
assert!(connector.should_perform_credssp());
|
||||
|
||||
let selected_protocol = match connector.state {
|
||||
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
|
||||
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
|
||||
};
|
||||
let selected_protocol = connector
|
||||
.selected_protocol()
|
||||
.ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?;
|
||||
|
||||
let (mut sequence, mut ts_request) = CredsspSequence::init(
|
||||
connector.config.credentials.clone(),
|
||||
connector.config.domain.as_deref(),
|
||||
selected_protocol,
|
||||
server_name,
|
||||
server_public_key,
|
||||
kerberos_config,
|
||||
)?;
|
||||
{
|
||||
let (mut sequence, mut ts_request) = connector.init_credssp(
|
||||
connector.config().credentials.clone(),
|
||||
connector.config().domain.as_deref(),
|
||||
selected_protocol,
|
||||
server_name,
|
||||
server_public_key,
|
||||
kerberos_config,
|
||||
)?;
|
||||
|
||||
loop {
|
||||
let client_state = {
|
||||
let mut generator = sequence.process_ts_request(ts_request);
|
||||
loop {
|
||||
let client_state = {
|
||||
let mut generator = sequence.process_ts_request(ts_request);
|
||||
|
||||
if let Some(network_client_ref) = network_client.as_deref_mut() {
|
||||
trace!("resolving network");
|
||||
resolve_generator(&mut generator, network_client_ref).await?
|
||||
} else {
|
||||
generator
|
||||
.resolve_to_result()
|
||||
.map_err(|e| custom_err!("resolve without network client", e))?
|
||||
if let Some(network_client_ref) = network_client.as_deref_mut() {
|
||||
trace!("resolving network");
|
||||
resolve_generator(&mut generator, network_client_ref).await?
|
||||
} else {
|
||||
generator
|
||||
.resolve_to_result()
|
||||
.map_err(|e| custom_err!("resolve without network client", e))?
|
||||
}
|
||||
}; // drop generator
|
||||
|
||||
buf.clear();
|
||||
let written = sequence.handle_process_result(client_state, buf)?;
|
||||
|
||||
if let Some(response_len) = written.size() {
|
||||
let response = &buf[..response_len];
|
||||
trace!(response_len, "Send response");
|
||||
framed
|
||||
.write_all(response)
|
||||
.await
|
||||
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
|
||||
}
|
||||
}; // drop generator
|
||||
|
||||
buf.clear();
|
||||
let written = sequence.handle_process_result(client_state, buf)?;
|
||||
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Some(response_len) = written.size() {
|
||||
let response = &buf[..response_len];
|
||||
trace!(response_len, "Send response");
|
||||
framed
|
||||
.write_all(response)
|
||||
debug!(
|
||||
connector.state = connector.state().name(),
|
||||
hint = ?next_pdu_hint,
|
||||
"Wait for PDU"
|
||||
);
|
||||
|
||||
let pdu = framed
|
||||
.read_by_hint(next_pdu_hint)
|
||||
.await
|
||||
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
|
||||
}
|
||||
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
|
||||
|
||||
let Some(next_pdu_hint) = sequence.next_pdu_hint() else {
|
||||
break;
|
||||
};
|
||||
trace!(length = pdu.len(), "PDU received");
|
||||
|
||||
debug!(
|
||||
connector.state = connector.state.name(),
|
||||
hint = ?next_pdu_hint,
|
||||
"Wait for PDU"
|
||||
);
|
||||
|
||||
let pdu = framed
|
||||
.read_by_hint(next_pdu_hint)
|
||||
.await
|
||||
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;
|
||||
|
||||
trace!(length = pdu.len(), "PDU received");
|
||||
|
||||
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
|
||||
ts_request = next_request;
|
||||
} else {
|
||||
break;
|
||||
if let Some(next_request) = sequence.decode_server_message(&pdu)? {
|
||||
ts_request = next_request;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connector.mark_credssp_as_done();
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -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>(
|
||||
|
|
56
crates/ironrdp-async/src/vmconnector.rs
Normal file
56
crates/ironrdp-async/src/vmconnector.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::{single_sequence_step, CredSSPFinished, Framed, FramedRead, FramedWrite};
|
||||
use ironrdp_connector::{ClientConnector, ConnectorResult};
|
||||
use ironrdp_pdu::pcb::PcbVersion;
|
||||
|
||||
use ironrdp_vmconnect::VmClientConnector;
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct PcbSent;
|
||||
|
||||
pub async fn send_pcb<S>(framed: &mut Framed<S>, payload: String) -> ConnectorResult<PcbSent>
|
||||
where
|
||||
S: Sync + FramedRead + FramedWrite,
|
||||
{
|
||||
let pcb_pdu = ironrdp_pdu::pcb::PreconnectionBlob {
|
||||
id: 0,
|
||||
version: PcbVersion::V2,
|
||||
v2_payload: Some(payload),
|
||||
};
|
||||
|
||||
let buf = ironrdp_core::encode_vec(&pcb_pdu)
|
||||
.map_err(|e| ironrdp_connector::custom_err!("encode PreconnectionBlob PDU", e))?;
|
||||
|
||||
framed
|
||||
.write_all(&buf)
|
||||
.await
|
||||
.map_err(|e| ironrdp_connector::custom_err!("write PCB PDU", e))?;
|
||||
|
||||
Ok(PcbSent)
|
||||
}
|
||||
|
||||
pub fn mark_pcb_sent_by_rdclean_path() -> PcbSent {
|
||||
PcbSent
|
||||
}
|
||||
|
||||
pub fn vm_connector_take_over(_: PcbSent, connector: ClientConnector) -> ConnectorResult<VmClientConnector> {
|
||||
VmClientConnector::take_over(connector)
|
||||
}
|
||||
|
||||
pub async fn run_until_handover(
|
||||
credssp_finished: &mut CredSSPFinished,
|
||||
framed: &mut Framed<impl FramedRead + FramedWrite>,
|
||||
mut connector: VmClientConnector,
|
||||
) -> ConnectorResult<ClientConnector> {
|
||||
let result = loop {
|
||||
single_sequence_step(framed, &mut connector, &mut credssp_finished.write_buf).await?;
|
||||
|
||||
if connector.should_hand_over() {
|
||||
break connector.hand_over();
|
||||
}
|
||||
};
|
||||
|
||||
info!("Handover to client connector");
|
||||
credssp_finished.write_buf.clear();
|
||||
|
||||
Ok(result)
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
use std::io::{Read, Write};
|
||||
|
||||
use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig};
|
||||
use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig};
|
||||
use ironrdp_connector::sspi::credssp::ClientState;
|
||||
use ironrdp_connector::sspi::generator::GeneratorState;
|
||||
use ironrdp_connector::sspi::network_client::NetworkClient;
|
||||
use ironrdp_connector::{
|
||||
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
|
||||
Sequence as _, ServerName, State as _,
|
||||
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError,
|
||||
ConnectorResult, SecurityConnector, Sequence, ServerName,
|
||||
};
|
||||
use ironrdp_core::WriteBuf;
|
||||
|
||||
|
@ -16,7 +16,7 @@ use crate::framed::Framed;
|
|||
pub struct ShouldUpgrade;
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut ClientConnector) -> ConnectorResult<ShouldUpgrade>
|
||||
pub fn connect_begin<S>(framed: &mut Framed<S>, connector: &mut dyn ConnectorCore) -> ConnectorResult<ShouldUpgrade>
|
||||
where
|
||||
S: Sync + Read + Write,
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ where
|
|||
Ok(ShouldUpgrade)
|
||||
}
|
||||
|
||||
pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
|
||||
pub fn skip_connect_begin(connector: &mut dyn SecurityConnector) -> ShouldUpgrade {
|
||||
assert!(connector.should_perform_security_upgrade());
|
||||
ShouldUpgrade
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ fn resolve_generator(
|
|||
#[instrument(level = "trace", skip_all)]
|
||||
fn perform_credssp_step<S>(
|
||||
framed: &mut Framed<S>,
|
||||
connector: &mut ClientConnector,
|
||||
connector: &mut dyn ConnectorCore,
|
||||
buf: &mut WriteBuf,
|
||||
server_name: ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
|
@ -125,14 +125,13 @@ where
|
|||
{
|
||||
assert!(connector.should_perform_credssp());
|
||||
|
||||
let selected_protocol = match connector.state {
|
||||
ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol,
|
||||
_ => return Err(general_err!("invalid connector state for CredSSP sequence")),
|
||||
};
|
||||
let selected_protocol = connector
|
||||
.selected_protocol()
|
||||
.ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?;
|
||||
|
||||
let (mut sequence, mut ts_request) = CredsspSequence::init(
|
||||
connector.config.credentials.clone(),
|
||||
connector.config.domain.as_deref(),
|
||||
let (mut sequence, mut ts_request) = connector.init_credssp(
|
||||
connector.config().credentials.clone(),
|
||||
connector.config().domain.as_deref(),
|
||||
selected_protocol,
|
||||
server_name,
|
||||
server_public_key,
|
||||
|
@ -161,7 +160,7 @@ where
|
|||
};
|
||||
|
||||
debug!(
|
||||
connector.state = connector.state.name(),
|
||||
connector.state = connector.state().name(),
|
||||
hint = ?next_pdu_hint,
|
||||
"Wait for PDU"
|
||||
);
|
||||
|
@ -186,7 +185,7 @@ where
|
|||
|
||||
pub fn single_sequence_step<S>(
|
||||
framed: &mut Framed<S>,
|
||||
connector: &mut ClientConnector,
|
||||
connector: &mut dyn Sequence,
|
||||
buf: &mut WriteBuf,
|
||||
) -> ConnectorResult<()>
|
||||
where
|
||||
|
@ -196,7 +195,7 @@ where
|
|||
|
||||
let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
|
||||
debug!(
|
||||
connector.state = connector.state.name(),
|
||||
connector.state = connector.state().name(),
|
||||
hint = ?next_pdu_hint,
|
||||
"Wait for PDU"
|
||||
);
|
||||
|
|
|
@ -49,6 +49,7 @@ ironrdp-tls = { path = "../ironrdp-tls", version = "0.1" }
|
|||
ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.5", features = ["reqwest"] }
|
||||
ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath"
|
||||
ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy"
|
||||
ironrdp-vmconnect.path = "../ironrdp-vmconnect"
|
||||
|
||||
# Windowing and rendering
|
||||
winit = { version = "0.30", features = ["rwh_06"] }
|
||||
|
|
|
@ -28,6 +28,8 @@ pub struct Config {
|
|||
/// server, which will be used for proxying DVC messages to/from user-defined DVC logic
|
||||
/// implemented as named pipe clients (either in the same process or in a different process).
|
||||
pub dvc_pipe_proxies: Vec<DvcProxyInfo>,
|
||||
|
||||
pub pcb: Option<PreconnectionBlobPayload>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
|
@ -280,6 +282,36 @@ struct Args {
|
|||
/// e.g. PipeName will automatically be prefixed with `\\.\pipe\` on Windows.
|
||||
#[clap(long)]
|
||||
dvc_proxy: Vec<DvcProxyInfo>,
|
||||
|
||||
/// The ID for the HyperV VM server to connect to
|
||||
#[clap(long, conflicts_with("pcb"))]
|
||||
vmconnect: Option<uuid::Uuid>,
|
||||
|
||||
/// Preconnection Blob payload to use
|
||||
#[clap(long)]
|
||||
pcb: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PreconnectionBlobPayload {
|
||||
General(String),
|
||||
VmConnect(String),
|
||||
}
|
||||
|
||||
impl PreconnectionBlobPayload {
|
||||
pub fn general(&self) -> Option<&str> {
|
||||
match self {
|
||||
PreconnectionBlobPayload::General(pcb) => Some(pcb),
|
||||
PreconnectionBlobPayload::VmConnect(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vmconnect(&self) -> Option<&str> {
|
||||
match self {
|
||||
PreconnectionBlobPayload::VmConnect(vm_id) => Some(vm_id),
|
||||
PreconnectionBlobPayload::General(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -393,6 +425,15 @@ impl Config {
|
|||
.zip(args.rdcleanpath_token)
|
||||
.map(|(url, auth_token)| RDCleanPathConfig { url, auth_token });
|
||||
|
||||
let pcb = match (args.vmconnect, args.pcb) {
|
||||
(Some(_), Some(_)) => {
|
||||
unreachable!("Cannot use both `--vmconnect` and `--pcb` at the same time");
|
||||
}
|
||||
(Some(vm_id), None) => Some(PreconnectionBlobPayload::VmConnect(vm_id.to_string())),
|
||||
(None, Some(pcb)) => Some(PreconnectionBlobPayload::General(pcb)),
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
log_file: args.log_file,
|
||||
destination,
|
||||
|
@ -400,6 +441,7 @@ impl Config {
|
|||
clipboard_type,
|
||||
rdcleanpath,
|
||||
dvc_pipe_proxies: args.dvc_proxy,
|
||||
pcb,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||
|
||||
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory};
|
||||
use ironrdp::connector::connection_activation::ConnectionActivationState;
|
||||
use ironrdp::connector::{ConnectionResult, ConnectorResult};
|
||||
use ironrdp::connector::{ClientConnector, ConnectionResult, ConnectorCore, ConnectorResult};
|
||||
use ironrdp::displaycontrol::client::DisplayControlClient;
|
||||
use ironrdp::displaycontrol::pdu::MonitorLayoutEntry;
|
||||
use ironrdp::graphics::image_processing::PixelFormat;
|
||||
|
@ -17,15 +17,21 @@ use ironrdp_core::WriteBuf;
|
|||
use ironrdp_dvc_pipe_proxy::DvcNamedPipeProxy;
|
||||
use ironrdp_rdpsnd_native::cpal;
|
||||
use ironrdp_tokio::reqwest::ReqwestNetworkClient;
|
||||
use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite};
|
||||
use ironrdp_tokio::{
|
||||
mark_pcb_sent_by_rdclean_path, perform_credssp, run_until_handover, send_pcb, single_sequence_step_read,
|
||||
split_tokio_framed, vm_connector_take_over, CredSSPFinished, Framed, FramedRead, FramedWrite, TokioStream,
|
||||
Upgraded,
|
||||
};
|
||||
use ironrdp_vmconnect::VmClientConnector;
|
||||
use rdpdr::NoopRdpdrBackend;
|
||||
use smallvec::SmallVec;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::mpsc;
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
use x509_cert::der::asn1::OctetString;
|
||||
|
||||
use crate::config::{Config, RDCleanPathConfig};
|
||||
use crate::config::{Config, PreconnectionBlobPayload, RDCleanPathConfig};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RdpOutputEvent {
|
||||
|
@ -194,49 +200,67 @@ async fn connect(
|
|||
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
|
||||
}
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
|
||||
let mut connector = ClientConnector::new(config.connector.clone(), client_addr)
|
||||
.with_static_channel(drdynvc)
|
||||
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
|
||||
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
|
||||
|
||||
if let Some(builder) = cliprdr_factory {
|
||||
let backend = builder.build_cliprdr_backend();
|
||||
|
||||
let cliprdr = cliprdr::Cliprdr::new(backend);
|
||||
|
||||
connector.attach_static_channel(cliprdr);
|
||||
}
|
||||
|
||||
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?;
|
||||
let mut connector: Box<dyn ConnectorCore> =
|
||||
if let Some(PreconnectionBlobPayload::VmConnect(vmconnect)) = &config.pcb {
|
||||
let pcb_sent = send_pcb(&mut framed, vmconnect.to_owned()).await?;
|
||||
let connector = vm_connector_take_over(pcb_sent, connector)?;
|
||||
Box::new(connector)
|
||||
} else {
|
||||
Box::new(connector)
|
||||
};
|
||||
|
||||
debug!("TLS upgrade");
|
||||
let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, connector.as_mut()).await?;
|
||||
let (mut upgraded_framed, server_public_key) = upgrade(framed, config.destination.name()).await?;
|
||||
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut());
|
||||
|
||||
// Ensure there is no leftover
|
||||
let (initial_stream, leftover_bytes) = framed.into_inner();
|
||||
let server_name = (&config.destination).into();
|
||||
|
||||
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
|
||||
|
||||
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector);
|
||||
|
||||
let erased_stream = Box::new(upgraded_stream) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
|
||||
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
|
||||
|
||||
let connection_result = ironrdp_tokio::connect_finalize(
|
||||
let mut credssp_finished = perform_credssp(
|
||||
upgraded,
|
||||
connector.as_mut(),
|
||||
&mut upgraded_framed,
|
||||
connector,
|
||||
(&config.destination).into(),
|
||||
server_name,
|
||||
server_public_key,
|
||||
Some(&mut ReqwestNetworkClient::new()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let connector = downcast_back_to_client_connector(connector, &mut credssp_finished, &mut upgraded_framed).await?;
|
||||
let connection_result = ironrdp_tokio::connect_finalize(credssp_finished, &mut upgraded_framed, connector).await?;
|
||||
debug!(?connection_result);
|
||||
|
||||
Ok((connection_result, upgraded_framed))
|
||||
return Ok((connection_result, upgraded_framed));
|
||||
|
||||
async fn upgrade(
|
||||
framed: Framed<TokioStream<TcpStream>>,
|
||||
server_name: &str,
|
||||
) -> ConnectorResult<(
|
||||
Framed<TokioStream<Box<dyn AsyncReadWrite + Sync + Send + Unpin + 'static>>>,
|
||||
Vec<u8>,
|
||||
)> {
|
||||
let (initial_stream, leftover_bytes) = framed.into_inner();
|
||||
|
||||
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, server_name)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
|
||||
|
||||
let erased_stream = Box::new(upgraded_stream) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
|
||||
let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
|
||||
|
||||
Ok((upgraded_framed, server_public_key))
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_ws(
|
||||
|
@ -285,7 +309,7 @@ async fn connect_ws(
|
|||
drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name));
|
||||
}
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
|
||||
let mut connector = ClientConnector::new(config.connector.clone(), client_addr)
|
||||
.with_static_channel(drdynvc)
|
||||
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
|
||||
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
|
||||
|
@ -300,42 +324,48 @@ async fn connect_ws(
|
|||
|
||||
let destination = format!("{}:{}", config.destination.name(), config.destination.port());
|
||||
|
||||
let (upgraded, server_public_key) = connect_rdcleanpath(
|
||||
let (upgraded, server_public_key, mut connector) = connect_rdcleanpath(
|
||||
&mut framed,
|
||||
&mut connector,
|
||||
connector,
|
||||
destination,
|
||||
rdcleanpath.auth_token.clone(),
|
||||
None,
|
||||
&config.pcb,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let connection_result = ironrdp_tokio::connect_finalize(
|
||||
let (ws, leftover_bytes) = framed.into_inner();
|
||||
let erased_stream = Box::new(ws) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
|
||||
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
|
||||
|
||||
let server_name = (&config.destination).into();
|
||||
|
||||
let mut credssp_done = perform_credssp(
|
||||
upgraded,
|
||||
&mut framed,
|
||||
connector,
|
||||
(&config.destination).into(),
|
||||
connector.as_mut(),
|
||||
&mut upgraded_framed,
|
||||
server_name,
|
||||
server_public_key,
|
||||
Some(&mut ReqwestNetworkClient::new()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (ws, leftover_bytes) = framed.into_inner();
|
||||
let erased_stream = Box::new(ws) as Box<dyn AsyncReadWrite + Unpin + Send + Sync>;
|
||||
let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
|
||||
let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut upgraded_framed).await?;
|
||||
|
||||
let connection_result = ironrdp_tokio::connect_finalize(credssp_done, &mut upgraded_framed, connector).await?;
|
||||
|
||||
Ok((connection_result, upgraded_framed))
|
||||
}
|
||||
|
||||
async fn connect_rdcleanpath<S>(
|
||||
framed: &mut ironrdp_tokio::Framed<S>,
|
||||
connector: &mut connector::ClientConnector,
|
||||
framed: &mut Framed<S>,
|
||||
mut connector: ClientConnector,
|
||||
destination: String,
|
||||
proxy_auth_token: String,
|
||||
pcb: Option<String>,
|
||||
) -> ConnectorResult<(ironrdp_tokio::Upgraded, Vec<u8>)>
|
||||
pcb: &Option<PreconnectionBlobPayload>,
|
||||
) -> ConnectorResult<(Upgraded, Vec<u8>, Box<dyn ConnectorCore>)>
|
||||
where
|
||||
S: ironrdp_tokio::FramedRead + FramedWrite,
|
||||
S: FramedRead + FramedWrite,
|
||||
{
|
||||
use ironrdp::connector::Sequence as _;
|
||||
use x509_cert::der::Decode as _;
|
||||
|
@ -360,79 +390,104 @@ where
|
|||
|
||||
let mut buf = WriteBuf::new();
|
||||
|
||||
info!("Begin connection procedure");
|
||||
debug!(?pcb, "Begin connection procedure");
|
||||
|
||||
{
|
||||
// RDCleanPath request
|
||||
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
|
||||
return Err(connector::general_err!("invalid connector state (send request)"));
|
||||
};
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
|
||||
return Err(connector::general_err!("invalid connector state (send request)"));
|
||||
debug_assert!(connector.next_pdu_hint().is_none());
|
||||
let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box<dyn ConnectorCore>) =
|
||||
if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb {
|
||||
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
|
||||
None,
|
||||
destination,
|
||||
proxy_auth_token,
|
||||
Some(vm_id.to_owned()),
|
||||
)
|
||||
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
|
||||
|
||||
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect");
|
||||
|
||||
let pcb_sent = mark_pcb_sent_by_rdclean_path();
|
||||
let connector = vm_connector_take_over(pcb_sent, connector)?;
|
||||
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
|
||||
} else {
|
||||
let written = connector.step_no_input(&mut buf)?;
|
||||
let x224_pdu_len = written.size().expect("written size");
|
||||
debug_assert_eq!(x224_pdu_len, buf.filled_len());
|
||||
let x224_pdu = buf.filled().to_vec();
|
||||
let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general());
|
||||
// RDCleanPath request
|
||||
|
||||
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
|
||||
Some(x224_pdu),
|
||||
destination,
|
||||
proxy_auth_token,
|
||||
general_pcb.map(str::to_string),
|
||||
)
|
||||
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
|
||||
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
|
||||
};
|
||||
|
||||
debug_assert!(connector.next_pdu_hint().is_none());
|
||||
let rdcleanpath_request = rdcleanpath_request
|
||||
.to_der()
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
|
||||
|
||||
let written = connector.step_no_input(&mut buf)?;
|
||||
let x224_pdu_len = written.size().expect("written size");
|
||||
debug_assert_eq!(x224_pdu_len, buf.filled_len());
|
||||
let x224_pdu = buf.filled().to_vec();
|
||||
framed
|
||||
.write_all(&rdcleanpath_request)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?;
|
||||
|
||||
let rdcleanpath_req =
|
||||
ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb)
|
||||
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
|
||||
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request");
|
||||
let rdcleanpath_req = rdcleanpath_req
|
||||
.to_der()
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
|
||||
let rdcleanpath_result = framed
|
||||
.read_by_hint(&RDCLEANPATH_HINT)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
|
||||
|
||||
framed
|
||||
.write_all(&rdcleanpath_req)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?;
|
||||
let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result)
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
|
||||
|
||||
debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain) = match rdcleanpath_result
|
||||
.into_enum()
|
||||
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
|
||||
{
|
||||
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
|
||||
return Err(connector::general_err!(
|
||||
"received an unexpected RDCleanPath type (request)",
|
||||
));
|
||||
}
|
||||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(connector::custom_err!("received an RDCleanPath error", error));
|
||||
}
|
||||
};
|
||||
|
||||
buf.clear();
|
||||
if let Some(x224_connection_response) = x224_connection_response {
|
||||
// let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state() else {
|
||||
// return Err(connector::general_err!("invalid connector state (wait confirm)"));
|
||||
// };
|
||||
debug_assert!(connector.next_pdu_hint().is_some());
|
||||
// Write the X.224 connection response PDU
|
||||
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
|
||||
debug_assert!(written.is_nothing());
|
||||
}
|
||||
|
||||
{
|
||||
// RDCleanPath response
|
||||
let server_public_key = extract_server_public_key(server_cert_chain)?;
|
||||
|
||||
let rdcleanpath_res = framed
|
||||
.read_by_hint(&RDCLEANPATH_HINT)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
|
||||
let should_upgrade = ironrdp_tokio::skip_connect_begin(connector.as_mut());
|
||||
|
||||
let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res)
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
|
||||
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut());
|
||||
|
||||
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain) = match rdcleanpath_res
|
||||
.into_enum()
|
||||
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
|
||||
{
|
||||
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
|
||||
return Err(connector::general_err!(
|
||||
"received an unexpected RDCleanPath type (request)",
|
||||
));
|
||||
}
|
||||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(connector::custom_err!("received an RDCleanPath error", error));
|
||||
}
|
||||
};
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
|
||||
return Err(connector::general_err!("invalid connector state (wait confirm)"));
|
||||
};
|
||||
|
||||
debug_assert!(connector.next_pdu_hint().is_some());
|
||||
|
||||
buf.clear();
|
||||
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
|
||||
|
||||
debug_assert!(written.is_nothing());
|
||||
return Ok((upgraded, server_public_key, connector));
|
||||
|
||||
fn extract_server_public_key(server_cert_chain: Vec<OctetString>) -> ConnectorResult<Vec<u8>> {
|
||||
let server_cert = server_cert_chain
|
||||
.into_iter()
|
||||
.next()
|
||||
|
@ -449,13 +504,7 @@ where
|
|||
.ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))?
|
||||
.to_owned();
|
||||
|
||||
let should_upgrade = ironrdp_tokio::skip_connect_begin(connector);
|
||||
|
||||
// At this point, proxy established the TLS session.
|
||||
|
||||
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector);
|
||||
|
||||
Ok((upgraded, server_public_key))
|
||||
Ok(server_public_key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,3 +691,25 @@ async fn active_session(
|
|||
|
||||
Ok(RdpControlFlow::TerminatedGracefully(disconnect_reason))
|
||||
}
|
||||
|
||||
pub async fn downcast_back_to_client_connector(
|
||||
connector: Box<dyn ConnectorCore>, // `ConnectorCore: Any`
|
||||
credssp_finished: &mut CredSSPFinished,
|
||||
framed: &mut Framed<impl FramedRead + FramedWrite>,
|
||||
) -> ConnectorResult<ClientConnector> {
|
||||
let connector: Box<dyn core::any::Any> = connector;
|
||||
|
||||
let client = match connector.downcast::<VmClientConnector>() {
|
||||
Ok(vm_connector) => run_until_handover(credssp_finished, framed, *vm_connector).await?,
|
||||
Err(err) => match err.downcast::<ClientConnector>() {
|
||||
Ok(c) => *c,
|
||||
Err(_) => {
|
||||
return Err(connector::general_err!(
|
||||
"connector is neither ClientConnector nor VmClientConnector"
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@ use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor};
|
|||
|
||||
use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
|
||||
use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState};
|
||||
use crate::credssp::{self};
|
||||
use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache};
|
||||
use crate::{
|
||||
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State,
|
||||
Written,
|
||||
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, CredsspSequenceFactory,
|
||||
DesktopSize, SecurityConnector, Sequence, State, Written,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -153,27 +154,63 @@ impl ClientConnector {
|
|||
{
|
||||
self.static_channels.insert(channel);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_perform_security_upgrade(&self) -> bool {
|
||||
impl SecurityConnector for ClientConnector {
|
||||
fn should_perform_security_upgrade(&self) -> bool {
|
||||
matches!(self.state, ClientConnectorState::EnhancedSecurityUpgrade { .. })
|
||||
}
|
||||
|
||||
pub fn mark_security_upgrade_as_done(&mut self) {
|
||||
fn mark_security_upgrade_as_done(&mut self) {
|
||||
assert!(self.should_perform_security_upgrade());
|
||||
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
debug_assert!(!self.should_perform_security_upgrade());
|
||||
}
|
||||
|
||||
pub fn should_perform_credssp(&self) -> bool {
|
||||
fn should_perform_credssp(&self) -> bool {
|
||||
matches!(self.state, ClientConnectorState::Credssp { .. })
|
||||
}
|
||||
|
||||
pub fn mark_credssp_as_done(&mut self) {
|
||||
fn mark_credssp_as_done(&mut self) {
|
||||
assert!(self.should_perform_credssp());
|
||||
let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
debug_assert!(!self.should_perform_credssp());
|
||||
assert_eq!(res, Written::Nothing);
|
||||
}
|
||||
|
||||
fn selected_protocol(&self) -> Option<nego::SecurityProtocol> {
|
||||
match &self.state {
|
||||
ClientConnectorState::Credssp { selected_protocol } => Some(*selected_protocol),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
impl CredsspSequenceFactory for ClientConnector {
|
||||
fn init_credssp(
|
||||
&self,
|
||||
credentials: crate::Credentials,
|
||||
domain: Option<&str>,
|
||||
protocol: nego::SecurityProtocol,
|
||||
server_name: crate::ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
kerberos_config: Option<credssp::KerberosConfig>,
|
||||
) -> ConnectorResult<(Box<dyn credssp::CredsspSequenceTrait>, sspi::credssp::TsRequest)> {
|
||||
let (sequence, ts_request) = credssp::CredsspSequence::init(
|
||||
credentials,
|
||||
domain,
|
||||
protocol,
|
||||
server_name,
|
||||
server_public_key,
|
||||
kerberos_config,
|
||||
)?;
|
||||
|
||||
Ok((Box::new(sequence), ts_request))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sequence for ClientConnector {
|
||||
|
@ -330,6 +367,8 @@ impl Sequence for ClientConnector {
|
|||
|
||||
let written = encode_x224_packet(&connect_initial, output)?;
|
||||
|
||||
trace!(written, "Written");
|
||||
|
||||
(
|
||||
Written::from_size(written)?,
|
||||
ClientConnectorState::BasicSettingsExchangeWaitResponse { connect_initial },
|
||||
|
|
|
@ -73,21 +73,104 @@ pub struct CredsspSequence {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum CredsspState {
|
||||
pub enum CredsspState {
|
||||
Ongoing,
|
||||
EarlyUserAuthResult,
|
||||
Finished,
|
||||
}
|
||||
|
||||
impl CredsspSequence {
|
||||
pub fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
|
||||
match self.state {
|
||||
pub trait CredsspSequenceTrait {
|
||||
fn credssp_state(&self) -> &CredsspState;
|
||||
|
||||
fn set_credssp_state(&mut self, state: CredsspState);
|
||||
|
||||
fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
|
||||
match self.credssp_state() {
|
||||
CredsspState::Ongoing => Some(&CREDSSP_TS_REQUEST_HINT),
|
||||
CredsspState::EarlyUserAuthResult => Some(&CREDSSP_EARLY_USER_AUTH_RESULT_HINT),
|
||||
CredsspState::Finished => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult<Option<credssp::TsRequest>> {
|
||||
match self.credssp_state() {
|
||||
CredsspState::Ongoing => {
|
||||
let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
|
||||
debug!(?message, "Received");
|
||||
Ok(Some(message))
|
||||
}
|
||||
CredsspState::EarlyUserAuthResult => {
|
||||
let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input)
|
||||
.map_err(|e| custom_err!("EarlyUserAuthResult", e))?;
|
||||
|
||||
debug!(message = ?early_user_auth_result, "Received");
|
||||
|
||||
match early_user_auth_result {
|
||||
credssp::EarlyUserAuthResult::Success => {
|
||||
self.set_credssp_state(CredsspState::Finished);
|
||||
Ok(None)
|
||||
}
|
||||
credssp::EarlyUserAuthResult::AccessDenied => {
|
||||
Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(general_err!(
|
||||
"attempted to feed server request to CredSSP sequence in an unexpected state"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_>;
|
||||
|
||||
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written>;
|
||||
}
|
||||
|
||||
impl CredsspSequenceTrait for CredsspSequence {
|
||||
fn credssp_state(&self) -> &CredsspState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
fn set_credssp_state(&mut self, state: CredsspState) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> {
|
||||
self.client.process(request)
|
||||
}
|
||||
|
||||
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
|
||||
let (size, next_state) = match self.state {
|
||||
CredsspState::Ongoing => {
|
||||
let (ts_request_from_client, next_state) = match result {
|
||||
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
|
||||
ClientState::FinalMessage(ts_request) => (
|
||||
ts_request,
|
||||
if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) {
|
||||
CredsspState::EarlyUserAuthResult
|
||||
} else {
|
||||
CredsspState::Finished
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
debug!(message = ?ts_request_from_client, "Send");
|
||||
|
||||
let written = write_credssp_request(ts_request_from_client, output)?;
|
||||
|
||||
Ok((Written::from_size(written)?, next_state))
|
||||
}
|
||||
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
|
||||
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
|
||||
}?;
|
||||
|
||||
self.state = next_state;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl CredsspSequence {
|
||||
/// `server_name` must be the actual target server hostname (as opposed to the proxy)
|
||||
pub fn init(
|
||||
credentials: Credentials,
|
||||
|
@ -169,71 +252,6 @@ impl CredsspSequence {
|
|||
|
||||
Ok((sequence, initial_request))
|
||||
}
|
||||
|
||||
/// Returns Some(ts_request) when a TS request is received from server,
|
||||
/// and None when an early user auth result PDU is received instead.
|
||||
pub fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult<Option<credssp::TsRequest>> {
|
||||
match self.state {
|
||||
CredsspState::Ongoing => {
|
||||
let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?;
|
||||
debug!(?message, "Received");
|
||||
Ok(Some(message))
|
||||
}
|
||||
CredsspState::EarlyUserAuthResult => {
|
||||
let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input)
|
||||
.map_err(|e| custom_err!("EarlyUserAuthResult", e))?;
|
||||
|
||||
debug!(message = ?early_user_auth_result, "Received");
|
||||
|
||||
match early_user_auth_result {
|
||||
credssp::EarlyUserAuthResult::Success => {
|
||||
self.state = CredsspState::Finished;
|
||||
Ok(None)
|
||||
}
|
||||
credssp::EarlyUserAuthResult::AccessDenied => {
|
||||
Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(general_err!(
|
||||
"attempted to feed server request to CredSSP sequence in an unexpected state"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> {
|
||||
self.client.process(request)
|
||||
}
|
||||
|
||||
pub fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
|
||||
let (size, next_state) = match self.state {
|
||||
CredsspState::Ongoing => {
|
||||
let (ts_request_from_client, next_state) = match result {
|
||||
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
|
||||
ClientState::FinalMessage(ts_request) => (
|
||||
ts_request,
|
||||
if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) {
|
||||
CredsspState::EarlyUserAuthResult
|
||||
} else {
|
||||
CredsspState::Finished
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
debug!(message = ?ts_request_from_client, "Send");
|
||||
|
||||
let written = write_credssp_request(ts_request_from_client, output)?;
|
||||
|
||||
Ok((Written::from_size(written)?, next_state))
|
||||
}
|
||||
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
|
||||
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
|
||||
}?;
|
||||
|
||||
self.state = next_state;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_user_name(cert: &Certificate) -> Option<String> {
|
||||
|
|
|
@ -22,18 +22,20 @@ use core::fmt;
|
|||
use std::sync::Arc;
|
||||
|
||||
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
|
||||
use ironrdp_pdu::nego::NegoRequestData;
|
||||
use ironrdp_pdu::nego::{NegoRequestData, SecurityProtocol};
|
||||
use ironrdp_pdu::rdp::capability_sets::{self, BitmapCodecs};
|
||||
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
|
||||
use ironrdp_pdu::x224::X224;
|
||||
use ironrdp_pdu::{gcc, x224, PduHint};
|
||||
pub use sspi;
|
||||
use sspi::credssp::TsRequest;
|
||||
|
||||
pub use self::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
|
||||
pub use self::connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
|
||||
pub use self::connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
|
||||
pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
|
||||
pub use self::server_name::ServerName;
|
||||
use crate::credssp::{CredsspSequenceTrait, KerberosConfig};
|
||||
pub use crate::license_exchange::LicenseCache;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -382,3 +384,39 @@ where
|
|||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
pub trait SecurityConnector {
|
||||
fn should_perform_security_upgrade(&self) -> bool;
|
||||
|
||||
fn mark_security_upgrade_as_done(&mut self);
|
||||
|
||||
fn should_perform_credssp(&self) -> bool;
|
||||
|
||||
fn selected_protocol(&self) -> Option<SecurityProtocol>;
|
||||
|
||||
fn mark_credssp_as_done(&mut self);
|
||||
|
||||
fn config(&self) -> &Config;
|
||||
}
|
||||
|
||||
pub trait CredsspSequenceFactory {
|
||||
fn init_credssp(
|
||||
&self,
|
||||
credentials: Credentials,
|
||||
domain: Option<&str>,
|
||||
protocol: SecurityProtocol,
|
||||
server_name: ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
kerberos_config: Option<KerberosConfig>,
|
||||
) -> ConnectorResult<(Box<dyn CredsspSequenceTrait>, TsRequest)>;
|
||||
}
|
||||
|
||||
pub trait ConnectorCore: Sequence + SecurityConnector + CredsspSequenceFactory + Any {
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any>;
|
||||
}
|
||||
|
||||
impl<T: Sequence + SecurityConnector + CredsspSequenceFactory + 'static> ConnectorCore for T {
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ impl RDCleanPathPdu {
|
|||
}
|
||||
|
||||
pub fn new_request(
|
||||
x224_pdu: Vec<u8>,
|
||||
x224_pdu: Option<Vec<u8>>,
|
||||
destination: String,
|
||||
proxy_auth: String,
|
||||
pcb: Option<String>,
|
||||
|
@ -208,19 +208,19 @@ impl RDCleanPathPdu {
|
|||
destination: Some(destination),
|
||||
proxy_auth: Some(proxy_auth),
|
||||
preconnection_blob: pcb,
|
||||
x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
|
||||
x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?,
|
||||
..Self::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_response(
|
||||
server_addr: String,
|
||||
x224_pdu: Vec<u8>,
|
||||
x224_pdu: Option<Vec<u8>>,
|
||||
x509_chain: impl IntoIterator<Item = Vec<u8>>,
|
||||
) -> der::Result<Self> {
|
||||
Ok(Self {
|
||||
version: VERSION_1,
|
||||
x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
|
||||
x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?,
|
||||
server_cert_chain: Some(
|
||||
x509_chain
|
||||
.into_iter()
|
||||
|
@ -271,10 +271,10 @@ pub enum RDCleanPath {
|
|||
proxy_auth: String,
|
||||
server_auth: Option<String>,
|
||||
preconnection_blob: Option<String>,
|
||||
x224_connection_request: OctetString,
|
||||
x224_connection_request: Option<OctetString>,
|
||||
},
|
||||
Response {
|
||||
x224_connection_response: OctetString,
|
||||
x224_connection_response: Option<OctetString>,
|
||||
server_cert_chain: Vec<OctetString>,
|
||||
server_addr: String,
|
||||
},
|
||||
|
@ -308,15 +308,11 @@ impl TryFrom<RDCleanPathPdu> for RDCleanPath {
|
|||
proxy_auth: pdu.proxy_auth.ok_or(MissingRDCleanPathField("proxy_auth"))?,
|
||||
server_auth: pdu.server_auth,
|
||||
preconnection_blob: pdu.preconnection_blob,
|
||||
x224_connection_request: pdu
|
||||
.x224_connection_pdu
|
||||
.ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
|
||||
x224_connection_request: pdu.x224_connection_pdu,
|
||||
}
|
||||
} else if let Some(server_addr) = pdu.server_addr {
|
||||
Self::Response {
|
||||
x224_connection_response: pdu
|
||||
.x224_connection_pdu
|
||||
.ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
|
||||
x224_connection_response: pdu.x224_connection_pdu,
|
||||
server_cert_chain: pdu
|
||||
.server_cert_chain
|
||||
.ok_or(MissingRDCleanPathField("server_cert_chain"))?,
|
||||
|
@ -345,7 +341,7 @@ impl From<RDCleanPath> for RDCleanPathPdu {
|
|||
proxy_auth: Some(proxy_auth),
|
||||
server_auth,
|
||||
preconnection_blob,
|
||||
x224_connection_pdu: Some(x224_connection_request),
|
||||
x224_connection_pdu: x224_connection_request,
|
||||
..Default::default()
|
||||
},
|
||||
RDCleanPath::Response {
|
||||
|
@ -354,7 +350,7 @@ impl From<RDCleanPath> for RDCleanPathPdu {
|
|||
server_addr,
|
||||
} => Self {
|
||||
version: VERSION_1,
|
||||
x224_connection_pdu: Some(x224_connection_response),
|
||||
x224_connection_pdu: x224_connection_response,
|
||||
server_cert_chain: Some(server_cert_chain),
|
||||
server_addr: Some(server_addr),
|
||||
..Default::default()
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
|
|
24
crates/ironrdp-vmconnect/Cargo.toml
Normal file
24
crates/ironrdp-vmconnect/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "ironrdp-vmconnect"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public
|
||||
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.5", features = [
|
||||
"std",
|
||||
] } # public
|
||||
|
||||
arbitrary = { version = "1", features = ["derive"], optional = true } # public
|
||||
sspi = "0.15" # public
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
ironrdp-connector = { path = "../ironrdp-connector" } # public
|
||||
|
||||
[lints]
|
||||
workspace = true
|
60
crates/ironrdp-vmconnect/src/config.rs
Normal file
60
crates/ironrdp-vmconnect/src/config.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use ironrdp_connector::general_err;
|
||||
use ironrdp_pdu::nego::NegoRequestData;
|
||||
use sspi::Username;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Credentials {
|
||||
pub(crate) username: String,
|
||||
pub(crate) password: String,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub(crate) fn to_sspi_auth_identity(&self, domain: Option<&str>) -> Result<sspi::AuthIdentity, sspi::Error> {
|
||||
Ok(sspi::AuthIdentity {
|
||||
username: Username::new(&self.username, domain)?,
|
||||
password: self.password.clone().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ironrdp_connector::Credentials> for Credentials {
|
||||
type Error = ironrdp_connector::ConnectorError;
|
||||
|
||||
fn try_from(value: &ironrdp_connector::Credentials) -> Result<Self, Self::Error> {
|
||||
let ironrdp_connector::Credentials::UsernamePassword { username, password } = value else {
|
||||
return Err(general_err!("Invalid credentials type for VM connection",));
|
||||
};
|
||||
|
||||
Ok(Credentials {
|
||||
username: username.to_owned(),
|
||||
password: password.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VmConnectorConfig {
|
||||
pub request_data: Option<NegoRequestData>,
|
||||
pub credentials: Credentials,
|
||||
}
|
||||
|
||||
impl TryFrom<&ironrdp_connector::Config> for VmConnectorConfig {
|
||||
type Error = ironrdp_connector::ConnectorError;
|
||||
|
||||
fn try_from(value: &ironrdp_connector::Config) -> Result<Self, Self::Error> {
|
||||
let request_data = value.request_data.clone();
|
||||
let ironrdp_connector::Credentials::UsernamePassword { username, password } = &value.credentials else {
|
||||
return Err(general_err!("Invalid credentials type for VM connection",));
|
||||
};
|
||||
|
||||
let credentials = Credentials {
|
||||
username: username.to_owned(),
|
||||
password: password.to_owned(),
|
||||
};
|
||||
|
||||
Ok(VmConnectorConfig {
|
||||
request_data,
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
}
|
250
crates/ironrdp-vmconnect/src/connector.rs
Normal file
250
crates/ironrdp-vmconnect/src/connector.rs
Normal file
|
@ -0,0 +1,250 @@
|
|||
use core::mem;
|
||||
|
||||
use ironrdp_core::{decode, WriteBuf};
|
||||
use ironrdp_pdu::nego::SecurityProtocol;
|
||||
use ironrdp_pdu::x224::X224;
|
||||
use ironrdp_pdu::{nego, PduHint};
|
||||
|
||||
use ironrdp_connector::{
|
||||
general_err, reason_err, ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorExt as _,
|
||||
ConnectorResult, CredsspSequenceFactory, Sequence, State, Written,
|
||||
};
|
||||
|
||||
use crate::config::VmConnectorConfig;
|
||||
|
||||
pub const HYPERV_SECURITY_PROTOCOL: SecurityProtocol = SecurityProtocol::HYBRID_EX
|
||||
.union(SecurityProtocol::SSL)
|
||||
.union(SecurityProtocol::HYBRID);
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub enum VmConnectorState {
|
||||
#[default]
|
||||
Consumed,
|
||||
EnhancedSecurityUpgrade,
|
||||
Credssp,
|
||||
ConnectionInitiationSendRequest,
|
||||
ConnectionInitiationWaitConfirm,
|
||||
Handover {
|
||||
selected_protocol: SecurityProtocol,
|
||||
},
|
||||
}
|
||||
|
||||
impl State for VmConnectorState {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Consumed => "Consumed",
|
||||
Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest",
|
||||
Self::ConnectionInitiationWaitConfirm => "ConnectionInitiationWaitResponse",
|
||||
Self::EnhancedSecurityUpgrade => "EnhancedSecurityUpgrade",
|
||||
Self::Credssp => "Credssp",
|
||||
Self::Handover { .. } => "Handover",
|
||||
}
|
||||
}
|
||||
|
||||
fn is_terminal(&self) -> bool {
|
||||
matches!(self, Self::Handover { .. })
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub struct VmClientConnector {
|
||||
pub config: VmConnectorConfig,
|
||||
pub state: VmConnectorState,
|
||||
client_connector: ClientConnector, // hold it hostage, can't do anything with it until VMConnector handover
|
||||
}
|
||||
|
||||
impl Sequence for VmClientConnector {
|
||||
fn next_pdu_hint(&self) -> Option<&dyn PduHint> {
|
||||
match &self.state {
|
||||
VmConnectorState::Consumed => None,
|
||||
VmConnectorState::ConnectionInitiationSendRequest => None,
|
||||
VmConnectorState::ConnectionInitiationWaitConfirm => Some(&ironrdp_pdu::X224_HINT),
|
||||
VmConnectorState::EnhancedSecurityUpgrade => None,
|
||||
VmConnectorState::Credssp => None,
|
||||
VmConnectorState::Handover { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> &dyn State {
|
||||
&self.state
|
||||
}
|
||||
|
||||
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written> {
|
||||
let (written, next_state) = match mem::take(&mut self.state) {
|
||||
// Invalid state
|
||||
VmConnectorState::Consumed => {
|
||||
return Err(general_err!("connector sequence state is consumed (this is a bug)",))
|
||||
}
|
||||
|
||||
//== Connection Initiation ==//
|
||||
// Exchange supported security protocols and a few other connection flags.
|
||||
VmConnectorState::EnhancedSecurityUpgrade => (Written::Nothing, VmConnectorState::Credssp),
|
||||
|
||||
VmConnectorState::Credssp => (Written::Nothing, VmConnectorState::ConnectionInitiationSendRequest),
|
||||
VmConnectorState::ConnectionInitiationSendRequest => {
|
||||
debug!("Connection Initiation");
|
||||
|
||||
let connection_request = nego::ConnectionRequest {
|
||||
nego_data: self
|
||||
.config
|
||||
.request_data
|
||||
.clone()
|
||||
.or_else(|| Some(nego::NegoRequestData::cookie(self.config.credentials.username.clone()))),
|
||||
flags: nego::RequestFlags::empty(),
|
||||
protocol: HYPERV_SECURITY_PROTOCOL,
|
||||
};
|
||||
|
||||
debug!(message = ?connection_request, "Send");
|
||||
|
||||
let written =
|
||||
ironrdp_core::encode_buf(&X224(connection_request), output).map_err(ConnectorError::encode)?;
|
||||
|
||||
(
|
||||
Written::from_size(written)?,
|
||||
VmConnectorState::ConnectionInitiationWaitConfirm,
|
||||
)
|
||||
}
|
||||
VmConnectorState::ConnectionInitiationWaitConfirm => {
|
||||
let connection_confirm = decode::<X224<nego::ConnectionConfirm>>(input)
|
||||
.map_err(ConnectorError::decode)
|
||||
.map(|p| p.0)?;
|
||||
|
||||
debug!(message = ?connection_confirm, "Received");
|
||||
|
||||
let (flags, selected_protocol) = match connection_confirm {
|
||||
nego::ConnectionConfirm::Response { flags, protocol } => (flags, protocol),
|
||||
nego::ConnectionConfirm::Failure { code } => {
|
||||
error!(?code, "Received connection failure code");
|
||||
return Err(reason_err!("Initiation", "{code}"));
|
||||
}
|
||||
};
|
||||
|
||||
info!(?selected_protocol, ?flags, "Server confirmed connection");
|
||||
|
||||
(Written::Nothing, VmConnectorState::Handover { selected_protocol })
|
||||
}
|
||||
|
||||
VmConnectorState::Handover { .. } => {
|
||||
return Err(general_err!(
|
||||
"connector sequence state is already in handover (this is a bug)",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.state = next_state;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmClientConnector {
|
||||
pub fn take_over(connector: ClientConnector) -> ConnectorResult<Self> {
|
||||
assert!(
|
||||
matches!(connector.state, ClientConnectorState::ConnectionInitiationSendRequest),
|
||||
"Invalid connector state for VM connection, expected ConnectionInitiationSendRequest, got: {}",
|
||||
connector.state.name()
|
||||
);
|
||||
|
||||
debug!("Taking over VM connector");
|
||||
|
||||
let vm_connector_config = VmConnectorConfig::try_from(&connector.config)?;
|
||||
let vm_connector = VmClientConnector {
|
||||
config: vm_connector_config,
|
||||
state: VmConnectorState::EnhancedSecurityUpgrade,
|
||||
client_connector: connector,
|
||||
};
|
||||
|
||||
Ok(vm_connector)
|
||||
}
|
||||
|
||||
pub fn should_hand_over(&self) -> bool {
|
||||
matches!(self.state, VmConnectorState::Handover { .. })
|
||||
}
|
||||
|
||||
pub fn hand_over(self) -> ClientConnector {
|
||||
let VmConnectorState::Handover { selected_protocol } = self.state else {
|
||||
panic!(
|
||||
"Invalid state for handover, expected Handover, got: {}",
|
||||
self.state.name()
|
||||
);
|
||||
};
|
||||
let VmClientConnector {
|
||||
mut client_connector, ..
|
||||
} = self;
|
||||
|
||||
client_connector.state = ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol };
|
||||
|
||||
client_connector
|
||||
}
|
||||
}
|
||||
|
||||
impl VmClientConnector {
|
||||
pub fn should_perform_credssp(&self) -> bool {
|
||||
matches!(self.state, VmConnectorState::Credssp)
|
||||
}
|
||||
}
|
||||
|
||||
impl ironrdp_connector::SecurityConnector for VmClientConnector {
|
||||
fn should_perform_security_upgrade(&self) -> bool {
|
||||
matches!(self.state, VmConnectorState::EnhancedSecurityUpgrade)
|
||||
}
|
||||
|
||||
fn mark_security_upgrade_as_done(&mut self) {
|
||||
assert!(self.should_perform_security_upgrade());
|
||||
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
debug_assert!(!self.should_perform_security_upgrade());
|
||||
}
|
||||
|
||||
fn should_perform_credssp(&self) -> bool {
|
||||
matches!(self.state, VmConnectorState::Credssp)
|
||||
}
|
||||
|
||||
fn selected_protocol(&self) -> Option<SecurityProtocol> {
|
||||
if self.should_perform_credssp() {
|
||||
Some(HYPERV_SECURITY_PROTOCOL)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_credssp_as_done(&mut self) {
|
||||
assert!(self.should_perform_credssp());
|
||||
self.step(&[], &mut WriteBuf::new()).expect("transition to next state");
|
||||
debug_assert!(!self.should_perform_credssp());
|
||||
}
|
||||
|
||||
fn config(&self) -> &ironrdp_connector::Config {
|
||||
self.client_connector.config()
|
||||
}
|
||||
}
|
||||
|
||||
impl CredsspSequenceFactory for VmClientConnector {
|
||||
fn init_credssp(
|
||||
&self,
|
||||
credentials: ironrdp_connector::Credentials,
|
||||
domain: Option<&str>,
|
||||
_protocol: SecurityProtocol,
|
||||
server_name: ironrdp_connector::ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
_kerberos_config: Option<ironrdp_connector::credssp::KerberosConfig>,
|
||||
) -> ConnectorResult<(
|
||||
Box<dyn ironrdp_connector::credssp::CredsspSequenceTrait>,
|
||||
sspi::credssp::TsRequest,
|
||||
)> {
|
||||
let credentials = crate::config::Credentials::try_from(&credentials)?;
|
||||
|
||||
let (credssp, ts_request) =
|
||||
crate::credssp::VmCredsspSequence::init(credentials, domain, server_name, server_public_key)?;
|
||||
|
||||
let credssp: Box<dyn ironrdp_connector::credssp::CredsspSequenceTrait> = Box::new(credssp);
|
||||
|
||||
Ok((credssp, ts_request))
|
||||
}
|
||||
}
|
114
crates/ironrdp-vmconnect/src/credssp.rs
Normal file
114
crates/ironrdp-vmconnect/src/credssp.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use ironrdp_connector::credssp::{CredsspSequenceTrait, CredsspState};
|
||||
use ironrdp_connector::{custom_err, general_err};
|
||||
use ironrdp_core::WriteBuf;
|
||||
use sspi::credssp::{self, ClientState, CredSspClient};
|
||||
|
||||
use ironrdp_connector::{ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written};
|
||||
|
||||
use crate::config::Credentials;
|
||||
|
||||
// pub type CredsspProcessGenerator<'a> = Generator<'a, NetworkRequest, sspi::Result<Vec<u8>>, sspi::Result<ClientState>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VmCredsspSequence {
|
||||
client: CredSspClient,
|
||||
state: CredsspState,
|
||||
}
|
||||
|
||||
impl CredsspSequenceTrait for VmCredsspSequence {
|
||||
fn credssp_state(&self) -> &CredsspState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
fn set_credssp_state(&mut self, state: CredsspState) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
fn process_ts_request(
|
||||
&mut self,
|
||||
request: credssp::TsRequest,
|
||||
) -> ironrdp_connector::credssp::CredsspProcessGenerator<'_> {
|
||||
self.client.process(request)
|
||||
}
|
||||
|
||||
fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult<Written> {
|
||||
let (size, next_state) = match self.state {
|
||||
CredsspState::Ongoing => {
|
||||
let (ts_request_from_client, next_state) = match result {
|
||||
ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing),
|
||||
ClientState::FinalMessage(ts_request) => (ts_request, CredsspState::Finished),
|
||||
};
|
||||
|
||||
debug!(message = ?ts_request_from_client, "Send");
|
||||
|
||||
let written = write_credssp_request(ts_request_from_client, output)?;
|
||||
|
||||
Ok((Written::from_size(written)?, next_state))
|
||||
}
|
||||
CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)),
|
||||
CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")),
|
||||
}?;
|
||||
|
||||
self.state = next_state;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
/// The main difference between this and the `credssp::CredsspSequence` is that this sequence uses NTLM only
|
||||
/// No Kerberos or Negotiate, as Hyper-V does not support it
|
||||
impl VmCredsspSequence {
|
||||
/// `server_name` must be the actual target server hostname (as opposed to the proxy)
|
||||
pub fn init(
|
||||
credentials: Credentials,
|
||||
domain: Option<&str>,
|
||||
server_name: ServerName,
|
||||
server_public_key: Vec<u8>,
|
||||
) -> ConnectorResult<(Self, credssp::TsRequest)> {
|
||||
let credentials: sspi::Credentials = credentials
|
||||
.to_sspi_auth_identity(domain)
|
||||
.map_err(|e| custom_err!("Invalid username", e))?
|
||||
.into();
|
||||
|
||||
let server_name = server_name.into_inner();
|
||||
|
||||
let service_principal_name = format!("TERMSRV/{}", &server_name);
|
||||
|
||||
let credssp_config = Box::<sspi::ntlm::NtlmConfig>::default();
|
||||
debug!(?credssp_config);
|
||||
|
||||
let client = CredSspClient::new(
|
||||
server_public_key,
|
||||
credentials,
|
||||
credssp::CredSspMode::WithCredentials,
|
||||
credssp::ClientMode::Ntlm(sspi::ntlm::NtlmConfig {
|
||||
client_computer_name: Some(server_name),
|
||||
}),
|
||||
service_principal_name,
|
||||
)
|
||||
.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?;
|
||||
|
||||
let sequence = Self {
|
||||
client,
|
||||
state: CredsspState::Ongoing,
|
||||
};
|
||||
|
||||
let initial_request = credssp::TsRequest::default();
|
||||
|
||||
Ok((sequence, initial_request))
|
||||
}
|
||||
}
|
||||
|
||||
fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf) -> ConnectorResult<usize> {
|
||||
let length = usize::from(ts_request.buffer_len());
|
||||
|
||||
let unfilled_buffer = output.unfilled_to(length);
|
||||
|
||||
ts_request
|
||||
.encode_ts_request(unfilled_buffer)
|
||||
.map_err(|e| custom_err!("TsRequest", e))?;
|
||||
|
||||
output.advance(length);
|
||||
|
||||
Ok(length)
|
||||
}
|
7
crates/ironrdp-vmconnect/src/lib.rs
Normal file
7
crates/ironrdp-vmconnect/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod config;
|
||||
pub mod connector;
|
||||
pub mod credssp;
|
||||
pub use connector::*;
|
|
@ -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"
|
||||
|
|
|
@ -17,7 +17,7 @@ use ironrdp::cliprdr::backend::ClipboardMessage;
|
|||
use ironrdp::cliprdr::CliprdrClient;
|
||||
use ironrdp::connector::connection_activation::ConnectionActivationState;
|
||||
use ironrdp::connector::credssp::KerberosConfig;
|
||||
use ironrdp::connector::{self, ClientConnector, Credentials};
|
||||
use ironrdp::connector::{self, ClientConnector, ConnectorCore, Credentials};
|
||||
use ironrdp::displaycontrol::client::DisplayControlClient;
|
||||
use ironrdp::dvc::DrdynvcClient;
|
||||
use ironrdp::graphics::image_processing::PixelFormat;
|
||||
|
@ -27,12 +27,13 @@ use ironrdp::pdu::rdp::client_info::PerformanceFlags;
|
|||
use ironrdp::session::image::DecodedImage;
|
||||
use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason};
|
||||
use ironrdp_core::WriteBuf;
|
||||
use ironrdp_futures::{single_sequence_step_read, FramedWrite};
|
||||
use ironrdp_futures::{single_sequence_step_read, FramedRead, FramedWrite};
|
||||
use rgb::AsPixels as _;
|
||||
use tap::prelude::*;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
use x509_cert::der::asn1::OctetString;
|
||||
|
||||
use crate::canvas::Canvas;
|
||||
use crate::clipboard;
|
||||
|
@ -55,7 +56,7 @@ struct SessionBuilderInner {
|
|||
password: Option<String>,
|
||||
proxy_address: Option<String>,
|
||||
auth_token: Option<String>,
|
||||
pcb: Option<String>,
|
||||
pcb: Option<PreconnectionBlobPayload>,
|
||||
kdc_proxy_url: Option<String>,
|
||||
client_name: String,
|
||||
desktop_size: DesktopSize,
|
||||
|
@ -70,6 +71,21 @@ struct SessionBuilderInner {
|
|||
use_display_control: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum PreconnectionBlobPayload {
|
||||
General(String),
|
||||
VmConnect(String),
|
||||
}
|
||||
|
||||
impl PreconnectionBlobPayload {
|
||||
pub(crate) fn general(&self) -> Option<&str> {
|
||||
match self {
|
||||
PreconnectionBlobPayload::General(pcb) => Some(pcb),
|
||||
PreconnectionBlobPayload::VmConnect(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionBuilderInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -213,7 +229,8 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
|
|||
fn extension(&self, ext: Extension) -> Self {
|
||||
iron_remote_desktop::extension_match! {
|
||||
match ext;
|
||||
|pcb: String| { self.0.borrow_mut().pcb = Some(pcb) };
|
||||
|pcb: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::General(pcb)) };
|
||||
|vmconnect: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::VmConnect(vmconnect)) };
|
||||
|kdc_proxy_url: String| { self.0.borrow_mut().kdc_proxy_url = Some(kdc_proxy_url) };
|
||||
|display_control: bool| { self.0.borrow_mut().use_display_control = display_control };
|
||||
}
|
||||
|
@ -905,7 +922,7 @@ struct ConnectParams {
|
|||
config: connector::Config,
|
||||
proxy_auth_token: String,
|
||||
destination: String,
|
||||
pcb: Option<String>,
|
||||
pcb: Option<PreconnectionBlobPayload>,
|
||||
kdc_proxy_url: Option<String>,
|
||||
clipboard_backend: Option<WasmClipboardBackend>,
|
||||
use_display_control: bool,
|
||||
|
@ -940,13 +957,13 @@ async fn connect(
|
|||
);
|
||||
}
|
||||
|
||||
let (upgraded, server_public_key) =
|
||||
connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?;
|
||||
let (upgraded, server_public_key, mut connector) =
|
||||
connect_rdcleanpath(&mut framed, connector, destination.clone(), proxy_auth_token, &pcb).await?;
|
||||
|
||||
let connection_result = ironrdp_futures::connect_finalize(
|
||||
let mut credssp_done = ironrdp_futures::perform_credssp(
|
||||
upgraded,
|
||||
connector.as_mut(),
|
||||
&mut framed,
|
||||
connector,
|
||||
(&destination).into(),
|
||||
server_public_key,
|
||||
Some(&mut WasmNetworkClient),
|
||||
|
@ -961,6 +978,9 @@ async fn connect(
|
|||
)
|
||||
.await?;
|
||||
|
||||
let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut framed).await?;
|
||||
let connection_result = ironrdp_futures::connect_finalize(credssp_done, &mut framed, connector).await?;
|
||||
|
||||
let ws = framed.into_inner_no_leftover();
|
||||
|
||||
Ok((connection_result, ws))
|
||||
|
@ -968,13 +988,13 @@ async fn connect(
|
|||
|
||||
async fn connect_rdcleanpath<S>(
|
||||
framed: &mut ironrdp_futures::Framed<S>,
|
||||
connector: &mut ClientConnector,
|
||||
mut connector: ClientConnector,
|
||||
destination: String,
|
||||
proxy_auth_token: String,
|
||||
pcb: Option<String>,
|
||||
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), IronError>
|
||||
pcb: &Option<PreconnectionBlobPayload>,
|
||||
) -> connector::ConnectorResult<(ironrdp_futures::Upgraded, Vec<u8>, Box<dyn ConnectorCore>)>
|
||||
where
|
||||
S: ironrdp_futures::FramedRead + FramedWrite,
|
||||
S: FramedRead + FramedWrite,
|
||||
{
|
||||
use ironrdp::connector::Sequence as _;
|
||||
use x509_cert::der::Decode as _;
|
||||
|
@ -999,99 +1019,121 @@ where
|
|||
|
||||
let mut buf = WriteBuf::new();
|
||||
|
||||
info!("Begin connection procedure");
|
||||
debug!(?pcb, "Begin connection procedure");
|
||||
|
||||
{
|
||||
// RDCleanPath request
|
||||
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
|
||||
return Err(connector::general_err!("invalid connector state (send request)"));
|
||||
};
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else {
|
||||
return Err(anyhow::Error::msg("invalid connector state (send request)").into());
|
||||
debug_assert!(connector.next_pdu_hint().is_none());
|
||||
let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box<dyn ConnectorCore>) =
|
||||
if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb {
|
||||
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
|
||||
None,
|
||||
destination,
|
||||
proxy_auth_token,
|
||||
Some(vm_id.to_owned()),
|
||||
)
|
||||
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
|
||||
|
||||
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect");
|
||||
|
||||
let pcb_sent = ironrdp_futures::mark_pcb_sent_by_rdclean_path();
|
||||
let connector = ironrdp_futures::vm_connector_take_over(pcb_sent, connector)?;
|
||||
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
|
||||
} else {
|
||||
let written = connector.step_no_input(&mut buf)?;
|
||||
let x224_pdu_len = written.size().expect("written size");
|
||||
debug_assert_eq!(x224_pdu_len, buf.filled_len());
|
||||
let x224_pdu = buf.filled().to_vec();
|
||||
let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general());
|
||||
// RDCleanPath request
|
||||
|
||||
let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request(
|
||||
Some(x224_pdu),
|
||||
destination,
|
||||
proxy_auth_token,
|
||||
general_pcb.map(str::to_string),
|
||||
)
|
||||
.map_err(|e| connector::custom_err!("new RDCleanPath request", e))?;
|
||||
(rdcleanpath_req, Box::new(connector) as Box<dyn ConnectorCore>)
|
||||
};
|
||||
|
||||
debug_assert!(connector.next_pdu_hint().is_none());
|
||||
let rdcleanpath_request = rdcleanpath_request
|
||||
.to_der()
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?;
|
||||
|
||||
let written = connector.step_no_input(&mut buf)?;
|
||||
let x224_pdu_len = written.size().expect("written size");
|
||||
debug_assert_eq!(x224_pdu_len, buf.filled_len());
|
||||
let x224_pdu = buf.filled().to_vec();
|
||||
framed
|
||||
.write_all(&rdcleanpath_request)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("couldn’t write RDCleanPath request", e))?;
|
||||
|
||||
let rdcleanpath_req =
|
||||
ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb)
|
||||
.context("new RDCleanPath request")?;
|
||||
debug!(message = ?rdcleanpath_req, "Send RDCleanPath request");
|
||||
let rdcleanpath_req = rdcleanpath_req.to_der().context("RDCleanPath request encode")?;
|
||||
let rdcleanpath_result = framed
|
||||
.read_by_hint(&RDCLEANPATH_HINT)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("read RDCleanPath request", e))?;
|
||||
|
||||
framed
|
||||
.write_all(&rdcleanpath_req)
|
||||
.await
|
||||
.context("couldn’t write RDCleanPath request")?;
|
||||
let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result)
|
||||
.map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?;
|
||||
|
||||
debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain) = match rdcleanpath_result
|
||||
.into_enum()
|
||||
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
|
||||
{
|
||||
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
|
||||
return Err(connector::general_err!(
|
||||
"received an unexpected RDCleanPath type (request)",
|
||||
));
|
||||
}
|
||||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(connector::custom_err!("received an RDCleanPath error", error));
|
||||
}
|
||||
};
|
||||
|
||||
buf.clear();
|
||||
if let Some(x224_connection_response) = x224_connection_response {
|
||||
// let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state() else {
|
||||
// return Err(connector::general_err!("invalid connector state (wait confirm)"));
|
||||
// };
|
||||
debug_assert!(connector.next_pdu_hint().is_some());
|
||||
// Write the X.224 connection response PDU
|
||||
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
|
||||
debug_assert!(written.is_nothing());
|
||||
}
|
||||
|
||||
{
|
||||
// RDCleanPath response
|
||||
let server_public_key = extract_server_public_key(server_cert_chain)?;
|
||||
|
||||
let rdcleanpath_res = framed
|
||||
.read_by_hint(&RDCLEANPATH_HINT)
|
||||
.await
|
||||
.context("read RDCleanPath request")?;
|
||||
let should_upgrade = ironrdp_futures::skip_connect_begin(connector.as_mut());
|
||||
|
||||
let rdcleanpath_res =
|
||||
ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res).context("RDCleanPath response decode")?;
|
||||
let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector.as_mut());
|
||||
|
||||
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain) =
|
||||
match rdcleanpath_res.into_enum().context("invalid RDCleanPath PDU")? {
|
||||
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
|
||||
return Err(anyhow::Error::msg("received an unexpected RDCleanPath type (request)").into());
|
||||
}
|
||||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(
|
||||
IronError::from(anyhow::Error::new(error).context("received an RDCleanPath error"))
|
||||
.with_kind(IronErrorKind::RDCleanPath),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
|
||||
return Err(anyhow::Error::msg("invalid connector state (wait confirm)").into());
|
||||
};
|
||||
|
||||
debug_assert!(connector.next_pdu_hint().is_some());
|
||||
|
||||
buf.clear();
|
||||
let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?;
|
||||
|
||||
debug_assert!(written.is_nothing());
|
||||
return Ok((upgraded, server_public_key, connector));
|
||||
|
||||
fn extract_server_public_key(server_cert_chain: Vec<OctetString>) -> connector::ConnectorResult<Vec<u8>> {
|
||||
let server_cert = server_cert_chain
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("server cert chain missing from rdcleanpath response")?;
|
||||
.ok_or_else(|| connector::general_err!("server cert chain missing from rdcleanpath response"))?;
|
||||
|
||||
let cert = x509_cert::Certificate::from_der(server_cert.as_bytes())
|
||||
.context("failed to decode x509 certificate sent by proxy")?;
|
||||
.map_err(|e| connector::custom_err!("server cert chain missing from rdcleanpath response", e))?;
|
||||
|
||||
let server_public_key = cert
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.subject_public_key
|
||||
.as_bytes()
|
||||
.context("subject public key BIT STRING is not aligned")?
|
||||
.ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))?
|
||||
.to_owned();
|
||||
|
||||
let should_upgrade = ironrdp_futures::skip_connect_begin(connector);
|
||||
|
||||
// At this point, proxy established the TLS session.
|
||||
|
||||
let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector);
|
||||
|
||||
Ok((upgraded, server_public_key))
|
||||
Ok(server_public_key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1100,3 +1142,25 @@ where
|
|||
fn f64_to_u16_saturating_cast(value: f64) -> u16 {
|
||||
value as u16
|
||||
}
|
||||
|
||||
async fn downcast_back_to_client_connector(
|
||||
connector: Box<dyn ConnectorCore>, // `ConnectorCore: Any`
|
||||
credssp_finished: &mut ironrdp_futures::CredSSPFinished,
|
||||
framed: &mut ironrdp_futures::Framed<impl FramedRead + FramedWrite>,
|
||||
) -> connector::ConnectorResult<ClientConnector> {
|
||||
let connector: Box<dyn core::any::Any> = connector;
|
||||
|
||||
let client = match connector.downcast::<ironrdp_vmconnect::VmClientConnector>() {
|
||||
Ok(vm_connector) => ironrdp_futures::run_until_handover(credssp_finished, framed, *vm_connector).await?,
|
||||
Err(err) => match err.downcast::<ClientConnector>() {
|
||||
Ok(c) => *c,
|
||||
Err(_) => {
|
||||
return Err(connector::general_err!(
|
||||
"connector is neither ClientConnector nor VmClientConnector"
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
import { currentSession, userInteractionService } from '../../services/session.service';
|
||||
import type { UserInteraction } from '../../../static/iron-remote-desktop';
|
||||
import type { Session } from '../../models/session';
|
||||
import { preConnectionBlob, displayControl, kdcProxyUrl, init } from '../../../static/iron-remote-desktop-rdp';
|
||||
import {
|
||||
preConnectionBlob,
|
||||
displayControl,
|
||||
kdcProxyUrl,
|
||||
init,
|
||||
vmConnect,
|
||||
} from '../../../static/iron-remote-desktop-rdp';
|
||||
import { toast } from '$lib/messages/message-store';
|
||||
import { showLogin } from '$lib/login/login-store';
|
||||
import { onMount } from 'svelte';
|
||||
|
@ -16,6 +22,7 @@
|
|||
let kdc_proxy_url = '';
|
||||
let desktopSize = { width: 1280, height: 720 };
|
||||
let pcb = '';
|
||||
let vmconnect = '';
|
||||
let pop_up = false;
|
||||
let enable_clipboard = true;
|
||||
|
||||
|
@ -131,6 +138,10 @@
|
|||
configBuilder.withExtension(preConnectionBlob(pcb));
|
||||
}
|
||||
|
||||
if (vmconnect !== '') {
|
||||
configBuilder.withExtension(vmConnect(vmconnect));
|
||||
}
|
||||
|
||||
if (kdc_proxy_url !== '') {
|
||||
configBuilder.withExtension(kdcProxyUrl(kdc_proxy_url));
|
||||
}
|
||||
|
@ -202,6 +213,10 @@
|
|||
<input id="pcb" type="text" bind:value={pcb} />
|
||||
<label for="pcb">Pre Connection Blob</label>
|
||||
</div>
|
||||
<div class="field label border">
|
||||
<input id="pcb" type="text" bind:value={vmconnect} />
|
||||
<label for="pcb">HyperV VmConnect ID</label>
|
||||
</div>
|
||||
<div class="field label border">
|
||||
<input id="desktopSizeW" type="text" bind:value={desktopSize.width} />
|
||||
<label for="desktopSizeW">Desktop Width</label>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue