feat(core): RDP client on IronRDP

clients::rdp connects over any caller-dialled TCP stream (direct, proxy
or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM),
optionally falling back to TLS-only logon for servers without NLA such
as xrdp (credentials are passed so they log straight in). The server
certificate is pinned through a caller check on its SHA256 fingerprint
before any credentials are sent. The desktop decodes into the VNC
client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse
operations), Display Control resizes and the deactivation-reactivation
sequence run on the session thread.

IronRDP's releases need three vendored patches (vendor/README.md):
picky and sspi with their RustCrypto release-candidate pins relaxed so
they share russh's stable versions (their own suites, 402 tests, pass
against them), and ironrdp-session with two unreleased upstream bitmap
fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops
render sheared.

Verified live against xrdp (scottyhardy/docker-remote-desktop): logon,
full desktop rendering, keyboard and mouse input.
This commit is contained in:
Andy Dixon
2026-10-12 10:50:24 +01:00
parent 6a4d7381d9
commit 50c0104d57
227 changed files with 66687 additions and 20 deletions
+101
View File
@@ -0,0 +1,101 @@
//! Hash algorithms supported by picky
use digest::Digest;
use picky_asn1_x509::ShaVariant;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
/// unsupported algorithm
#[derive(Debug)]
pub struct UnsupportedHashAlgorithmError {
pub algorithm: String,
}
impl fmt::Display for UnsupportedHashAlgorithmError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unsupported algorithm: {}", self.algorithm)
}
}
impl Error for UnsupportedHashAlgorithmError {}
/// Supported hash algorithms
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HashAlgorithm {
MD5,
SHA1,
SHA2_224,
SHA2_256,
SHA2_384,
SHA2_512,
SHA3_384,
SHA3_512,
}
impl TryFrom<HashAlgorithm> for ShaVariant {
type Error = UnsupportedHashAlgorithmError;
fn try_from(v: HashAlgorithm) -> Result<ShaVariant, UnsupportedHashAlgorithmError> {
match v {
HashAlgorithm::MD5 => Ok(ShaVariant::MD5),
HashAlgorithm::SHA1 => Ok(ShaVariant::SHA1),
HashAlgorithm::SHA2_256 => Ok(ShaVariant::SHA2_256),
HashAlgorithm::SHA2_384 => Ok(ShaVariant::SHA2_384),
HashAlgorithm::SHA2_512 => Ok(ShaVariant::SHA2_512),
HashAlgorithm::SHA3_384 => Ok(ShaVariant::SHA3_384),
HashAlgorithm::SHA3_512 => Ok(ShaVariant::SHA3_512),
_ => Err(UnsupportedHashAlgorithmError {
algorithm: format!("{v:?}"),
}),
}
}
}
impl TryFrom<ShaVariant> for HashAlgorithm {
type Error = UnsupportedHashAlgorithmError;
fn try_from(v: ShaVariant) -> Result<HashAlgorithm, UnsupportedHashAlgorithmError> {
match v {
ShaVariant::MD5 => Ok(HashAlgorithm::MD5),
ShaVariant::SHA1 => Ok(HashAlgorithm::SHA1),
ShaVariant::SHA2_256 => Ok(HashAlgorithm::SHA2_256),
ShaVariant::SHA2_384 => Ok(HashAlgorithm::SHA2_384),
ShaVariant::SHA2_512 => Ok(HashAlgorithm::SHA2_512),
ShaVariant::SHA3_384 => Ok(HashAlgorithm::SHA3_384),
ShaVariant::SHA3_512 => Ok(HashAlgorithm::SHA3_512),
_ => Err(UnsupportedHashAlgorithmError {
algorithm: format!("{v:?}"),
}),
}
}
}
impl HashAlgorithm {
pub fn digest(self, msg: &[u8]) -> Vec<u8> {
match self {
Self::MD5 => md5::Md5::digest(msg).as_slice().to_vec(),
Self::SHA1 => sha1::Sha1::digest(msg).as_slice().to_vec(),
Self::SHA2_224 => sha2::Sha224::digest(msg).as_slice().to_vec(),
Self::SHA2_256 => sha2::Sha256::digest(msg).as_slice().to_vec(),
Self::SHA2_384 => sha2::Sha384::digest(msg).as_slice().to_vec(),
Self::SHA2_512 => sha2::Sha512::digest(msg).as_slice().to_vec(),
Self::SHA3_384 => sha3::Sha3_384::digest(msg).as_slice().to_vec(),
Self::SHA3_512 => sha3::Sha3_512::digest(msg).as_slice().to_vec(),
}
}
pub fn output_size(self) -> usize {
match self {
Self::MD5 => md5::Md5::output_size(),
Self::SHA1 => sha1::Sha1::output_size(),
Self::SHA2_224 => sha2::Sha224::output_size(),
Self::SHA2_256 => sha2::Sha256::output_size(),
Self::SHA2_384 => sha2::Sha384::output_size(),
Self::SHA2_512 => sha2::Sha512::output_size(),
Self::SHA3_384 => sha3::Sha3_384::output_size(),
Self::SHA3_512 => sha3::Sha3_512::output_size(),
}
}
}