WIP, not working

This commit is contained in:
irving ou 2025-05-26 15:46:03 -04:00
parent 41ad74dbf1
commit 64f4f3cc1a
12 changed files with 156 additions and 52 deletions

View file

@ -132,6 +132,7 @@ where
server_name,
server_public_key,
kerberos_config,
connector.config.vmconnect.is_some(),
)?;
loop {

View file

@ -137,6 +137,7 @@ where
server_name,
server_public_key,
kerberos_config,
connector.config.vmconnect.is_some(),
)?;
loop {

View file

@ -239,9 +239,9 @@ struct Args {
#[clap(long, value_parser, num_args = 1.., value_delimiter = ',')]
codecs: Vec<String>,
/// The Preconnection Blob
/// The Virtual Machine ID for Hyper-V, used as Pre Connection Blob
#[clap(long)]
pcb: Option<String>,
vmconnect: Option<String>,
}
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

View file

@ -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)"));
};

View file

@ -36,7 +36,10 @@ pub enum ClientConnectorState {
#[default]
Consumed,
ConnectionInitiationSendRequest,
PreconnectionBlob,
ConnectionInitiationSendRequest {
selected_protocol: Option<nego::SecurityProtocol>,
},
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,12 +216,86 @@ 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,
},
);
};
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 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);
}
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, its 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",));
}
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 => 'state: {
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,
};
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::ConnectionInitiationWaitConfirm {
requested_protocol: selected_protocol,
},
);
}
let mut security_protocol = nego::SecurityProtocol::empty();
if self.config.enable_tls {
@ -240,23 +319,6 @@ 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
@ -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 } => (
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.

View file

@ -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<u8>,
kerberos_config: Option<KerberosConfig>,
vmconnect: bool,
) -> ConnectorResult<(Self, credssp::TsRequest)> {
let credentials: Option<sspi::Credentials> = 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(),
)
.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,
},
),
};

View file

@ -195,7 +195,7 @@ pub struct Config {
pub pointer_software_rendering: bool,
pub performance_flags: PerformanceFlags,
pub pcb: Option<String>,
pub vmconnect: Option<String>,
}
ironrdp_core::assert_impl!(Config: Send, Sync);

View file

@ -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
}
}

View file

@ -862,7 +862,7 @@ fn build_config(
hardware_id: None,
license_cache: None,
// TODO: implement this
pcb: None,
vmconnect: None,
}
}

View file

@ -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)))

View file

@ -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 { .. } => {

View file

@ -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 {