mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-12 20:15:00 +00:00
feat: support license caching (#634)
Adds support for license caching by storing the license obtained from SERVER_UPGRADE_LICENSE message and sending CLIENT_LICENSE_INFO if a license requested by the server is already stored in the cache. Co-authored-by: Benoît Cortier <3809077+CBenoit@users.noreply.github.com>
This commit is contained in:
parent
a2378efb7a
commit
dd221bf224
16 changed files with 421 additions and 97 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2416,6 +2416,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
|
@ -41,6 +41,7 @@ ironrdp = { workspace = true, features = [
|
||||||
"rdpsnd",
|
"rdpsnd",
|
||||||
"cliprdr",
|
"cliprdr",
|
||||||
"displaycontrol",
|
"displaycontrol",
|
||||||
|
"connector"
|
||||||
] }
|
] }
|
||||||
ironrdp-cliprdr-native.workspace = true
|
ironrdp-cliprdr-native.workspace = true
|
||||||
ironrdp-rdpsnd-native.workspace = true
|
ironrdp-rdpsnd-native.workspace = true
|
||||||
|
@ -77,6 +78,7 @@ reqwest = "0.12"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
raw-window-handle = "0.6.2"
|
raw-window-handle = "0.6.2"
|
||||||
ironrdp-core = { workspace = true, features = ["alloc"] }
|
ironrdp-core = { workspace = true, features = ["alloc"] }
|
||||||
|
uuid = { version = "1.11.0"}
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { workspace = true, features = ["Win32_Foundation"] }
|
windows = { workspace = true, features = ["Win32_Foundation"] }
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use core::num::ParseIntError;
|
|
||||||
use core::str::FromStr;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use clap::clap_derive::ValueEnum;
|
use clap::clap_derive::ValueEnum;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use core::num::ParseIntError;
|
||||||
|
use core::str::FromStr;
|
||||||
use ironrdp::connector::{self, Credentials};
|
use ironrdp::connector::{self, Credentials};
|
||||||
use ironrdp::pdu::rdp::capability_sets::MajorPlatformType;
|
use ironrdp::pdu::rdp::capability_sets::MajorPlatformType;
|
||||||
use ironrdp::pdu::rdp::client_info::PerformanceFlags;
|
use ironrdp::pdu::rdp::client_info::PerformanceFlags;
|
||||||
|
use std::io;
|
||||||
use tap::prelude::*;
|
use tap::prelude::*;
|
||||||
|
|
||||||
const DEFAULT_WIDTH: u16 = 1920;
|
const DEFAULT_WIDTH: u16 = 1920;
|
||||||
|
@ -316,6 +315,8 @@ impl Config {
|
||||||
whoami::Platform::Android => MajorPlatformType::ANDROID,
|
whoami::Platform::Android => MajorPlatformType::ANDROID,
|
||||||
_ => MajorPlatformType::UNSPECIFIED,
|
_ => MajorPlatformType::UNSPECIFIED,
|
||||||
},
|
},
|
||||||
|
hardware_id: None,
|
||||||
|
license_cache: None,
|
||||||
no_server_pointer: args.no_server_pointer,
|
no_server_pointer: args.no_server_pointer,
|
||||||
autologon: args.autologon,
|
autologon: args.autologon,
|
||||||
request_data: None,
|
request_data: None,
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
use ironrdp_core::{decode, encode_vec, Encode, WriteBuf};
|
use ironrdp_core::{decode, encode_vec, Encode, WriteBuf};
|
||||||
use ironrdp_pdu::rdp::client_info::{OptionalSystemTime, TimezoneInfo};
|
use ironrdp_pdu::rdp::client_info::{OptionalSystemTime, TimezoneInfo};
|
||||||
use ironrdp_pdu::x224::X224;
|
use ironrdp_pdu::x224::X224;
|
||||||
use ironrdp_pdu::{gcc, mcs, nego, rdp, PduHint};
|
use ironrdp_pdu::{gcc, mcs, nego, rdp, PduHint};
|
||||||
use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor};
|
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::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
|
||||||
use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState};
|
use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState};
|
||||||
use crate::license_exchange::LicenseExchangeSequence;
|
use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache};
|
||||||
use crate::{
|
use crate::{
|
||||||
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State,
|
encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State,
|
||||||
Written,
|
Written,
|
||||||
|
@ -481,6 +481,11 @@ impl Sequence for ClientConnector {
|
||||||
io_channel_id,
|
io_channel_id,
|
||||||
self.config.credentials.username().unwrap_or("").to_owned(),
|
self.config.credentials.username().unwrap_or("").to_owned(),
|
||||||
self.config.domain.clone(),
|
self.config.domain.clone(),
|
||||||
|
self.config.hardware_id.unwrap_or_default(),
|
||||||
|
self.config
|
||||||
|
.license_cache
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| Arc::new(NoopLicenseCache)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,12 +17,12 @@ pub mod credssp;
|
||||||
mod license_exchange;
|
mod license_exchange;
|
||||||
mod server_name;
|
mod server_name;
|
||||||
|
|
||||||
use core::any::Any;
|
pub use crate::license_exchange::LicenseCache;
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
pub use channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
|
pub use channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
|
||||||
pub use connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
|
pub use connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
|
||||||
pub use connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
|
pub use connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
|
||||||
|
use core::any::Any;
|
||||||
|
use core::fmt;
|
||||||
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
|
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
|
||||||
use ironrdp_pdu::nego::NegoRequestData;
|
use ironrdp_pdu::nego::NegoRequestData;
|
||||||
use ironrdp_pdu::rdp::capability_sets;
|
use ironrdp_pdu::rdp::capability_sets;
|
||||||
|
@ -32,6 +32,7 @@ use ironrdp_pdu::{gcc, x224, PduHint};
|
||||||
pub use license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
|
pub use license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
|
||||||
pub use server_name::ServerName;
|
pub use server_name::ServerName;
|
||||||
pub use sspi;
|
pub use sspi;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
@ -161,6 +162,10 @@ pub struct Config {
|
||||||
pub dig_product_id: String,
|
pub dig_product_id: String,
|
||||||
pub client_dir: String,
|
pub client_dir: String,
|
||||||
pub platform: capability_sets::MajorPlatformType,
|
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]>,
|
||||||
/// Optional data for the x224 connection request.
|
/// Optional data for the x224 connection request.
|
||||||
///
|
///
|
||||||
/// Fallbacks to a sensible default depending on the provided credentials:
|
/// Fallbacks to a sensible default depending on the provided credentials:
|
||||||
|
@ -170,6 +175,7 @@ pub struct Config {
|
||||||
pub request_data: Option<NegoRequestData>,
|
pub request_data: Option<NegoRequestData>,
|
||||||
/// If true, the INFO_AUTOLOGON flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu)
|
/// If true, the INFO_AUTOLOGON flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu)
|
||||||
pub autologon: bool,
|
pub autologon: bool,
|
||||||
|
pub license_cache: Option<Arc<dyn LicenseCache>>,
|
||||||
|
|
||||||
// FIXME(@CBenoit): these are client-only options, not part of the connector.
|
// FIXME(@CBenoit): these are client-only options, not part of the connector.
|
||||||
pub no_server_pointer: bool,
|
pub no_server_pointer: bool,
|
||||||
|
|
|
@ -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 core::{fmt, mem};
|
||||||
|
|
||||||
use ironrdp_core::WriteBuf;
|
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 ironrdp_pdu::PduHint;
|
||||||
use rand_core::{OsRng, RngCore as _};
|
use rand_core::{OsRng, RngCore as _};
|
||||||
|
use std::str;
|
||||||
use super::legacy;
|
use std::sync::Arc;
|
||||||
use crate::{encode_send_data_request, ConnectorResult, ConnectorResultExt as _, Sequence, State, Written};
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -57,15 +58,43 @@ pub struct LicenseExchangeSequence {
|
||||||
pub io_channel_id: u16,
|
pub io_channel_id: u16,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub domain: Option<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 {
|
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 {
|
Self {
|
||||||
state: LicenseExchangeState::NewLicenseRequest,
|
state: LicenseExchangeState::NewLicenseRequest,
|
||||||
io_channel_id,
|
io_channel_id,
|
||||||
username,
|
username,
|
||||||
domain,
|
domain,
|
||||||
|
hardware_id,
|
||||||
|
license_cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,52 +136,102 @@ impl Sequence for LicenseExchangeSequence {
|
||||||
let mut premaster_secret = [0u8; server_license::PREMASTER_SECRET_SIZE];
|
let mut premaster_secret = [0u8; server_license::PREMASTER_SECRET_SIZE];
|
||||||
OsRng.fill_bytes(&mut premaster_secret);
|
OsRng.fill_bytes(&mut premaster_secret);
|
||||||
|
|
||||||
match server_license::ClientNewLicenseRequest::from_server_license_request(
|
let license_info = license_request
|
||||||
&license_request,
|
.scope_list
|
||||||
&client_random,
|
.iter()
|
||||||
&premaster_secret,
|
.filter_map(|scope| {
|
||||||
&self.username,
|
self.license_cache
|
||||||
self.domain.as_deref().unwrap_or(""),
|
.get_license(LicenseInformation {
|
||||||
) {
|
version: license_request.product_info.version,
|
||||||
Ok((new_license_request, encryption_data)) => {
|
scope: scope.0.clone(),
|
||||||
trace!(?encryption_data, "Successfully generated Client New License Request");
|
company_name: license_request.product_info.company_name.clone(),
|
||||||
info!(message = ?new_license_request, "Send");
|
product_id: license_request.product_info.product_id.clone(),
|
||||||
|
license_info: vec![],
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let written = encode_send_data_request::<LicensePdu>(
|
if let Some(info) = license_info {
|
||||||
send_data_indication_ctx.initiator_id,
|
match server_license::ClientLicenseInfo::from_server_license_request(
|
||||||
send_data_indication_ctx.channel_id,
|
&license_request,
|
||||||
&new_license_request.into(),
|
&client_random,
|
||||||
output,
|
&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>(
|
||||||
Written::from_size(written)?,
|
send_data_indication_ctx.initiator_id,
|
||||||
LicenseExchangeState::PlatformChallenge { encryption_data },
|
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) => {
|
} else {
|
||||||
if let ServerLicenseError::InvalidX509Certificate {
|
let hwid = self.hardware_id;
|
||||||
source: error,
|
match server_license::ClientNewLicenseRequest::from_server_license_request(
|
||||||
cert_der,
|
&license_request,
|
||||||
} = &error
|
&client_random,
|
||||||
{
|
&premaster_secret,
|
||||||
struct BytesHexFormatter<'a>(&'a [u8]);
|
&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");
|
||||||
|
|
||||||
impl fmt::Display for BytesHexFormatter<'_> {
|
let written = encode_send_data_request::<LicensePdu>(
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
send_data_indication_ctx.initiator_id,
|
||||||
write!(f, "0x")?;
|
send_data_indication_ctx.channel_id,
|
||||||
self.0.iter().try_for_each(|byte| write!(f, "{byte:02X}"))
|
&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!(
|
return Err(custom_err!("ClientNewLicenseRequest", error));
|
||||||
%error,
|
|
||||||
cert_der = %BytesHexFormatter(cert_der),
|
|
||||||
"Unsupported or invalid X509 certificate received during license exchange step"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(custom_err!("ClientNewLicenseRequest", error));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +267,7 @@ impl Sequence for LicenseExchangeSequence {
|
||||||
let challenge_response =
|
let challenge_response =
|
||||||
server_license::ClientPlatformChallengeResponse::from_server_platform_challenge(
|
server_license::ClientPlatformChallengeResponse::from_server_platform_challenge(
|
||||||
&challenge,
|
&challenge,
|
||||||
self.domain.as_deref().unwrap_or(""),
|
self.hardware_id,
|
||||||
&encryption_data,
|
&encryption_data,
|
||||||
)
|
)
|
||||||
.map_err(|e| custom_err!("ClientPlatformChallengeResponse", e))?;
|
.map_err(|e| custom_err!("ClientPlatformChallengeResponse", e))?;
|
||||||
|
@ -242,6 +321,12 @@ impl Sequence for LicenseExchangeSequence {
|
||||||
.map_err(|e| custom_err!("license verification", e))?;
|
.map_err(|e| custom_err!("license verification", e))?;
|
||||||
|
|
||||||
debug!("License verified with success");
|
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) => {
|
LicensePdu::LicensingErrorMessage(error_message) => {
|
||||||
if error_message.error_code != server_license::LicenseErrorCode::StatusValidClient {
|
if error_message.error_code != server_license::LicenseErrorCode::StatusValidClient {
|
||||||
|
|
|
@ -11,11 +11,13 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::rdp::headers::{BasicSecurityHeader, BasicSecurityHeaderFlags, BASIC_SECURITY_HEADER_SIZE};
|
use crate::rdp::headers::{BasicSecurityHeader, BasicSecurityHeaderFlags, BASIC_SECURITY_HEADER_SIZE};
|
||||||
|
pub use crate::rdp::server_license::client_license_info::ClientLicenseInfo;
|
||||||
use crate::PduError;
|
use crate::PduError;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
mod client_license_info;
|
||||||
mod client_new_license_request;
|
mod client_new_license_request;
|
||||||
mod client_platform_challenge_response;
|
mod client_platform_challenge_response;
|
||||||
mod licensing_error_message;
|
mod licensing_error_message;
|
||||||
|
@ -30,7 +32,7 @@ pub use self::client_platform_challenge_response::{
|
||||||
pub use self::licensing_error_message::{LicenseErrorCode, LicensingErrorMessage, LicensingStateTransition};
|
pub use self::licensing_error_message::{LicenseErrorCode, LicensingErrorMessage, LicensingStateTransition};
|
||||||
pub use self::server_license_request::{cert, ProductInfo, Scope, ServerCertificate, ServerLicenseRequest};
|
pub use self::server_license_request::{cert, ProductInfo, Scope, ServerCertificate, ServerLicenseRequest};
|
||||||
pub use self::server_platform_challenge::ServerPlatformChallenge;
|
pub use self::server_platform_challenge::ServerPlatformChallenge;
|
||||||
pub use self::server_upgrade_license::{NewLicenseInformation, ServerUpgradeLicense};
|
pub use self::server_upgrade_license::{LicenseInformation, ServerUpgradeLicense};
|
||||||
|
|
||||||
pub const PREAMBLE_SIZE: usize = 4;
|
pub const PREAMBLE_SIZE: usize = 4;
|
||||||
pub const PREMASTER_SECRET_SIZE: usize = 48;
|
pub const PREMASTER_SECRET_SIZE: usize = 48;
|
||||||
|
@ -341,6 +343,7 @@ fn compute_mac_data(mac_salt_key: &[u8], data: &[u8]) -> Vec<u8> {
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum LicensePdu {
|
pub enum LicensePdu {
|
||||||
ClientNewLicenseRequest(ClientNewLicenseRequest),
|
ClientNewLicenseRequest(ClientNewLicenseRequest),
|
||||||
|
ClientLicenseInfo(ClientLicenseInfo),
|
||||||
ClientPlatformChallengeResponse(ClientPlatformChallengeResponse),
|
ClientPlatformChallengeResponse(ClientPlatformChallengeResponse),
|
||||||
ServerLicenseRequest(ServerLicenseRequest),
|
ServerLicenseRequest(ServerLicenseRequest),
|
||||||
ServerPlatformChallenge(ServerPlatformChallenge),
|
ServerPlatformChallenge(ServerPlatformChallenge),
|
||||||
|
@ -375,6 +378,7 @@ impl Encode for LicensePdu {
|
||||||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::ClientNewLicenseRequest(ref pdu) => pdu.encode(dst),
|
Self::ClientNewLicenseRequest(ref pdu) => pdu.encode(dst),
|
||||||
|
Self::ClientLicenseInfo(ref pdu) => pdu.encode(dst),
|
||||||
Self::ClientPlatformChallengeResponse(ref pdu) => pdu.encode(dst),
|
Self::ClientPlatformChallengeResponse(ref pdu) => pdu.encode(dst),
|
||||||
Self::ServerLicenseRequest(ref pdu) => pdu.encode(dst),
|
Self::ServerLicenseRequest(ref pdu) => pdu.encode(dst),
|
||||||
Self::ServerPlatformChallenge(ref pdu) => pdu.encode(dst),
|
Self::ServerPlatformChallenge(ref pdu) => pdu.encode(dst),
|
||||||
|
@ -386,6 +390,7 @@ impl Encode for LicensePdu {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::ClientNewLicenseRequest(pdu) => pdu.name(),
|
Self::ClientNewLicenseRequest(pdu) => pdu.name(),
|
||||||
|
Self::ClientLicenseInfo(pdu) => pdu.name(),
|
||||||
Self::ClientPlatformChallengeResponse(pdu) => pdu.name(),
|
Self::ClientPlatformChallengeResponse(pdu) => pdu.name(),
|
||||||
Self::ServerLicenseRequest(pdu) => pdu.name(),
|
Self::ServerLicenseRequest(pdu) => pdu.name(),
|
||||||
Self::ServerPlatformChallenge(pdu) => pdu.name(),
|
Self::ServerPlatformChallenge(pdu) => pdu.name(),
|
||||||
|
@ -397,6 +402,7 @@ impl Encode for LicensePdu {
|
||||||
fn size(&self) -> usize {
|
fn size(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::ClientNewLicenseRequest(pdu) => pdu.size(),
|
Self::ClientNewLicenseRequest(pdu) => pdu.size(),
|
||||||
|
Self::ClientLicenseInfo(pdu) => pdu.size(),
|
||||||
Self::ClientPlatformChallengeResponse(pdu) => pdu.size(),
|
Self::ClientPlatformChallengeResponse(pdu) => pdu.size(),
|
||||||
Self::ServerLicenseRequest(pdu) => pdu.size(),
|
Self::ServerLicenseRequest(pdu) => pdu.size(),
|
||||||
Self::ServerPlatformChallenge(pdu) => pdu.size(),
|
Self::ServerPlatformChallenge(pdu) => pdu.size(),
|
||||||
|
@ -412,6 +418,12 @@ impl From<ClientNewLicenseRequest> for LicensePdu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ClientLicenseInfo> for LicensePdu {
|
||||||
|
fn from(pdu: ClientLicenseInfo) -> Self {
|
||||||
|
Self::ClientLicenseInfo(pdu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ClientPlatformChallengeResponse> for LicensePdu {
|
impl From<ClientPlatformChallengeResponse> for LicensePdu {
|
||||||
fn from(pdu: ClientPlatformChallengeResponse) -> Self {
|
fn from(pdu: ClientPlatformChallengeResponse) -> Self {
|
||||||
Self::ClientPlatformChallengeResponse(pdu)
|
Self::ClientPlatformChallengeResponse(pdu)
|
||||||
|
|
208
crates/ironrdp-pdu/src/rdp/server_license/client_license_info.rs
Normal file
208
crates/ironrdp-pdu/src/rdp/server_license/client_license_info.rs
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
use crate::crypto::rc4::Rc4;
|
||||||
|
use crate::crypto::rsa::encrypt_with_public_key;
|
||||||
|
use crate::rdp::headers::{BasicSecurityHeader, BasicSecurityHeaderFlags};
|
||||||
|
use crate::rdp::server_license::client_new_license_request::{compute_master_secret, compute_session_key_blob};
|
||||||
|
use crate::rdp::server_license::client_platform_challenge_response::CLIENT_HARDWARE_IDENTIFICATION_SIZE;
|
||||||
|
use crate::rdp::server_license::{
|
||||||
|
compute_mac_data, BlobHeader, BlobType, LicenseEncryptionData, LicenseHeader, PreambleFlags, PreambleType,
|
||||||
|
PreambleVersion, ServerLicenseError, ServerLicenseRequest, KEY_EXCHANGE_ALGORITHM_RSA, MAC_SIZE, PLATFORM_ID,
|
||||||
|
PREAMBLE_SIZE, RANDOM_NUMBER_SIZE,
|
||||||
|
};
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
|
use ironrdp_core::{
|
||||||
|
ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
|
||||||
|
};
|
||||||
|
use md5::Digest;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
const LICENSE_INFO_STATIC_FIELDS_SIZE: usize = 20;
|
||||||
|
|
||||||
|
/// [2.2.2.3] Client License Info (CLIENT_LICENSE_INFO)
|
||||||
|
///
|
||||||
|
/// [2.2.2.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/9407b2eb-f180-4827-9488-cdbff4a5d4ea
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ClientLicenseInfo {
|
||||||
|
pub license_header: LicenseHeader,
|
||||||
|
pub client_random: Vec<u8>,
|
||||||
|
pub encrypted_premaster_secret: Vec<u8>,
|
||||||
|
pub license_info: Vec<u8>,
|
||||||
|
pub encrypted_hwid: Vec<u8>,
|
||||||
|
pub mac_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientLicenseInfo {
|
||||||
|
const NAME: &'static str = "ClientLicenseInfo";
|
||||||
|
|
||||||
|
pub fn from_server_license_request(
|
||||||
|
license_request: &ServerLicenseRequest,
|
||||||
|
client_random: &[u8],
|
||||||
|
premaster_secret: &[u8],
|
||||||
|
hardware_data: [u32; 4],
|
||||||
|
license_info: Vec<u8>,
|
||||||
|
) -> Result<(Self, LicenseEncryptionData), ServerLicenseError> {
|
||||||
|
let public_key = license_request.get_public_key()?
|
||||||
|
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData,
|
||||||
|
"attempted to retrieve the server public key from a server license request message that does not have a certificate"))?;
|
||||||
|
let encrypted_premaster_secret = encrypt_with_public_key(premaster_secret, &public_key)?;
|
||||||
|
|
||||||
|
let master_secret = compute_master_secret(
|
||||||
|
premaster_secret,
|
||||||
|
client_random,
|
||||||
|
license_request.server_random.as_slice(),
|
||||||
|
);
|
||||||
|
let session_key_blob = compute_session_key_blob(
|
||||||
|
master_secret.as_slice(),
|
||||||
|
client_random,
|
||||||
|
license_request.server_random.as_slice(),
|
||||||
|
);
|
||||||
|
let mac_salt_key = &session_key_blob[..16];
|
||||||
|
|
||||||
|
let mut md5 = md5::Md5::new();
|
||||||
|
md5.update(
|
||||||
|
[
|
||||||
|
&session_key_blob[16..32],
|
||||||
|
client_random,
|
||||||
|
license_request.server_random.as_slice(),
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
.as_slice(),
|
||||||
|
);
|
||||||
|
let license_key = md5.finalize().to_vec();
|
||||||
|
|
||||||
|
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
||||||
|
hardware_id.write_u32::<LittleEndian>(PLATFORM_ID)?;
|
||||||
|
for data in hardware_data {
|
||||||
|
hardware_id.write_u32::<LittleEndian>(data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rc4 = Rc4::new(&license_key);
|
||||||
|
let encrypted_hwid = rc4.process(&hardware_id);
|
||||||
|
|
||||||
|
let mac_data = compute_mac_data(mac_salt_key, &hardware_id);
|
||||||
|
|
||||||
|
let size = RANDOM_NUMBER_SIZE
|
||||||
|
+ PREAMBLE_SIZE
|
||||||
|
+ LICENSE_INFO_STATIC_FIELDS_SIZE
|
||||||
|
+ encrypted_premaster_secret.len()
|
||||||
|
+ license_info.len()
|
||||||
|
+ encrypted_hwid.len()
|
||||||
|
+ MAC_SIZE;
|
||||||
|
|
||||||
|
let license_header = LicenseHeader {
|
||||||
|
security_header: BasicSecurityHeader {
|
||||||
|
flags: BasicSecurityHeaderFlags::LICENSE_PKT,
|
||||||
|
},
|
||||||
|
preamble_message_type: PreambleType::LicenseInfo,
|
||||||
|
preamble_flags: PreambleFlags::empty(),
|
||||||
|
preamble_version: PreambleVersion::V3,
|
||||||
|
preamble_message_size: (size) as u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Self {
|
||||||
|
license_header,
|
||||||
|
client_random: Vec::from(client_random),
|
||||||
|
encrypted_premaster_secret,
|
||||||
|
license_info,
|
||||||
|
encrypted_hwid,
|
||||||
|
mac_data,
|
||||||
|
},
|
||||||
|
LicenseEncryptionData {
|
||||||
|
premaster_secret: Vec::from(premaster_secret),
|
||||||
|
mac_salt_key: Vec::from(mac_salt_key),
|
||||||
|
license_key,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientLicenseInfo {
|
||||||
|
pub fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||||
|
ensure_size!(in: dst, size: self.size());
|
||||||
|
|
||||||
|
self.license_header.encode(dst)?;
|
||||||
|
|
||||||
|
dst.write_u32(KEY_EXCHANGE_ALGORITHM_RSA);
|
||||||
|
dst.write_u32(PLATFORM_ID);
|
||||||
|
dst.write_slice(&self.client_random);
|
||||||
|
|
||||||
|
BlobHeader::new(BlobType::RANDOM, self.encrypted_premaster_secret.len()).encode(dst)?;
|
||||||
|
dst.write_slice(&self.encrypted_premaster_secret);
|
||||||
|
|
||||||
|
BlobHeader::new(BlobType::DATA, self.license_info.len()).encode(dst)?;
|
||||||
|
dst.write_slice(&self.license_info);
|
||||||
|
|
||||||
|
BlobHeader::new(BlobType::ENCRYPTED_DATA, self.encrypted_hwid.len()).encode(dst)?;
|
||||||
|
dst.write_slice(&self.encrypted_hwid);
|
||||||
|
|
||||||
|
dst.write_slice(&self.mac_data);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
Self::NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.license_header.size()
|
||||||
|
+ LICENSE_INFO_STATIC_FIELDS_SIZE
|
||||||
|
+ RANDOM_NUMBER_SIZE
|
||||||
|
+ self.encrypted_premaster_secret.len()
|
||||||
|
+ self.license_info.len()
|
||||||
|
+ self.encrypted_hwid.len()
|
||||||
|
+ MAC_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientLicenseInfo {
|
||||||
|
pub fn decode(license_header: LicenseHeader, src: &mut ReadCursor<'_>) -> DecodeResult<Self> {
|
||||||
|
if license_header.preamble_message_type != PreambleType::LicenseInfo {
|
||||||
|
return Err(invalid_field_err!("preambleMessageType", "unexpected preamble type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_exchange_algorithm = src.read_u32();
|
||||||
|
if key_exchange_algorithm != KEY_EXCHANGE_ALGORITHM_RSA {
|
||||||
|
return Err(invalid_field_err!("keyExchangeAlgo", "invalid key exchange algorithm"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can ignore platform ID
|
||||||
|
let _platform_id = src.read_u32();
|
||||||
|
|
||||||
|
ensure_size!(in: src, size: RANDOM_NUMBER_SIZE);
|
||||||
|
let client_random = src.read_slice(RANDOM_NUMBER_SIZE).into();
|
||||||
|
|
||||||
|
let premaster_secret_blob_header = BlobHeader::decode(src)?;
|
||||||
|
if premaster_secret_blob_header.blob_type != BlobType::RANDOM {
|
||||||
|
return Err(invalid_field_err!("blobType", "invalid blob type"));
|
||||||
|
}
|
||||||
|
ensure_size!(in: src, size: premaster_secret_blob_header.length);
|
||||||
|
let encrypted_premaster_secret = src.read_slice(premaster_secret_blob_header.length).into();
|
||||||
|
|
||||||
|
let license_info_blob_header = BlobHeader::decode(src)?;
|
||||||
|
if license_info_blob_header.blob_type != BlobType::DATA {
|
||||||
|
return Err(invalid_field_err!("blobType", "invalid blob type"));
|
||||||
|
}
|
||||||
|
ensure_size!(in: src, size: license_info_blob_header.length);
|
||||||
|
let license_info = src.read_slice(license_info_blob_header.length).into();
|
||||||
|
|
||||||
|
let encrypted_hwid_blob_header = BlobHeader::decode(src)?;
|
||||||
|
if encrypted_hwid_blob_header.blob_type != BlobType::DATA {
|
||||||
|
return Err(invalid_field_err!("blobType", "invalid blob type"));
|
||||||
|
}
|
||||||
|
ensure_size!(in: src, size: encrypted_hwid_blob_header.length);
|
||||||
|
let encrypted_hwid = src.read_slice(encrypted_hwid_blob_header.length).into();
|
||||||
|
|
||||||
|
ensure_size!(in: src, size: MAC_SIZE);
|
||||||
|
let mac_data = src.read_slice(MAC_SIZE).into();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
license_header,
|
||||||
|
client_random,
|
||||||
|
encrypted_premaster_secret,
|
||||||
|
license_info,
|
||||||
|
encrypted_hwid,
|
||||||
|
mac_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -231,7 +231,7 @@ fn salted_hash(salt: &[u8], salt_first: &[u8], salt_second: &[u8], input: &[u8])
|
||||||
}
|
}
|
||||||
|
|
||||||
// According to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/88061224-4a2f-4a28-a52e-e896b75ed2d3
|
// According to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/88061224-4a2f-4a28-a52e-e896b75ed2d3
|
||||||
fn compute_master_secret(premaster_secret: &[u8], client_random: &[u8], server_random: &[u8]) -> Vec<u8> {
|
pub(crate) fn compute_master_secret(premaster_secret: &[u8], client_random: &[u8], server_random: &[u8]) -> Vec<u8> {
|
||||||
[
|
[
|
||||||
salted_hash(premaster_secret, client_random, server_random, b"A"),
|
salted_hash(premaster_secret, client_random, server_random, b"A"),
|
||||||
salted_hash(premaster_secret, client_random, server_random, b"BB"),
|
salted_hash(premaster_secret, client_random, server_random, b"BB"),
|
||||||
|
@ -241,7 +241,7 @@ fn compute_master_secret(premaster_secret: &[u8], client_random: &[u8], server_r
|
||||||
}
|
}
|
||||||
|
|
||||||
// According to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/88061224-4a2f-4a28-a52e-e896b75ed2d3
|
// According to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/88061224-4a2f-4a28-a52e-e896b75ed2d3
|
||||||
fn compute_session_key_blob(master_secret: &[u8], client_random: &[u8], server_random: &[u8]) -> Vec<u8> {
|
pub(crate) fn compute_session_key_blob(master_secret: &[u8], client_random: &[u8], server_random: &[u8]) -> Vec<u8> {
|
||||||
[
|
[
|
||||||
salted_hash(master_secret, server_random, client_random, b"A"),
|
salted_hash(master_secret, server_random, client_random, b"A"),
|
||||||
salted_hash(master_secret, server_random, client_random, b"BB"),
|
salted_hash(master_secret, server_random, client_random, b"BB"),
|
||||||
|
|
|
@ -8,7 +8,6 @@ use ironrdp_core::{
|
||||||
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
|
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
|
||||||
ReadCursor, WriteCursor,
|
ReadCursor, WriteCursor,
|
||||||
};
|
};
|
||||||
use md5::Digest;
|
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::{FromPrimitive as _, ToPrimitive as _};
|
use num_traits::{FromPrimitive as _, ToPrimitive as _};
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ use crate::crypto::rc4::Rc4;
|
||||||
const RESPONSE_DATA_VERSION: u16 = 0x100;
|
const RESPONSE_DATA_VERSION: u16 = 0x100;
|
||||||
const RESPONSE_DATA_STATIC_FIELDS_SIZE: usize = 8;
|
const RESPONSE_DATA_STATIC_FIELDS_SIZE: usize = 8;
|
||||||
|
|
||||||
const CLIENT_HARDWARE_IDENTIFICATION_SIZE: usize = 20;
|
pub(crate) const CLIENT_HARDWARE_IDENTIFICATION_SIZE: usize = 20;
|
||||||
|
|
||||||
/// [2.2.2.5] Client Platform Challenge Response (CLIENT_PLATFORM_CHALLENGE_RESPONSE)
|
/// [2.2.2.5] Client Platform Challenge Response (CLIENT_PLATFORM_CHALLENGE_RESPONSE)
|
||||||
///
|
///
|
||||||
|
@ -40,7 +39,7 @@ impl ClientPlatformChallengeResponse {
|
||||||
|
|
||||||
pub fn from_server_platform_challenge(
|
pub fn from_server_platform_challenge(
|
||||||
platform_challenge: &ServerPlatformChallenge,
|
platform_challenge: &ServerPlatformChallenge,
|
||||||
hostname: &str,
|
hardware_data: [u32; 4],
|
||||||
encryption_data: &LicenseEncryptionData,
|
encryption_data: &LicenseEncryptionData,
|
||||||
) -> Result<Self, ServerLicenseError> {
|
) -> Result<Self, ServerLicenseError> {
|
||||||
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
||||||
|
@ -61,12 +60,10 @@ impl ClientPlatformChallengeResponse {
|
||||||
challenge_response_data.write_all(&decrypted_challenge)?;
|
challenge_response_data.write_all(&decrypted_challenge)?;
|
||||||
|
|
||||||
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
||||||
let mut md5 = md5::Md5::new();
|
|
||||||
md5.update(hostname.as_bytes());
|
|
||||||
let hardware_data = &md5.finalize();
|
|
||||||
|
|
||||||
hardware_id.write_u32::<LittleEndian>(PLATFORM_ID)?;
|
hardware_id.write_u32::<LittleEndian>(PLATFORM_ID)?;
|
||||||
hardware_id.write_all(hardware_data)?;
|
for data in hardware_data {
|
||||||
|
hardware_id.write_u32::<LittleEndian>(data)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
||||||
let encrypted_hwid = rc4.process(&hardware_id);
|
let encrypted_hwid = rc4.process(&hardware_id);
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use ironrdp_core::{decode, encode_vec};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::rdp::server_license::{
|
use crate::rdp::server_license::{
|
||||||
BasicSecurityHeader, BasicSecurityHeaderFlags, LicenseHeader, LicensePdu, PreambleFlags, PreambleType,
|
BasicSecurityHeader, BasicSecurityHeaderFlags, LicenseHeader, LicensePdu, PreambleFlags, PreambleType,
|
||||||
PreambleVersion, BASIC_SECURITY_HEADER_SIZE, PREAMBLE_SIZE,
|
PreambleVersion, BASIC_SECURITY_HEADER_SIZE, PREAMBLE_SIZE,
|
||||||
};
|
};
|
||||||
|
use ironrdp_core::{decode, encode_vec};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
const PLATFORM_CHALLENGE_RESPONSE_DATA_BUFFER: [u8; 18] = [
|
const PLATFORM_CHALLENGE_RESPONSE_DATA_BUFFER: [u8; 18] = [
|
||||||
0x00, 0x01, // version
|
0x00, 0x01, // version
|
||||||
|
@ -184,13 +183,10 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let hardware_data = vec![0u8; 16];
|
||||||
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
let mut hardware_id = Vec::with_capacity(CLIENT_HARDWARE_IDENTIFICATION_SIZE);
|
||||||
let mut md5 = md5::Md5::new();
|
|
||||||
md5.update(b"sample-hostname");
|
|
||||||
let hardware_data = &md5.finalize();
|
|
||||||
|
|
||||||
hardware_id.write_u32::<LittleEndian>(PLATFORM_ID).unwrap();
|
hardware_id.write_u32::<LittleEndian>(PLATFORM_ID).unwrap();
|
||||||
hardware_id.write_all(hardware_data).unwrap();
|
hardware_id.write_all(&hardware_data).unwrap();
|
||||||
|
|
||||||
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
let mut rc4 = Rc4::new(&encryption_data.license_key);
|
||||||
let encrypted_hwid = rc4.process(&hardware_id);
|
let encrypted_hwid = rc4.process(&hardware_id);
|
||||||
|
@ -226,12 +222,9 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
|
||||||
mac_data,
|
mac_data,
|
||||||
};
|
};
|
||||||
|
|
||||||
let challenge_response = ClientPlatformChallengeResponse::from_server_platform_challenge(
|
let challenge_response =
|
||||||
&server_challenge,
|
ClientPlatformChallengeResponse::from_server_platform_challenge(&server_challenge, [0u32; 4], &encryption_data)
|
||||||
"sample-hostname",
|
.unwrap();
|
||||||
&encryption_data,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(challenge_response, correct_challenge_response);
|
assert_eq!(challenge_response, correct_challenge_response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::crypto::rc4::Rc4;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::utils::CharacterSet;
|
use crate::utils::CharacterSet;
|
||||||
|
|
||||||
const NEW_LICENSE_INFO_STATIC_FIELDS_SIZE: usize = 20;
|
const LICENSE_INFO_STATIC_FIELDS_SIZE: usize = 20;
|
||||||
|
|
||||||
/// [2.2.2.6] Server Upgrade License (SERVER_UPGRADE_LICENSE)
|
/// [2.2.2.6] Server Upgrade License (SERVER_UPGRADE_LICENSE)
|
||||||
///
|
///
|
||||||
|
@ -28,8 +28,7 @@ pub struct ServerUpgradeLicense {
|
||||||
|
|
||||||
impl ServerUpgradeLicense {
|
impl ServerUpgradeLicense {
|
||||||
pub fn verify_server_license(&self, encryption_data: &LicenseEncryptionData) -> Result<(), ServerLicenseError> {
|
pub fn verify_server_license(&self, encryption_data: &LicenseEncryptionData) -> Result<(), ServerLicenseError> {
|
||||||
let mut rc4 = Rc4::new(encryption_data.license_key.as_slice());
|
let decrypted_license_info = self.decrypted_license_info(encryption_data);
|
||||||
let decrypted_license_info = rc4.process(self.encrypted_license_info.as_slice());
|
|
||||||
let mac_data =
|
let mac_data =
|
||||||
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_license_info.as_ref());
|
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_license_info.as_ref());
|
||||||
|
|
||||||
|
@ -39,6 +38,16 @@ impl ServerUpgradeLicense {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_license_info(&self, encryption_data: &LicenseEncryptionData) -> DecodeResult<LicenseInformation> {
|
||||||
|
let data = self.decrypted_license_info(encryption_data);
|
||||||
|
LicenseInformation::decode(&mut ReadCursor::new(&data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypted_license_info(&self, encryption_data: &LicenseEncryptionData) -> Vec<u8> {
|
||||||
|
let mut rc4 = Rc4::new(encryption_data.license_key.as_slice());
|
||||||
|
rc4.process(self.encrypted_license_info.as_slice())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerUpgradeLicense {
|
impl ServerUpgradeLicense {
|
||||||
|
@ -94,8 +103,8 @@ impl ServerUpgradeLicense {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct NewLicenseInformation {
|
pub struct LicenseInformation {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
pub company_name: String,
|
pub company_name: String,
|
||||||
|
@ -103,13 +112,13 @@ pub struct NewLicenseInformation {
|
||||||
pub license_info: Vec<u8>,
|
pub license_info: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewLicenseInformation {
|
impl LicenseInformation {
|
||||||
const NAME: &'static str = "NewLicenseInformation";
|
const NAME: &'static str = "LicenseInformation";
|
||||||
|
|
||||||
const FIXED_PART_SIZE: usize = NEW_LICENSE_INFO_STATIC_FIELDS_SIZE;
|
const FIXED_PART_SIZE: usize = LICENSE_INFO_STATIC_FIELDS_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for NewLicenseInformation {
|
impl Encode for LicenseInformation {
|
||||||
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
|
||||||
ensure_size!(in: dst, size: self.size());
|
ensure_size!(in: dst, size: self.size());
|
||||||
|
|
||||||
|
@ -151,7 +160,7 @@ impl Encode for NewLicenseInformation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Decode<'de> for NewLicenseInformation {
|
impl<'de> Decode<'de> for LicenseInformation {
|
||||||
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
|
||||||
ensure_fixed_part_size!(in: src);
|
ensure_fixed_part_size!(in: src);
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,7 @@ const NEW_LICENSE_INFORMATION_BUFFER: [u8; 2031] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref NEW_LICENSE_INFORMATION: NewLicenseInformation = NewLicenseInformation {
|
pub static ref NEW_LICENSE_INFORMATION: LicenseInformation = LicenseInformation {
|
||||||
version: 0x0006_0000,
|
version: 0x0006_0000,
|
||||||
scope: "microsoft.com".to_owned(),
|
scope: "microsoft.com".to_owned(),
|
||||||
company_name: "Microsoft Corporation".to_owned(),
|
company_name: "Microsoft Corporation".to_owned(),
|
||||||
|
|
|
@ -864,6 +864,8 @@ fn build_config(
|
||||||
pointer_software_rendering: false,
|
pointer_software_rendering: false,
|
||||||
performance_flags: PerformanceFlags::default(),
|
performance_flags: PerformanceFlags::default(),
|
||||||
desktop_scale_factor: 0,
|
desktop_scale_factor: 0,
|
||||||
|
hardware_id: None,
|
||||||
|
license_cache: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,9 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
use core::time::Duration;
|
|
||||||
use std::io::Write as _;
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use connector::Credentials;
|
use connector::Credentials;
|
||||||
|
use core::time::Duration;
|
||||||
use ironrdp::connector;
|
use ironrdp::connector;
|
||||||
use ironrdp::connector::ConnectionResult;
|
use ironrdp::connector::ConnectionResult;
|
||||||
use ironrdp::pdu::gcc::KeyboardType;
|
use ironrdp::pdu::gcc::KeyboardType;
|
||||||
|
@ -35,6 +31,9 @@ use ironrdp::session::image::DecodedImage;
|
||||||
use ironrdp::session::{ActiveStage, ActiveStageOutput};
|
use ironrdp::session::{ActiveStage, ActiveStageOutput};
|
||||||
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
|
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
|
||||||
use sspi::network_client::reqwest_network_client::ReqwestNetworkClient;
|
use sspi::network_client::reqwest_network_client::ReqwestNetworkClient;
|
||||||
|
use std::io::Write as _;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::path::PathBuf;
|
||||||
use tokio_rustls::rustls;
|
use tokio_rustls::rustls;
|
||||||
|
|
||||||
const HELP: &str = "\
|
const HELP: &str = "\
|
||||||
|
@ -212,6 +211,8 @@ fn build_config(username: String, password: String, domain: Option<String>) -> c
|
||||||
pointer_software_rendering: true,
|
pointer_software_rendering: true,
|
||||||
performance_flags: PerformanceFlags::default(),
|
performance_flags: PerformanceFlags::default(),
|
||||||
desktop_scale_factor: 0,
|
desktop_scale_factor: 0,
|
||||||
|
hardware_id: None,
|
||||||
|
license_cache: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,8 @@ pub mod ffi {
|
||||||
pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false),
|
pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false),
|
||||||
performance_flags: self.performance_flags.ok_or("performance flag is missing")?,
|
performance_flags: self.performance_flags.ok_or("performance flag is missing")?,
|
||||||
desktop_scale_factor: 0,
|
desktop_scale_factor: 0,
|
||||||
|
hardware_id: None,
|
||||||
|
license_cache: None,
|
||||||
};
|
};
|
||||||
tracing::debug!(config=?inner_config, "Built config");
|
tracing::debug!(config=?inner_config, "Built config");
|
||||||
Ok(Box::new(Config(inner_config)))
|
Ok(Box::new(Config(inner_config)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue