Files
nexus/rust/vendor/sspi/tools/aadjoin/AADJoin/Utils/CryptoUtils.cs
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

173 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
namespace AADJoin.Utils
{
public class CryptoUtils
{
public static string PemCsrToFile(string rawCsr, Guid id)
{
return PemCsrToFile(rawCsr, id.ToString());
}
public static string PemCsrToFile(string rawCsr, string id)
{
var fileId = id + ".csr";
string[] lines = { "-----BEGIN CERTIFICATE REQUEST-----", rawCsr, "-----END CERTIFICATE REQUEST-----" };
File.WriteAllLines(fileId, lines);
return fileId;
}
public static string PemCrtToFile(string rawCrt, Guid id)
{
return PemCrtToFile(rawCrt, id.ToString());
}
public static string PemCrtToFile(string rawCrt, string id)
{
var fileId = id + ".cer";
string[] lines = { "-----BEGIN CERTIFICATE-----", rawCrt, "-----END CERTIFICATE-----" };
File.WriteAllLines(fileId, lines);
return fileId;
}
public static string RsaToPkcs8String(RSACng rsaKey, string password)
{
var bytes = rsaKey.ExportEncryptedPkcs8PrivateKey(
password,
new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 15)
);
return Convert.ToBase64String(bytes);
}
public static string RsaToPkcs8File(RSACng rsaKey, string password, string id)
{
var fileId = id + ".key";
var bytes = rsaKey.ExportEncryptedPkcs8PrivateKey(
password,
new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 15)
);
string[] lines = {
"-----BEGIN ENCRYPTED PRIVATE KEY-----",
Convert.ToBase64String(bytes),
"-----END ENCRYPTED PRIVATE KEY-----"
};
File.WriteAllLines(fileId, lines);
return fileId;
}
public static string PemCrtToPfxFile(string rawCrt, RSA privateKey, string id, string password)
{
var fileId = id + ".pfx";
X509Certificate2 cert = new X509Certificate2(Convert.FromBase64String(rawCrt));
X509Certificate2 pfxCert = cert.CopyWithPrivateKey(privateKey);
byte[] pkcs12 = pfxCert.Export(X509ContentType.Pfx, password);
File.WriteAllBytes(fileId, pkcs12);
return fileId;
}
public static RSA Pkcs8ToRsa(string password, string content)
{
var rsa = RSA.Create();
int bytesRead = 0;
ReadOnlySpan<char> passwordSpan = password.AsSpan();
ReadOnlySpan<byte> contentSpan = Convert.FromBase64String(content);
rsa.ImportEncryptedPkcs8PrivateKey(passwordSpan, contentSpan, out bytesRead);
return rsa;
}
public static byte[] FromBase64(string payload)
{
var data = payload.Replace("_", "/").Replace("-","+");
while (data.Length % 4 != 0)
{
data += '=';
}
return Convert.FromBase64String(data);
}
public static byte[] DecryptSessionKeyFromJwe(string jwe, RSA key)
{
Console.WriteLine(jwe);
var parsedJwe = jwe.Split('.');
var cipher = FromBase64(parsedJwe[1]);
Console.WriteLine(cipher.Length);
return key.Decrypt(cipher, RSAEncryptionPadding.OaepSHA1);
}
public static byte[] ExtractContextFromJwe(string jwe)
{
var document = JsonDocument.Parse(FromBase64(jwe.Split('.')[0]));
return FromBase64(document.RootElement.GetProperty("ctx").ToString());
}
public static byte[] DeriveSigningKey(byte[] sessionKey, byte[] context)
{
byte[] label = Encoding.UTF8.GetBytes("AzureAD-SecureConversation");
List<byte> computeValue = new List<byte>();
computeValue.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x01 });
computeValue.AddRange(label);
computeValue.Add(0x00);
computeValue.AddRange(context);
computeValue.AddRange(new byte[] { 0x00, 0x00, 0x01, 0x00 });
HMACSHA256 hmac = new HMACSHA256(sessionKey);
byte[] hmacOutput = hmac.ComputeHash(computeValue.ToArray());
return hmacOutput;
}
public static string[] DecryptCeritficate(byte[] sessionKey, byte[] context, string jwe)
{
var parsedJwe = jwe.Split('.');
var iv = FromBase64(parsedJwe[2]);
var encData = FromBase64(parsedJwe[3]);
var signingKey = DeriveSigningKey(sessionKey, context);
var cryptoProvider = new AesCryptoServiceProvider();
cryptoProvider.IV = iv;
cryptoProvider.Key = signingKey;
var buffer = new MemoryStream();
var cryptoStream = new CryptoStream(buffer, cryptoProvider.CreateDecryptor(signingKey, iv), CryptoStreamMode.Write);
cryptoStream.Write(encData, 0, encData.Count());
cryptoStream.FlushFinalBlock();
var decData = buffer.ToArray();
cryptoStream.Dispose();
cryptoProvider.Dispose();
var document = JsonDocument.Parse(decData);
var rdp = document.RootElement.GetProperty("x5c").ToString();
var rdp_ca = document.RootElement.GetProperty("x5c_ca").ToString();
return new[] { rdp, rdp_ca };
}
}
}