Files
Andy Dixon 50c0104d57 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.
2026-10-12 10:50:24 +01:00

119 lines
4.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.IO;
using System.Text;
using AADJoin.Creds;
namespace AADJoin.Utils
{
public class JwtUtils
{
// The AAD cloud ap identifier. it will always be "38aa3b87-a06d-4817-b275–7a316988d93b"
static string AadApIdentifier = "38aa3b87-a06d-4817-b275-7a316988d93b";
public static string GenerateRdpCertificateRequestJwt(
string nonce,
string context,
string username,
string refreshToken,
byte[] signingKey,
RSA key
)
{
var header = new JwtHeader();
header.Add("alg", "HS256");
header.Add("kdf_ver", "2");
header.Add("ctx", context);
var payload = new JwtPayload();
payload.Add("iss", "aad:brokerplugin");
payload.Add("grant_type", "refresh_token");
payload.Add("aud", "login.microsoftonline.com");
payload.Add("request_nonce", nonce);
payload.Add("scope", "openid aza ugs");
payload.Add("refresh_token", refreshToken);
payload.Add("client_id", AadApIdentifier);
payload.Add("cert_token_use", "user_cert");
payload.Add("csr_type", "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10");
var csr = new CertificateRequest(string.Format("CN = {0}", username), key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var rawCsr = Convert.ToBase64String(csr.CreateSigningRequest());
payload.Add("csr", rawCsr);
payload.Add("win_ver", "10.0.22000.653");
var dataToSign = string.Format("{0}.{1}", header.Base64UrlEncode(), payload.Base64UrlEncode());
HMACSHA256 hmac = new HMACSHA256(signingKey);
byte[] hmacOutput = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataToSign));
var finalJwt = string.Format("{0}.{1}", dataToSign, Convert.ToBase64String(hmacOutput).Split('=')[0]);
Console.WriteLine(finalJwt);
return finalJwt;
}
public static string GenerateFRequestJwt(string nonce, DeviceCreds device, UserCreds user)
{
var header = new JwtHeader();
header.Add("alg", "RS256");
header.Add("typ", "JWT");
header.Add("x5c", Convert.ToBase64String(device.Certificate.Export(X509ContentType.Cert)));
header.Add("kdf_ver", "2");
var payload = new JwtPayload();
payload.Add("client_id", AadApIdentifier);
payload.Add("request_nonce", nonce);
payload.Add("scope", "openid aza ugs");
payload.Add("win_ver", "10.0.22000.653");
payload.Add("grant_type", "password");
payload.Add("username", user.Username);
payload.Add("password", user.Password);
var dataToSign = string.Format("{0}.{1}", header.Base64UrlEncode(), payload.Base64UrlEncode());
var signature = device.Key.SignData(Encoding.ASCII.GetBytes(dataToSign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var finalJwt = string.Format("{0}.{1}", dataToSign, Convert.ToBase64String(signature).Split('=')[0]);
Console.WriteLine(finalJwt);
return finalJwt;
}
public static string GenerateP2PRequestJwt(string nonce, DeviceCreds device, string dnsName)
{
var header = new JwtHeader();
header.Add("alg", "RS256");
header.Add("typ", "JWT");
header.Add("x5c", Convert.ToBase64String(device.Certificate.Export(X509ContentType.Cert)));
var payload = new JwtPayload();
payload.Add("client_id", AadApIdentifier);
payload.Add("request_nonce", nonce);
payload.Add("win_ver", "10.0.18363.0");
payload.Add("grant_type", "device_auth");
payload.Add("cert_token_use", "device_cert");
payload.Add("csr_type", "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10");
var csr = new CertificateRequest(device.Certificate.Subject, device.Key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var rawCsr = Convert.ToBase64String(csr.CreateSigningRequest());
payload.Add("csr", rawCsr);
payload.Add("netbios_name", dnsName);
payload.Add("dns_names", new string[] { dnsName });
var dataToSign = string.Format("{0}.{1}", header.Base64UrlEncode(), payload.Base64UrlEncode());
var signature = device.Key.SignData(Encoding.ASCII.GetBytes(dataToSign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var finalJwt = string.Format("{0}.{1}", dataToSign, Convert.ToBase64String(signature).Split('=')[0]);
Console.WriteLine(finalJwt);
return finalJwt;
}
}
}