mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-08-03 06:42:16 +00:00
feat: smartcard pin, autologon, GetReaderIconCall and GetReaderIconReturn (#230)
Includes #231
This commit is contained in:
parent
a29ff47f49
commit
47bc49b50e
10 changed files with 175 additions and 27 deletions
|
@ -53,14 +53,10 @@ where
|
|||
{
|
||||
let mut buf = WriteBuf::new();
|
||||
|
||||
debug!("CredSSP procedure");
|
||||
|
||||
while connector.is_credssp_step() {
|
||||
single_connect_step(framed, &mut connector, &mut buf).await?;
|
||||
}
|
||||
|
||||
debug!("Remaining of connection sequence");
|
||||
|
||||
let result = loop {
|
||||
single_connect_step(framed, &mut connector, &mut buf).await?;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::str::FromStr;
|
|||
use anyhow::Context as _;
|
||||
use clap::clap_derive::ValueEnum;
|
||||
use clap::{crate_name, Parser};
|
||||
use ironrdp::connector::Credentials;
|
||||
use ironrdp::pdu::rdp::capability_sets::MajorPlatformType;
|
||||
use ironrdp::{connector, pdu};
|
||||
use tap::prelude::*;
|
||||
|
@ -220,6 +221,11 @@ struct Args {
|
|||
/// starting from V8 to V10_7
|
||||
#[clap(long, value_parser = parse_hex, default_value_t = 0)]
|
||||
capabilities: u32,
|
||||
|
||||
/// Automatically logon to the server by passing the INFO_AUTOLOGON flag. This flag is
|
||||
/// ignored if CredSSP is used (SecurityProtocol::Hybrid | SecurityProtocol::HybridEx).
|
||||
#[clap(long)]
|
||||
autologon: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -276,8 +282,7 @@ impl Config {
|
|||
};
|
||||
|
||||
let connector = connector::Config {
|
||||
username,
|
||||
password,
|
||||
credentials: Credentials::UsernamePassword { username, password },
|
||||
domain: args.domain,
|
||||
security_protocol: SecurityProtocol::parse(args.security_protocol),
|
||||
keyboard_type: KeyboardType::parse(args.keyboard_type),
|
||||
|
@ -309,6 +314,7 @@ impl Config {
|
|||
_ => MajorPlatformType::UNSPECIFIED,
|
||||
},
|
||||
no_server_pointer: args.no_server_pointer,
|
||||
autologon: args.autologon,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
|
@ -304,8 +304,9 @@ impl Sequence for ClientConnector {
|
|||
//== Connection Initiation ==//
|
||||
// Exchange supported security protocols and a few other connection flags.
|
||||
ClientConnectorState::ConnectionInitiationSendRequest => {
|
||||
debug!("Connection Initiation");
|
||||
let connection_request = nego::ConnectionRequest {
|
||||
nego_data: Some(nego::NegoRequestData::cookie(self.config.username.clone())),
|
||||
nego_data: Some(nego::NegoRequestData::cookie(self.config.credentials.username().into())),
|
||||
flags: nego::RequestFlags::empty(),
|
||||
protocol: self.config.security_protocol,
|
||||
};
|
||||
|
@ -356,6 +357,7 @@ impl Sequence for ClientConnector {
|
|||
{
|
||||
ClientConnectorState::CredsspInitial { selected_protocol }
|
||||
} else {
|
||||
debug!("Skipped CredSSP");
|
||||
ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }
|
||||
};
|
||||
|
||||
|
@ -364,9 +366,16 @@ impl Sequence for ClientConnector {
|
|||
|
||||
//== CredSSP ==//
|
||||
ClientConnectorState::CredsspInitial { selected_protocol } => {
|
||||
debug!("CredSSP");
|
||||
if let crate::Credentials::SmartCard { .. } = self.config.credentials {
|
||||
return Err(general_err!(
|
||||
"CredSSP with smart card credentials is not currently supported"
|
||||
));
|
||||
}
|
||||
|
||||
let credentials = sspi::AuthIdentity {
|
||||
username: self.config.username.clone(),
|
||||
password: self.config.password.clone().into(),
|
||||
username: self.config.credentials.username().into(),
|
||||
password: self.config.credentials.secret().to_owned().into(),
|
||||
domain: self.config.domain.clone(),
|
||||
};
|
||||
|
||||
|
@ -484,6 +493,7 @@ impl Sequence for ClientConnector {
|
|||
//== Basic Settings Exchange ==//
|
||||
// Exchange basic settings including Core Data, Security Data and Network Data.
|
||||
ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol } => {
|
||||
debug!("Basic Settings Exchange");
|
||||
let client_gcc_blocks =
|
||||
create_gcc_blocks(&self.config, selected_protocol, self.static_channels.values());
|
||||
|
||||
|
@ -559,6 +569,7 @@ impl Sequence for ClientConnector {
|
|||
io_channel_id,
|
||||
mut channel_connection,
|
||||
} => {
|
||||
debug!("Channel Connection");
|
||||
let written = channel_connection.step(input, output)?;
|
||||
|
||||
let next_state = if let ChannelConnectionState::AllJoined { user_channel_id } = channel_connection.state
|
||||
|
@ -589,6 +600,7 @@ impl Sequence for ClientConnector {
|
|||
io_channel_id,
|
||||
user_channel_id,
|
||||
} => {
|
||||
debug!("RDP Security Commencement");
|
||||
if selected_protocol == nego::SecurityProtocol::RDP {
|
||||
return Err(general_err!("standard RDP Security (RC4 encryption) is not supported"));
|
||||
}
|
||||
|
@ -608,6 +620,7 @@ impl Sequence for ClientConnector {
|
|||
io_channel_id,
|
||||
user_channel_id,
|
||||
} => {
|
||||
debug!("Secure Settings Exchange");
|
||||
let routing_addr = self
|
||||
.server_addr
|
||||
.as_ref()
|
||||
|
@ -640,7 +653,7 @@ impl Sequence for ClientConnector {
|
|||
user_channel_id,
|
||||
license_exchange: LicenseExchangeSequence::new(
|
||||
io_channel_id,
|
||||
self.config.username.clone(),
|
||||
self.config.credentials.username().into(),
|
||||
self.config.domain.clone(),
|
||||
),
|
||||
},
|
||||
|
@ -654,6 +667,7 @@ impl Sequence for ClientConnector {
|
|||
user_channel_id,
|
||||
mut license_exchange,
|
||||
} => {
|
||||
debug!("Licensing Exchange");
|
||||
let written = license_exchange.step(input, output)?;
|
||||
|
||||
let next_state = if license_exchange.state.is_terminal() {
|
||||
|
@ -691,6 +705,7 @@ impl Sequence for ClientConnector {
|
|||
io_channel_id,
|
||||
user_channel_id,
|
||||
} => {
|
||||
debug!("Capabilities Exchange");
|
||||
let send_data_indication_ctx = legacy::decode_send_data_indication(input)?;
|
||||
let share_control_ctx = legacy::decode_share_control(send_data_indication_ctx)?;
|
||||
|
||||
|
@ -761,6 +776,7 @@ impl Sequence for ClientConnector {
|
|||
desktop_size,
|
||||
mut connection_finalization,
|
||||
} => {
|
||||
debug!("Connection Finalization");
|
||||
let written = connection_finalization.step(input, output)?;
|
||||
|
||||
let next_state = if connection_finalization.state.is_terminal() {
|
||||
|
@ -898,19 +914,30 @@ fn create_client_info_pdu(config: &Config, routing_addr: &SocketAddr) -> rdp::Cl
|
|||
flags: BasicSecurityHeaderFlags::INFO_PKT,
|
||||
};
|
||||
|
||||
let client_info = ClientInfo {
|
||||
credentials: Credentials {
|
||||
username: config.username.clone(),
|
||||
password: config.password.clone(),
|
||||
domain: config.domain.clone(),
|
||||
},
|
||||
code_page: 0, // ignored if the keyboardLayout field of the Client Core Data is set to zero
|
||||
flags: ClientInfoFlags::UNICODE
|
||||
// Default flags for all sessions
|
||||
let mut flags = ClientInfoFlags::UNICODE
|
||||
| ClientInfoFlags::DISABLE_CTRL_ALT_DEL
|
||||
| ClientInfoFlags::LOGON_NOTIFY
|
||||
| ClientInfoFlags::LOGON_ERRORS
|
||||
| ClientInfoFlags::NO_AUDIO_PLAYBACK
|
||||
| ClientInfoFlags::VIDEO_DISABLE,
|
||||
| ClientInfoFlags::VIDEO_DISABLE;
|
||||
|
||||
if config.autologon {
|
||||
flags |= ClientInfoFlags::AUTOLOGON;
|
||||
}
|
||||
|
||||
if let crate::Credentials::SmartCard { .. } = &config.credentials {
|
||||
flags |= ClientInfoFlags::PASSWORD_IS_SC_PIN;
|
||||
}
|
||||
|
||||
let client_info = ClientInfo {
|
||||
credentials: Credentials {
|
||||
username: config.credentials.username().into(),
|
||||
password: config.credentials.secret().into(),
|
||||
domain: config.domain.clone(),
|
||||
},
|
||||
code_page: 0, // ignored if the keyboardLayout field of the Client Core Data is set to zero
|
||||
flags,
|
||||
compression_type: CompressionType::K8, // ignored if ClientInfoFlags::COMPRESSION is not set
|
||||
alternate_shell: String::new(),
|
||||
work_dir: String::new(),
|
||||
|
|
|
@ -52,13 +52,34 @@ pub struct BitmapConfig {
|
|||
pub color_depth: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Credentials {
|
||||
UsernamePassword { username: String, password: String },
|
||||
SmartCard { pin: String },
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
fn username(&self) -> &str {
|
||||
match self {
|
||||
Self::UsernamePassword { username, .. } => username,
|
||||
Self::SmartCard { .. } => "", // Username is ultimately provided by the smart card certificate.
|
||||
}
|
||||
}
|
||||
|
||||
fn secret(&self) -> &str {
|
||||
match self {
|
||||
Self::UsernamePassword { password, .. } => password,
|
||||
Self::SmartCard { pin, .. } => pin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub struct Config {
|
||||
pub desktop_size: DesktopSize,
|
||||
pub security_protocol: nego::SecurityProtocol,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub credentials: Credentials,
|
||||
pub domain: Option<String>,
|
||||
/// The build number of the client.
|
||||
pub client_build: u32,
|
||||
|
@ -74,6 +95,8 @@ pub struct Config {
|
|||
pub client_dir: String,
|
||||
pub platform: capability_sets::MajorPlatformType,
|
||||
pub no_server_pointer: bool,
|
||||
/// If true, the INFO_AUTOLOGON flag is set in the [`ironrdp_pdu::rdp::ClientInfoPdu`].
|
||||
pub autologon: bool,
|
||||
}
|
||||
|
||||
ironrdp_pdu::assert_impl!(Config: Send, Sync);
|
||||
|
|
|
@ -31,6 +31,9 @@ const RECONNECT_COOKIE_LENGTH_SIZE: usize = 2;
|
|||
const BIAS_SIZE: usize = 4;
|
||||
const SYSTEM_TIME_SIZE: usize = 16;
|
||||
|
||||
/// [2.2.1.11.1.1 Info Packet (TS_INFO_PACKET)]
|
||||
///
|
||||
/// [2.2.1.11.1.1 Info Packet (TS_INFO_PACKET)]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/732394f5-e2b5-4ac5-8a0a-35345386b0d1
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientInfo {
|
||||
pub credentials: Credentials,
|
||||
|
@ -521,26 +524,47 @@ pub enum AddressFamily {
|
|||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ClientInfoFlags: u32 {
|
||||
/// INFO_MOUSE
|
||||
const MOUSE = 0x0000_0001;
|
||||
/// INFO_DISABLECTRLALTDEL
|
||||
const DISABLE_CTRL_ALT_DEL = 0x0000_0002;
|
||||
/// INFO_AUTOLOGON
|
||||
const AUTOLOGON = 0x0000_0008;
|
||||
/// INFO_UNICODE
|
||||
const UNICODE = 0x0000_0010;
|
||||
/// INFO_MAXIMIZESHELL
|
||||
const MAXIMIZE_SHELL = 0x0000_0020;
|
||||
/// INFO_LOGONNOTIFY
|
||||
const LOGON_NOTIFY = 0x0000_0040;
|
||||
/// INFO_COMPRESSION
|
||||
const COMPRESSION = 0x0000_0080;
|
||||
/// INFO_ENABLEWINDOWSKEY
|
||||
const ENABLE_WINDOWS_KEY = 0x0000_0100;
|
||||
/// INFO_REMOTECONSOLEAUDIO
|
||||
const REMOTE_CONSOLE_AUDIO = 0x0000_2000;
|
||||
/// INFO_FORCE_ENCRYPTED_CS_PDU
|
||||
const FORCE_ENCRYPTED_CS_PDU = 0x0000_4000;
|
||||
/// INFO_RAIL
|
||||
const RAIL = 0x0000_8000;
|
||||
/// INFO_LOGONERRORS
|
||||
const LOGON_ERRORS = 0x0001_0000;
|
||||
/// INFO_MOUSE_HAS_WHEEL
|
||||
const MOUSE_HAS_WHEEL = 0x0002_0000;
|
||||
/// INFO_PASSWORD_IS_SC_PIN
|
||||
const PASSWORD_IS_SC_PIN = 0x0004_0000;
|
||||
/// INFO_NOAUDIOPLAYBACK
|
||||
const NO_AUDIO_PLAYBACK = 0x0008_0000;
|
||||
/// INFO_USING_SAVED_CREDS
|
||||
const USING_SAVED_CREDS = 0x0010_0000;
|
||||
/// INFO_AUDIOCAPTURE
|
||||
const AUDIO_CAPTURE = 0x0020_0000;
|
||||
/// INFO_VIDEO_DISABLE
|
||||
const VIDEO_DISABLE = 0x0040_0000;
|
||||
/// INFO_RESERVED1
|
||||
const RESERVED1 = 0x0080_0000;
|
||||
/// INFO_RESERVED1
|
||||
const RESERVED2 = 0x0100_0000;
|
||||
/// INFO_HIDEF_RAIL_SUPPORTED
|
||||
const HIDEF_RAIL_SUPPORTED = 0x0200_0000;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,7 +272,7 @@ fn from_buffer_correctly_parses_server_license_request() {
|
|||
|
||||
#[test]
|
||||
fn from_buffer_correctly_parses_server_license_request_no_certificate() {
|
||||
let server_certificate_header_buffer = vec![
|
||||
let server_certificate_header_buffer = [
|
||||
0x03, 0x00, // blob type
|
||||
0x00, 0x00, // blob len
|
||||
];
|
||||
|
|
|
@ -34,6 +34,7 @@ pub enum ScardCall {
|
|||
GetDeviceTypeIdCall(GetDeviceTypeIdCall),
|
||||
ReadCacheCall(ReadCacheCall),
|
||||
WriteCacheCall(WriteCacheCall),
|
||||
GetReaderIconCall(GetReaderIconCall),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,7 @@ impl ScardCall {
|
|||
ScardIoCtlCode::GetDeviceTypeId => Ok(ScardCall::GetDeviceTypeIdCall(GetDeviceTypeIdCall::decode(src)?)),
|
||||
ScardIoCtlCode::ReadCacheW => Ok(ScardCall::ReadCacheCall(ReadCacheCall::decode(src)?)),
|
||||
ScardIoCtlCode::WriteCacheW => Ok(ScardCall::WriteCacheCall(WriteCacheCall::decode(src)?)),
|
||||
ScardIoCtlCode::GetReaderIcon => Ok(ScardCall::GetReaderIconCall(GetReaderIconCall::decode(src)?)),
|
||||
_ => {
|
||||
warn!(?io_ctl_code, "Unsupported ScardIoCtlCode");
|
||||
// TODO: maybe this should be an error
|
||||
|
@ -1740,3 +1742,72 @@ impl ndr::Decode for WriteCacheCommon {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [2.2.2.31 GetReaderIcon_Call]
|
||||
///
|
||||
/// [2.2.2.31 GetReaderIcon_Call]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/e6a68d90-697f-4b98-8ad6-f74853d27ccb
|
||||
#[derive(Debug)]
|
||||
pub struct GetReaderIconCall {
|
||||
pub context: ScardContext,
|
||||
pub reader_name: String,
|
||||
}
|
||||
|
||||
impl GetReaderIconCall {
|
||||
pub fn decode(src: &mut ReadCursor<'_>) -> PduResult<Self> {
|
||||
Ok(rpce::Pdu::<Self>::decode(src)?.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl rpce::HeaderlessDecode for GetReaderIconCall {
|
||||
fn decode(src: &mut ReadCursor<'_>) -> PduResult<Self> {
|
||||
let mut index = 0;
|
||||
let mut context = ScardContext::decode_ptr(src, &mut index)?;
|
||||
|
||||
let _reader_ptr = ndr::decode_ptr(src, &mut index)?;
|
||||
|
||||
context.decode_value(src)?;
|
||||
let reader_name = ndr::read_string_from_cursor(src)?;
|
||||
Ok(Self { context, reader_name })
|
||||
}
|
||||
}
|
||||
|
||||
/// [2.2.3.14 GetReaderIcon_Return]
|
||||
///
|
||||
/// [2.2.3.14 GetReaderIcon_Return]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/f011f3d9-e2a4-4c43-a336-4c89ecaa8360
|
||||
#[derive(Debug)]
|
||||
pub struct GetReaderIconReturn {
|
||||
pub return_code: ReturnCode,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl GetReaderIconReturn {
|
||||
const NAME: &'static str = "GetReaderIcon_Return";
|
||||
|
||||
pub fn new(return_code: ReturnCode, data: Vec<u8>) -> rpce::Pdu<Self> {
|
||||
rpce::Pdu(Self { return_code, data })
|
||||
}
|
||||
}
|
||||
|
||||
impl rpce::HeaderlessEncode for GetReaderIconReturn {
|
||||
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
|
||||
ensure_size!(in: dst, size: self.size());
|
||||
dst.write_u32(self.return_code.into());
|
||||
let data_len: u32 = cast_length!("GetReaderIconReturn", "data_len", self.data.len())?;
|
||||
let mut index = 0;
|
||||
ndr::encode_ptr(Some(data_len), &mut index, dst)?;
|
||||
dst.write_u32(data_len);
|
||||
dst.write_slice(&self.data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
Self::NAME
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
size_of::<u32>() // dst.write_u32(self.return_code.into());
|
||||
+ ndr::ptr_size(true) // ndr::encode_ptr(Some(data_len), &mut index, dst)?;
|
||||
+ size_of::<u32>() // dst.write_u32(data_len);
|
||||
+ self.data.len() // dst.write_slice(&self.data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ fn client_temp_dir_encode_decode_ms_1() {
|
|||
|
||||
if let ClipboardPdu::TemporaryDirectory(client_temp_dir) = &decoded_pdu {
|
||||
let path = client_temp_dir.temporary_directory_path().unwrap();
|
||||
expect![[r#"C:\DOCUME~1\ELTONS~1.NTD\LOCALS~1\Temp\cdepotslhrdp_1\_TSABD.tmp"#]].assert_eq(&path);
|
||||
expect![[r"C:\DOCUME~1\ELTONS~1.NTD\LOCALS~1\Temp\cdepotslhrdp_1\_TSABD.tmp"]].assert_eq(&path);
|
||||
} else {
|
||||
panic!("Expected ClientTemporaryDirectory");
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use futures_util::io::{ReadHalf, WriteHalf};
|
|||
use futures_util::{select, AsyncReadExt as _, AsyncWriteExt as _, FutureExt as _, StreamExt as _};
|
||||
use gloo_net::websocket;
|
||||
use gloo_net::websocket::futures::WebSocket;
|
||||
use ironrdp::connector::{self, ClientConnector};
|
||||
use ironrdp::connector::{self, ClientConnector, Credentials};
|
||||
use ironrdp::graphics::image_processing::PixelFormat;
|
||||
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
|
||||
use ironrdp::pdu::write_buf::WriteBuf;
|
||||
|
@ -462,8 +462,7 @@ fn build_config(
|
|||
desktop_size: DesktopSize,
|
||||
) -> connector::Config {
|
||||
connector::Config {
|
||||
username,
|
||||
password,
|
||||
credentials: Credentials::UsernamePassword { username, password },
|
||||
domain,
|
||||
security_protocol: ironrdp::pdu::nego::SecurityProtocol::HYBRID,
|
||||
keyboard_type: ironrdp::pdu::gcc::KeyboardType::IbmEnhanced,
|
||||
|
@ -492,6 +491,7 @@ fn build_config(
|
|||
client_dir: "C:\\Windows\\System32\\mstscax.dll".to_owned(),
|
||||
platform: ironrdp::pdu::rdp::capability_sets::MajorPlatformType::UNSPECIFIED,
|
||||
no_server_pointer: false,
|
||||
autologon: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ use std::path::PathBuf;
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use connector::Credentials;
|
||||
use ironrdp::connector;
|
||||
use ironrdp::connector::sspi::network_client::reqwest_network_client::RequestClientFactory;
|
||||
use ironrdp::connector::ConnectionResult;
|
||||
|
@ -174,8 +175,7 @@ fn run(
|
|||
|
||||
fn build_config(username: String, password: String, domain: Option<String>) -> connector::Config {
|
||||
connector::Config {
|
||||
username,
|
||||
password,
|
||||
credentials: Credentials::UsernamePassword { username, password },
|
||||
domain,
|
||||
security_protocol: SecurityProtocol::HYBRID,
|
||||
keyboard_type: KeyboardType::IbmEnhanced,
|
||||
|
@ -214,6 +214,7 @@ fn build_config(username: String, password: String, domain: Option<String>) -> c
|
|||
|
||||
// Disable custom pointers (there is no user interaction anyway)
|
||||
no_server_pointer: true,
|
||||
autologon: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue