From ac291423de6835df855e1b40c8da6b45ac0905d9 Mon Sep 17 00:00:00 2001 From: Gabriel Bauman <967743+gabrielbauman@users.noreply.github.com> Date: Fri, 8 Aug 2025 01:52:28 -0700 Subject: [PATCH] refactor(web): add NegotiationFailure to IronErrorKind (#905) Adds a general NegotiationFailure to IronErrorKind so that web embedders can handle failures that occur during protocol negotiation, before authentication. Changes: - RDP errors during the negotiation phase become IronErrorKind.NegotiationError - User-friendly RDP negotiation error messages to ironrdp-connector - Update TypeScript definitions --- crates/iron-remote-desktop/src/error.rs | 2 + crates/ironrdp-connector/src/connection.rs | 9 ++-- crates/ironrdp-connector/src/lib.rs | 52 +++++++++++++++++++ crates/ironrdp-web/Cargo.toml | 1 + crates/ironrdp-web/src/error.rs | 1 + .../src/interfaces/session-event.ts | 1 + 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/crates/iron-remote-desktop/src/error.rs b/crates/iron-remote-desktop/src/error.rs index 1251944f..42093844 100644 --- a/crates/iron-remote-desktop/src/error.rs +++ b/crates/iron-remote-desktop/src/error.rs @@ -21,4 +21,6 @@ pub enum IronErrorKind { RDCleanPath, /// Couldn’t connect to proxy ProxyConnect, + /// Protocol negotiation failed + NegotiationFailure, } diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index a79d2119..fb4ffcc1 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -13,8 +13,8 @@ use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionStat use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache}; use crate::{ - encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, Sequence, State, - Written, + encode_x224_packet, Config, ConnectorError, ConnectorErrorExt as _, ConnectorErrorKind, ConnectorResult, + DesktopSize, NegotiationFailure, Sequence, State, Written, }; #[derive(Debug)] @@ -274,7 +274,10 @@ impl Sequence for ClientConnector { nego::ConnectionConfirm::Response { flags, protocol } => (flags, protocol), nego::ConnectionConfirm::Failure { code } => { error!(?code, "Received connection failure code"); - return Err(reason_err!("Initiation", "{code}")); + return Err(ConnectorError::new( + "negotiation failure", + ConnectorErrorKind::Negotiation(NegotiationFailure::from(code)), + )); } }; diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index b443f878..a6b862bc 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -36,6 +36,55 @@ pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState}; pub use self::server_name::ServerName; pub use crate::license_exchange::LicenseCache; +/// Provides user-friendly error messages for RDP negotiation failures +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NegotiationFailure(ironrdp_pdu::nego::FailureCode); + +impl NegotiationFailure { + pub fn code(self) -> ironrdp_pdu::nego::FailureCode { + self.0 + } +} + +impl core::error::Error for NegotiationFailure {} + +impl From for NegotiationFailure { + fn from(code: ironrdp_pdu::nego::FailureCode) -> Self { + Self(code) + } +} + +impl fmt::Display for NegotiationFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ironrdp_pdu::nego::FailureCode; + + match self.0 { + FailureCode::SSL_REQUIRED_BY_SERVER => { + write!(f, "server requires Enhanced RDP Security with TLS or CredSSP") + } + FailureCode::SSL_NOT_ALLOWED_BY_SERVER => { + write!(f, "server only supports Standard RDP Security") + } + FailureCode::SSL_CERT_NOT_ON_SERVER => { + write!(f, "server lacks valid authentication certificate") + } + FailureCode::INCONSISTENT_FLAGS => { + write!(f, "inconsistent security protocol flags") + } + FailureCode::HYBRID_REQUIRED_BY_SERVER => { + write!(f, "server requires Enhanced RDP Security with CredSSP") + } + FailureCode::SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER => { + write!( + f, + "server requires Enhanced RDP Security with TLS and client certificate" + ) + } + _ => write!(f, "unknown negotiation failure (code: 0x{:08x})", u32::from(self.0)), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct DesktopSize { @@ -276,6 +325,7 @@ pub enum ConnectorErrorKind { AccessDenied, General, Custom, + Negotiation(NegotiationFailure), } impl fmt::Display for ConnectorErrorKind { @@ -288,6 +338,7 @@ impl fmt::Display for ConnectorErrorKind { ConnectorErrorKind::AccessDenied => write!(f, "access denied"), ConnectorErrorKind::General => write!(f, "general error"), ConnectorErrorKind::Custom => write!(f, "custom error"), + ConnectorErrorKind::Negotiation(failure) => write!(f, "negotiation failure: {failure}"), } } } @@ -302,6 +353,7 @@ impl core::error::Error for ConnectorErrorKind { ConnectorErrorKind::AccessDenied => None, ConnectorErrorKind::Custom => None, ConnectorErrorKind::General => None, + ConnectorErrorKind::Negotiation(failure) => Some(failure), } } } diff --git a/crates/ironrdp-web/Cargo.toml b/crates/ironrdp-web/Cargo.toml index 04a832e1..7f1a44e8 100644 --- a/crates/ironrdp-web/Cargo.toml +++ b/crates/ironrdp-web/Cargo.toml @@ -34,6 +34,7 @@ ironrdp = { path = "../ironrdp", features = [ "cliprdr", "svc", "displaycontrol", + "pdu", ] } ironrdp-core.path = "../ironrdp-core" ironrdp-cliprdr-format.path = "../ironrdp-cliprdr-format" diff --git a/crates/ironrdp-web/src/error.rs b/crates/ironrdp-web/src/error.rs index 927354b5..4e159ef9 100644 --- a/crates/ironrdp-web/src/error.rs +++ b/crates/ironrdp-web/src/error.rs @@ -37,6 +37,7 @@ impl From for IronError { .. }) => IronErrorKind::LogonFailure, ConnectorErrorKind::AccessDenied => IronErrorKind::AccessDenied, + ConnectorErrorKind::Negotiation(_) => IronErrorKind::NegotiationFailure, _ => IronErrorKind::General, }; diff --git a/web-client/iron-remote-desktop/src/interfaces/session-event.ts b/web-client/iron-remote-desktop/src/interfaces/session-event.ts index 1b21e22e..e79aebef 100644 --- a/web-client/iron-remote-desktop/src/interfaces/session-event.ts +++ b/web-client/iron-remote-desktop/src/interfaces/session-event.ts @@ -7,6 +7,7 @@ export enum IronErrorKind { AccessDenied = 3, RDCleanPath = 4, ProxyConnect = 5, + NegotiationFailure = 6, } export interface IronError {