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:
Przemko Robakowski 2025-01-18 15:34:58 +01:00 committed by GitHub
parent a2378efb7a
commit dd221bf224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 421 additions and 97 deletions

1
Cargo.lock generated
View file

@ -2416,6 +2416,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"url",
"uuid",
"whoami",
"windows 0.58.0",
"winit",

View file

@ -41,6 +41,7 @@ ironrdp = { workspace = true, features = [
"rdpsnd",
"cliprdr",
"displaycontrol",
"connector"
] }
ironrdp-cliprdr-native.workspace = true
ironrdp-rdpsnd-native.workspace = true
@ -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"}
[target.'cfg(windows)'.dependencies]
windows = { workspace = true, features = ["Win32_Foundation"] }

View file

@ -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;
@ -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,

View file

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

View file

@ -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;
@ -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))]
@ -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]>,
/// Optional data for the x224 connection request.
///
/// Fallbacks to a sensible default depending on the provided credentials:
@ -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,

View file

@ -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]
@ -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,
}
}
}
@ -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 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()?;
let written = encode_send_data_request::<LicensePdu>(
send_data_indication_ctx.initiator_id,
send_data_indication_ctx.channel_id,
&new_license_request.into(),
output,
)?;
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");
(
Written::from_size(written)?,
LicenseExchangeState::PlatformChallenge { encryption_data },
)
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]);
} 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");
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}"))
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));
}
}
}
@ -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))?;
@ -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 {

View file

@ -11,11 +11,13 @@ use num_traits::{FromPrimitive, ToPrimitive};
use thiserror::Error;
use crate::rdp::headers::{BasicSecurityHeader, BasicSecurityHeaderFlags, BASIC_SECURITY_HEADER_SIZE};
pub use crate::rdp::server_license::client_license_info::ClientLicenseInfo;
use crate::PduError;
#[cfg(test)]
mod tests;
mod client_license_info;
mod client_new_license_request;
mod client_platform_challenge_response;
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::server_license_request::{cert, ProductInfo, Scope, ServerCertificate, ServerLicenseRequest};
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 PREMASTER_SECRET_SIZE: usize = 48;
@ -341,6 +343,7 @@ fn compute_mac_data(mac_salt_key: &[u8], data: &[u8]) -> Vec<u8> {
#[derive(Debug, PartialEq)]
pub enum LicensePdu {
ClientNewLicenseRequest(ClientNewLicenseRequest),
ClientLicenseInfo(ClientLicenseInfo),
ClientPlatformChallengeResponse(ClientPlatformChallengeResponse),
ServerLicenseRequest(ServerLicenseRequest),
ServerPlatformChallenge(ServerPlatformChallenge),
@ -375,6 +378,7 @@ impl Encode for LicensePdu {
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
match self {
Self::ClientNewLicenseRequest(ref pdu) => pdu.encode(dst),
Self::ClientLicenseInfo(ref pdu) => pdu.encode(dst),
Self::ClientPlatformChallengeResponse(ref pdu) => pdu.encode(dst),
Self::ServerLicenseRequest(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 {
match self {
Self::ClientNewLicenseRequest(pdu) => pdu.name(),
Self::ClientLicenseInfo(pdu) => pdu.name(),
Self::ClientPlatformChallengeResponse(pdu) => pdu.name(),
Self::ServerLicenseRequest(pdu) => pdu.name(),
Self::ServerPlatformChallenge(pdu) => pdu.name(),
@ -397,6 +402,7 @@ impl Encode for LicensePdu {
fn size(&self) -> usize {
match self {
Self::ClientNewLicenseRequest(pdu) => pdu.size(),
Self::ClientLicenseInfo(pdu) => pdu.size(),
Self::ClientPlatformChallengeResponse(pdu) => pdu.size(),
Self::ServerLicenseRequest(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 {
fn from(pdu: ClientPlatformChallengeResponse) -> Self {
Self::ClientPlatformChallengeResponse(pdu)

View 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,
})
}
}

View file

@ -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
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"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
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"BB"),

View file

@ -8,7 +8,6 @@ use ironrdp_core::{
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
ReadCursor, WriteCursor,
};
use md5::Digest;
use num_derive::{FromPrimitive, ToPrimitive};
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_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)
///
@ -40,7 +39,7 @@ impl ClientPlatformChallengeResponse {
pub fn from_server_platform_challenge(
platform_challenge: &ServerPlatformChallenge,
hostname: &str,
hardware_data: [u32; 4],
encryption_data: &LicenseEncryptionData,
) -> Result<Self, ServerLicenseError> {
let mut rc4 = Rc4::new(&encryption_data.license_key);
@ -61,12 +60,10 @@ impl ClientPlatformChallengeResponse {
challenge_response_data.write_all(&decrypted_challenge)?;
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_all(hardware_data)?;
for data in hardware_data {
hardware_id.write_u32::<LittleEndian>(data)?;
}
let mut rc4 = Rc4::new(&encryption_data.license_key);
let encrypted_hwid = rc4.process(&hardware_id);

View file

@ -1,11 +1,10 @@
use ironrdp_core::{decode, encode_vec};
use lazy_static::lazy_static;
use super::*;
use crate::rdp::server_license::{
BasicSecurityHeader, BasicSecurityHeaderFlags, LicenseHeader, LicensePdu, PreambleFlags, PreambleType,
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] = [
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 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_all(hardware_data).unwrap();
hardware_id.write_all(&hardware_data).unwrap();
let mut rc4 = Rc4::new(&encryption_data.license_key);
let encrypted_hwid = rc4.process(&hardware_id);
@ -226,12 +222,9 @@ fn challenge_response_creates_from_server_challenge_and_encryption_data_correctl
mac_data,
};
let challenge_response = ClientPlatformChallengeResponse::from_server_platform_challenge(
&server_challenge,
"sample-hostname",
&encryption_data,
)
.unwrap();
let challenge_response =
ClientPlatformChallengeResponse::from_server_platform_challenge(&server_challenge, [0u32; 4], &encryption_data)
.unwrap();
assert_eq!(challenge_response, correct_challenge_response);
}

View file

@ -14,7 +14,7 @@ use crate::crypto::rc4::Rc4;
use crate::utils;
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)
///
@ -28,8 +28,7 @@ pub struct ServerUpgradeLicense {
impl ServerUpgradeLicense {
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 = rc4.process(self.encrypted_license_info.as_slice());
let decrypted_license_info = self.decrypted_license_info(encryption_data);
let mac_data =
super::compute_mac_data(encryption_data.mac_salt_key.as_slice(), decrypted_license_info.as_ref());
@ -39,6 +38,16 @@ impl ServerUpgradeLicense {
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 {
@ -94,8 +103,8 @@ impl ServerUpgradeLicense {
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct NewLicenseInformation {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct LicenseInformation {
pub version: u32,
pub scope: String,
pub company_name: String,
@ -103,13 +112,13 @@ pub struct NewLicenseInformation {
pub license_info: Vec<u8>,
}
impl NewLicenseInformation {
const NAME: &'static str = "NewLicenseInformation";
impl LicenseInformation {
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<()> {
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> {
ensure_fixed_part_size!(in: src);

View file

@ -245,7 +245,7 @@ const NEW_LICENSE_INFORMATION_BUFFER: [u8; 2031] = [
];
lazy_static! {
pub static ref NEW_LICENSE_INFORMATION: NewLicenseInformation = NewLicenseInformation {
pub static ref NEW_LICENSE_INFORMATION: LicenseInformation = LicenseInformation {
version: 0x0006_0000,
scope: "microsoft.com".to_owned(),
company_name: "Microsoft Corporation".to_owned(),

View file

@ -864,6 +864,8 @@ fn build_config(
pointer_software_rendering: false,
performance_flags: PerformanceFlags::default(),
desktop_scale_factor: 0,
hardware_id: None,
license_cache: None,
}
}

View file

@ -20,13 +20,9 @@
#[macro_use]
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 connector::Credentials;
use core::time::Duration;
use ironrdp::connector;
use ironrdp::connector::ConnectionResult;
use ironrdp::pdu::gcc::KeyboardType;
@ -35,6 +31,9 @@ use ironrdp::session::image::DecodedImage;
use ironrdp::session::{ActiveStage, ActiveStageOutput};
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
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;
const HELP: &str = "\
@ -212,6 +211,8 @@ fn build_config(username: String, password: String, domain: Option<String>) -> c
pointer_software_rendering: true,
performance_flags: PerformanceFlags::default(),
desktop_scale_factor: 0,
hardware_id: None,
license_cache: None,
}
}

View file

@ -196,6 +196,8 @@ pub mod ffi {
pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false),
performance_flags: self.performance_flags.ok_or("performance flag is missing")?,
desktop_scale_factor: 0,
hardware_id: None,
license_cache: None,
};
tracing::debug!(config=?inner_config, "Built config");
Ok(Box::new(Config(inner_config)))