RDP certificate retrieval is different from retrieving a certificate from an HTTPS endpoint.
For HTTPS on port 443, the connection usually starts TLS immediately after
the TCP connection is opened. RDP on port 3389 does not work that way. A
client first sends an RDP negotiation request. If the server accepts a
TLS-capable security mode, the client can then start the TLS handshake and
inspect the server certificate.
TLSleuth now has an RdpTls transport for that flow.
Basic Usage
Use the default RDP port:
Get-TLSleuthCertificate -Hostname rdp.example.com -Port 3389 -Transport RdpTls
If -Transport is omitted, TLSleuth can infer RdpTls from port 3389:
Get-TLSleuthCertificate -Hostname rdp.example.com -Port 3389
Use an explicit transport when the service is on a custom port:
Get-TLSleuthCertificate -Hostname rdp.example.com -Port 3390 -Transport RdpTls
When connecting by IP address, use -TargetHost for SNI and certificate-name
validation:
Get-TLSleuthCertificate `
-Hostname 192.0.2.10 `
-Port 3389 `
-Transport RdpTls `
-TargetHost rdp.example.com
You can also test runtime-available TLS protocol versions against the RDP transport:
Test-TLSleuthProtocol -Hostname rdp.example.com -Port 3389 -Transport RdpTls |
Select Protocol, ConnectionSuccessful, NegotiatedProtocol, ErrorMessage
Why RDP Needs A Transport
TLSleuth’s transport model separates TCP connection setup, protocol-specific pre-TLS negotiation, TLS handshake, and result mapping.
For RDP, the pre-TLS step is binary protocol negotiation. TLSleuth sends an RDP Negotiation Request that asks for TLS-capable RDP security protocols:
SslHybridHybridEx
If the server accepts one of those modes, TLSleuth starts the normal .NET
SslStream handshake on the same network stream and extracts the certificate
and negotiated TLS details.
If the server rejects the negotiation before TLS starts, there is no TLS certificate available through that RDP path.
Internal Flow
The public command flow stays the same shape as other TLSleuth transports:
flowchart TD
Command["Get-TLSleuthCertificate"]
Context["New-TlsOperationContext"]
Connect["New-TlsConnectionContext"]
Dispatch["Invoke-TlsTransportNegotiation"]
Adapter["Invoke-RdpTlsTransportAdapter"]
RdpNegotiation["Invoke-RdpTlsNegotiation"]
Request["New-RdpNegotiationRequest"]
Send["Send-BinaryProtocolData"]
Read["Read-RdpNegotiationResponse"]
Handshake["Start-TlsHandshake"]
Details["Get-TlsHandshakeDetails"]
Certificate["Get-RemoteCertificate"]
Result["TLSleuth.CertificateResult"]
Cleanup["Close-NetworkResources"]
Command --> Context
Command --> Connect
Command --> Dispatch
Dispatch --> Adapter
Adapter --> RdpNegotiation
RdpNegotiation --> Request
RdpNegotiation --> Send
RdpNegotiation --> Read
Command --> Handshake
Command --> Details
Command --> Certificate
Command --> Result
Command --> Cleanup
The RDP-specific code is isolated behind the transport adapter. Once RDP negotiation succeeds, the TLS handshake and certificate extraction use the same helpers as the other transports.
What The RDP Transport Adds
The RDP implementation adds focused private helpers:
ConvertTo-RdpSecurityProtocolMaskConvertFrom-RdpSecurityProtocolMaskNew-RdpNegotiationRequestRead-RdpNegotiationResponseInvoke-RdpTlsNegotiationInvoke-RdpTlsTransportAdapter
The public commands continue to use the existing orchestration path:
New-TlsOperationContextNew-TlsConnectionContextInvoke-TlsTransportNegotiationStart-TlsHandshakeGet-TlsHandshakeDetailsGet-RemoteCertificate- result mapping helpers
That keeps RDP protocol work out of the public command bodies.
Common Failures
Connection Refused Or Timed Out
The TCP connection failed before RDP negotiation started.
Check the hostname, port, firewall rules, and whether RDP is enabled on the target server.
RDP Negotiation Rejected
The TCP connection succeeded, but the server returned an RDP negotiation failure before TLS started.
TLSleuth reports errors such as:
RDP server rejected TLS negotiation: HYBRID_REQUIRED_BY_SERVER (0x00000005).
The exact reason depends on the server response.
Server Selected Non-TLS RDP Security
TLSleuth requires a TLS-capable selected security mode. If the server selects standard RDP security instead, TLSleuth stops because there is no TLS certificate to retrieve.
TLS Handshake Failure
RDP negotiation succeeded, but the TLS handshake failed. That can happen because of TLS protocol policy, cipher support, runtime behavior, or certificate validation behavior.
Use Test-TLSleuthProtocol with -Transport RdpTls to see which TLS protocol
attempts succeed or fail.
NLA Caveat
Network Level Authentication is an authentication layer used by many RDP deployments. TLSleuth does not authenticate to the RDP server and does not log on. It negotiates far enough to start TLS, retrieve the certificate, and report TLS/session details.
If server policy rejects negotiation before TLS starts, TLSleuth reports the negotiation failure instead of returning a certificate result.
Testing And Validation
The RDP transport is covered by unit tests for:
- RDP negotiation request construction
- security protocol mask conversion
- negotiation response parsing
- negotiation failure parsing
- dispatcher routing to
RdpTls - public command transport behavior
TLSleuth also includes an opt-in integration test for a real RDP endpoint. It is skipped by default so normal CI does not depend on a reachable RDP server.
To enable it:
$env:TLSLEUTH_RDP_HOST = 'rdp.example.com'
$env:TLSLEUTH_RDP_PORT = '3389'
$env:TLSLEUTH_RDP_TARGETHOST = 'rdp.example.com'
Invoke-Build IntegrationTest
The full validation command used for normal development remains:
Invoke-Build Validate -File .\TLSleuth.build.ps1
Summary
RdpTls lets TLSleuth retrieve certificates from RDP services without treating
port 3389 like ordinary implicit TLS.
The flow is:
TCP connect -> RDP negotiation -> TLS handshake -> certificate extraction -> result object
That keeps the user experience consistent with the rest of TLSleuth while respecting the extra protocol step RDP requires before TLS can start.