feat: smartcard pin, autologon, GetReaderIconCall and GetReaderIconReturn (#230)

Includes #231
This commit is contained in:
Isaiah Becker-Mayer 2023-10-20 20:35:19 +00:00 committed by GitHub
parent a29ff47f49
commit 47bc49b50e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 175 additions and 27 deletions

View file

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

View file

@ -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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");
}

View file

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

View file

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