mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-12 12:04:59 +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-subscriber",
|
||||
"url",
|
||||
"uuid",
|
||||
"whoami",
|
||||
"windows 0.58.0",
|
||||
"winit",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)),
|
||||
),
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
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
|
||||
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"),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue