Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/ironrdp-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ironrdp = { workspace = true, features = [
"rdpsnd",
"cliprdr",
"displaycontrol",
"connector"
] }
ironrdp-cliprdr-native.workspace = true
ironrdp-rdpsnd-native.workspace = true
Expand Down Expand Up @@ -77,6 +78,7 @@ reqwest = "0.12"
url = "2.5"
raw-window-handle = "0.6.2"
ironrdp-core = { workspace = true, features = ["alloc"] }
uuid = { version = "1.11.0"}
Comment thread
probakowski marked this conversation as resolved.

[target.'cfg(windows)'.dependencies]
windows = { workspace = true, features = ["Win32_Foundation"] }
Expand Down
9 changes: 5 additions & 4 deletions crates/ironrdp-client/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use core::num::ParseIntError;
use core::str::FromStr;
use std::io;

use anyhow::Context as _;
use clap::clap_derive::ValueEnum;
use clap::Parser;
use core::num::ParseIntError;
use core::str::FromStr;
use ironrdp::connector::{self, Credentials};
use ironrdp::pdu::rdp::capability_sets::MajorPlatformType;
use ironrdp::pdu::rdp::client_info::PerformanceFlags;
use std::io;
use tap::prelude::*;

const DEFAULT_WIDTH: u16 = 1920;
Expand Down Expand Up @@ -316,6 +315,8 @@ impl Config {
whoami::Platform::Android => MajorPlatformType::ANDROID,
_ => MajorPlatformType::UNSPECIFIED,
},
hardware_id: None,
license_cache: None,
no_server_pointer: args.no_server_pointer,
autologon: args.autologon,
request_data: None,
Expand Down
13 changes: 9 additions & 4 deletions crates/ironrdp-connector/src/connection.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use core::mem;
use std::borrow::Cow;
use std::net::SocketAddr;

use ironrdp_core::{decode, encode_vec, Encode, WriteBuf};
use ironrdp_pdu::rdp::client_info::{OptionalSystemTime, TimezoneInfo};
use ironrdp_pdu::x224::X224;
use ironrdp_pdu::{gcc, mcs, nego, rdp, PduHint};
use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor};
use std::borrow::Cow;
use std::net::SocketAddr;
use std::sync::Arc;

use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState};
use crate::license_exchange::LicenseExchangeSequence;
use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache};
use crate::{
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State,
Written,
Expand Down Expand Up @@ -481,6 +481,11 @@ impl Sequence for ClientConnector {
io_channel_id,
self.config.credentials.username().unwrap_or("").to_owned(),
self.config.domain.clone(),
self.config.hardware_id.unwrap_or_default(),
self.config
.license_cache
.clone()
.unwrap_or_else(|| Arc::new(NoopLicenseCache)),
),
},
),
Expand Down
12 changes: 9 additions & 3 deletions crates/ironrdp-connector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ pub mod credssp;
mod license_exchange;
mod server_name;

use core::any::Any;
use core::fmt;

pub use crate::license_exchange::LicenseCache;
pub use channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
pub use connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
pub use connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
use core::any::Any;
use core::fmt;
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
use ironrdp_pdu::nego::NegoRequestData;
use ironrdp_pdu::rdp::capability_sets;
Expand All @@ -32,6 +32,7 @@ use ironrdp_pdu::{gcc, x224, PduHint};
pub use license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
pub use server_name::ServerName;
pub use sspi;
use std::sync::Arc;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
Expand Down Expand Up @@ -161,6 +162,10 @@ pub struct Config {
pub dig_product_id: String,
pub client_dir: String,
pub platform: capability_sets::MajorPlatformType,
/// Unique identifier for the computer
///
/// Each 32-bit integer contains client hardware-specific data helping the server uniquely identify the client.
pub hardware_id: Option<[u32; 4]>,
Comment thread
CBenoit marked this conversation as resolved.
/// Optional data for the x224 connection request.
///
/// Fallbacks to a sensible default depending on the provided credentials:
Expand All @@ -170,6 +175,7 @@ pub struct Config {
pub request_data: Option<NegoRequestData>,
/// If true, the INFO_AUTOLOGON flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu)
pub autologon: bool,
pub license_cache: Option<Arc<dyn LicenseCache>>,

// FIXME(@CBenoit): these are client-only options, not part of the connector.
pub no_server_pointer: bool,
Expand Down
181 changes: 133 additions & 48 deletions crates/ironrdp-connector/src/license_exchange.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{legacy, ConnectorError, ConnectorErrorExt};
use crate::{encode_send_data_request, ConnectorResult, ConnectorResultExt as _, Sequence, State, Written};
use core::fmt::Debug;
use core::{fmt, mem};

use ironrdp_core::WriteBuf;
use ironrdp_pdu::rdp::server_license::{self, LicensePdu, ServerLicenseError};
use ironrdp_pdu::rdp::server_license::{self, LicenseInformation, LicensePdu, ServerLicenseError};
use ironrdp_pdu::PduHint;
use rand_core::{OsRng, RngCore as _};

use super::legacy;
use crate::{encode_send_data_request, ConnectorResult, ConnectorResultExt as _, Sequence, State, Written};
use std::str;
use std::sync::Arc;

#[derive(Default, Debug)]
#[non_exhaustive]
Expand Down Expand Up @@ -57,15 +58,43 @@ pub struct LicenseExchangeSequence {
pub io_channel_id: u16,
pub username: String,
pub domain: Option<String>,
pub hardware_id: [u32; 4],
pub license_cache: Arc<dyn LicenseCache>,
}

pub trait LicenseCache: Sync + Send + Debug {
fn get_license(&self, license_info: LicenseInformation) -> ConnectorResult<Option<Vec<u8>>>;
fn store_license(&self, license_info: LicenseInformation) -> ConnectorResult<()>;
}

#[derive(Debug)]
pub(crate) struct NoopLicenseCache;

impl LicenseCache for NoopLicenseCache {
fn get_license(&self, _license_info: LicenseInformation) -> ConnectorResult<Option<Vec<u8>>> {
Ok(None)
}

fn store_license(&self, _license_info: LicenseInformation) -> ConnectorResult<()> {
Ok(())
}
}

impl LicenseExchangeSequence {
pub fn new(io_channel_id: u16, username: String, domain: Option<String>) -> Self {
pub fn new(
io_channel_id: u16,
username: String,
domain: Option<String>,
hardware_id: [u32; 4],
license_cache: Arc<dyn LicenseCache>,
) -> Self {
Self {
state: LicenseExchangeState::NewLicenseRequest,
io_channel_id,
username,
domain,
hardware_id,
license_cache,
}
}
}
Expand Down Expand Up @@ -107,52 +136,102 @@ impl Sequence for LicenseExchangeSequence {
let mut premaster_secret = [0u8; server_license::PREMASTER_SECRET_SIZE];
OsRng.fill_bytes(&mut premaster_secret);

match server_license::ClientNewLicenseRequest::from_server_license_request(
&license_request,
&client_random,
&premaster_secret,
&self.username,
self.domain.as_deref().unwrap_or(""),
) {
Ok((new_license_request, encryption_data)) => {
trace!(?encryption_data, "Successfully generated Client New License Request");
info!(message = ?new_license_request, "Send");

let written = encode_send_data_request::<LicensePdu>(
send_data_indication_ctx.initiator_id,
send_data_indication_ctx.channel_id,
&new_license_request.into(),
output,
)?;

(
Written::from_size(written)?,
LicenseExchangeState::PlatformChallenge { encryption_data },
)
let license_info = license_request
.scope_list
.iter()
.filter_map(|scope| {
self.license_cache
.get_license(LicenseInformation {
version: license_request.product_info.version,
scope: scope.0.clone(),
company_name: license_request.product_info.company_name.clone(),
product_id: license_request.product_info.product_id.clone(),
license_info: vec![],
})
.transpose()
})
.next()
.transpose()?;

if let Some(info) = license_info {
match server_license::ClientLicenseInfo::from_server_license_request(
&license_request,
&client_random,
&premaster_secret,
self.hardware_id,
info,
) {
Ok((client_license_info, encryption_data)) => {
trace!(?encryption_data, "Successfully generated Client License Info");
info!(message = ?client_license_info, "Send");

let written = encode_send_data_request::<LicensePdu>(
send_data_indication_ctx.initiator_id,
send_data_indication_ctx.channel_id,
&client_license_info.into(),
output,
)?;

trace!(?written, "Written ClientLicenseInfo");

(
Written::from_size(written)?,
LicenseExchangeState::PlatformChallenge { encryption_data },
)
}
Err(err) => {
return Err(custom_err!("ClientNewLicenseRequest", err));
}
}
Err(error) => {
if let ServerLicenseError::InvalidX509Certificate {
source: error,
cert_der,
} = &error
{
struct BytesHexFormatter<'a>(&'a [u8]);

impl fmt::Display for BytesHexFormatter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x")?;
self.0.iter().try_for_each(|byte| write!(f, "{byte:02X}"))
} else {
let hwid = self.hardware_id;
match server_license::ClientNewLicenseRequest::from_server_license_request(
&license_request,
&client_random,
&premaster_secret,
&self.username,
&format!("{:X}-{:X}-{:X}-{:X}", hwid[0], hwid[1], hwid[2], hwid[3]),
) {
Ok((new_license_request, encryption_data)) => {
trace!(?encryption_data, "Successfully generated Client New License Request");
info!(message = ?new_license_request, "Send");

let written = encode_send_data_request::<LicensePdu>(
send_data_indication_ctx.initiator_id,
send_data_indication_ctx.channel_id,
&new_license_request.into(),
output,
)?;

(
Written::from_size(written)?,
LicenseExchangeState::PlatformChallenge { encryption_data },
)
}
Err(error) => {
if let ServerLicenseError::InvalidX509Certificate {
source: error,
cert_der,
} = &error
{
struct BytesHexFormatter<'a>(&'a [u8]);

impl fmt::Display for BytesHexFormatter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x")?;
self.0.iter().try_for_each(|byte| write!(f, "{byte:02X}"))
}
}

error!(
%error,
cert_der = %BytesHexFormatter(cert_der),
"Unsupported or invalid X509 certificate received during license exchange step"
);
}

error!(
%error,
cert_der = %BytesHexFormatter(cert_der),
"Unsupported or invalid X509 certificate received during license exchange step"
);
return Err(custom_err!("ClientNewLicenseRequest", error));
}

return Err(custom_err!("ClientNewLicenseRequest", error));
}
}
}
Expand Down Expand Up @@ -188,7 +267,7 @@ impl Sequence for LicenseExchangeSequence {
let challenge_response =
server_license::ClientPlatformChallengeResponse::from_server_platform_challenge(
&challenge,
self.domain.as_deref().unwrap_or(""),
self.hardware_id,
&encryption_data,
)
.map_err(|e| custom_err!("ClientPlatformChallengeResponse", e))?;
Expand Down Expand Up @@ -242,6 +321,12 @@ impl Sequence for LicenseExchangeSequence {
.map_err(|e| custom_err!("license verification", e))?;

debug!("License verified with success");

let license_info = upgrade_license
.new_license_info(&encryption_data)
.map_err(ConnectorError::decode)?;

self.license_cache.store_license(license_info)?
}
LicensePdu::LicensingErrorMessage(error_message) => {
if error_message.error_code != server_license::LicenseErrorCode::StatusValidClient {
Expand Down
Loading