You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IronRDP's current credential model assumes pre-loaded credentials (exact match at acceptor level). This works perfectly for credential-injection architectures like Devolutions Gateway, but makes it impossible for standalone RDP servers to implement server-side validation (PAM, LDAP, database-backed auth, etc.).
I'd like to propose a CredentialValidator trait that would enable both use cases cleanly.
Current Architecture
The Acceptor receives credentials from the client in ClientInfoPdu during SecureSettingsExchange and compares them against pre-loaded server credentials:
This has two limitations for servers that need to validate credentials on receipt:
No validation hook: The acceptor either matches exactly or rejects. There's no way to intercept the credentials and validate them against an external system (PAM, LDAP, etc.).
Credentials not exposed after handshake: AcceptorResult doesn't include the received credentials. They're extracted at line 555 and dropped after comparison, so even with fix(acceptor): skip credential check when server credentials are None #1150 (skip check when None), the server has no way to retrieve what the client sent for post-handshake validation.
Use Case: Standalone Linux RDP Server
I'm building lamco-rdp-server, a standalone Linux RDP server using IronRDP. For Linux servers, authentication against system accounts via PAM (pam_authenticate) is essential. The flow should be:
Client connects over TLS
Client sends credentials in ClientInfoPdu
Server extracts credentials and validates via PAM
Server accepts or rejects the connection
This is the same flow that xrdp uses for PAM authentication. It requires TLS-only mode (not CredSSP/Hybrid) because PAM validates credentials, whereas CredSSP/NTLM requires the server to already know the password.
Why This Doesn't Apply to CredSSP
I understand that the current model is by design for Devolutions' architecture: the Gateway pre-loads credentials from its vault and injects them via CredSSP. CredSSP fundamentally requires pre-known credentials for NTLM challenge-response. The proposed trait wouldn't change CredSSP behavior at all.
Proposed Solution: CredentialValidator Trait
/// Server-side credential validator for TLS-mode connections.////// Called during SecureSettingsExchange when the server receives/// client credentials via ClientInfoPdu. Not used for CredSSP/Hybrid/// connections (those use pre-loaded credentials for NTLM).pubtraitCredentialValidator:Send + Sync{/// Validate credentials received from the client./// Return Ok(true) to accept, Ok(false) to reject.fnvalidate(&self,credentials:&Credentials) -> Result<bool,ConnectorError>;}
The Acceptor would accept Option<Arc<dyn CredentialValidator>>:
Some(validator): Call validator.validate(&creds) during SecureSettingsExchange
Built-in implementations for backward compatibility:
/// Exact match against pre-loaded credentials (current behavior)pubstructExactMatchValidator(Credentials);implCredentialValidatorforExactMatchValidator{fnvalidate(&self,creds:&Credentials) -> Result<bool,ConnectorError>{Ok(&self.0 == creds)}}/// Accept all credentials (for no-auth servers)pubstructAcceptAllValidator;implCredentialValidatorforAcceptAllValidator{fnvalidate(&self, _:&Credentials) -> Result<bool,ConnectorError>{Ok(true)}}
This follows the pattern used by rustls (Arc<dyn ServerCertVerifier>) and tonic interceptors.
Interim Solution
While this trait is designed and reviewed, I'll submit a small companion PR to #1150 that adds the received credentials to AcceptorResult. This is a minimal, non-breaking change that enables post-handshake validation as a stopgap:
This stopgap would be superseded by the trait approach once it lands.
Impact Assessment
Devolutions Gateway: No impact. Gateway uses CredSSP with pre-loaded credentials, which bypasses SecureSettingsExchange credential handling entirely.
Devolutions Agent: No impact. Agent doesn't set credentials and doesn't use TLS-mode credential validation.
Existing API: Backward compatible. ExactMatchValidator provides the same behavior as current Some(creds). The set_credentials(Option<Credentials>) API could be preserved as a convenience method that wraps into ExactMatchValidator.
Summary
IronRDP's current credential model assumes pre-loaded credentials (exact match at acceptor level). This works perfectly for credential-injection architectures like Devolutions Gateway, but makes it impossible for standalone RDP servers to implement server-side validation (PAM, LDAP, database-backed auth, etc.).
I'd like to propose a
CredentialValidatortrait that would enable both use cases cleanly.Current Architecture
The
Acceptorreceives credentials from the client inClientInfoPduduringSecureSettingsExchangeand compares them against pre-loaded server credentials:This has two limitations for servers that need to validate credentials on receipt:
No validation hook: The acceptor either matches exactly or rejects. There's no way to intercept the credentials and validate them against an external system (PAM, LDAP, etc.).
Credentials not exposed after handshake:
AcceptorResultdoesn't include the received credentials. They're extracted at line 555 and dropped after comparison, so even with fix(acceptor): skip credential check when server credentials are None #1150 (skip check whenNone), the server has no way to retrieve what the client sent for post-handshake validation.Use Case: Standalone Linux RDP Server
I'm building lamco-rdp-server, a standalone Linux RDP server using IronRDP. For Linux servers, authentication against system accounts via PAM (
pam_authenticate) is essential. The flow should be:ClientInfoPduThis is the same flow that xrdp uses for PAM authentication. It requires TLS-only mode (not CredSSP/Hybrid) because PAM validates credentials, whereas CredSSP/NTLM requires the server to already know the password.
Why This Doesn't Apply to CredSSP
I understand that the current model is by design for Devolutions' architecture: the Gateway pre-loads credentials from its vault and injects them via CredSSP. CredSSP fundamentally requires pre-known credentials for NTLM challenge-response. The proposed trait wouldn't change CredSSP behavior at all.
Proposed Solution: CredentialValidator Trait
The
Acceptorwould acceptOption<Arc<dyn CredentialValidator>>:None: No credential check (equivalent to currentcreds: Nonewith fix(acceptor): skip credential check when server credentials are None #1150 merged)Some(validator): Callvalidator.validate(&creds)duringSecureSettingsExchangeBuilt-in implementations for backward compatibility:
This follows the pattern used by
rustls(Arc<dyn ServerCertVerifier>) andtonicinterceptors.Interim Solution
While this trait is designed and reviewed, I'll submit a small companion PR to #1150 that adds the received credentials to
AcceptorResult. This is a minimal, non-breaking change that enables post-handshake validation as a stopgap:This stopgap would be superseded by the trait approach once it lands.
Impact Assessment
SecureSettingsExchangecredential handling entirely.ExactMatchValidatorprovides the same behavior as currentSome(creds). Theset_credentials(Option<Credentials>)API could be preserved as a convenience method that wraps intoExactMatchValidator.Related
None(prerequisite, already submitted)