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
+31
View File
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.32407.337
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AADJoin", "AADJoin\AADJoin.csproj", "{75FA134F-D957-4FA5-8215-5881F73267F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{C8CC0BBA-909E-418A-9CCD-96087369FAC0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{75FA134F-D957-4FA5-8215-5881F73267F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75FA134F-D957-4FA5-8215-5881F73267F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75FA134F-D957-4FA5-8215-5881F73267F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75FA134F-D957-4FA5-8215-5881F73267F6}.Release|Any CPU.Build.0 = Release|Any CPU
{C8CC0BBA-909E-418A-9CCD-96087369FAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8CC0BBA-909E-418A-9CCD-96087369FAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8CC0BBA-909E-418A-9CCD-96087369FAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8CC0BBA-909E-418A-9CCD-96087369FAC0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D8FCDD3A-161B-4671-A938-4192019BC7A6}
EndGlobalSection
EndGlobal
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.35.0" />
</ItemGroup>
</Project>
+529
View File
@@ -0,0 +1,529 @@
using AADJoin.Creds;
using AADJoin.Messages;
using AADJoin.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace AADJoin
{
internal class AzureAD {
/// <summary>
/// Checks if the account associated with provided username exists and active
/// </summary>
/// <param name="username">Account username</param>
/// <returns></returns>
public static async Task CheckUsername(string username)
{
Console.WriteLine("Start {0} username checking...", username);
var url = String.Format("https://login.microsoftonline.com/common/userrealm/{0}?api-version=1.0", username);
var httpClient = new HttpClient();
var response = await httpClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine("Error. Status code: {0}. Body:", response.StatusCode);
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
Console.WriteLine("User exist and active");
}
/// <summary>
/// Performs basic authorization in the Azure AD using account username and password.
/// It's just account authorization and not related to any tenant.
/// </summary>
/// <param name="clientId">client id</param>
/// <param name="user">Account credentials</param>
/// <returns>AuthResponse object</returns>
public static async Task<AuthResponse> AzureAdAuthorize(string clientId, UserCreds user)
{
Console.WriteLine("Start Azure AD authorization...");
var url = "https://login.microsoftonline.com//common/oauth2/token";
var httpClient = new HttpClient();
var form = new Dictionary<string, string>();
form.Add("grant_type", "password");
form.Add("password", user.Password);
form.Add("client_id", clientId);
form.Add("username", user.Username);
form.Add("resource", "https://graph.windows.net");
form.Add("scope", "openid");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(form));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
var authResponse = JsonSerializer.Deserialize<AuthResponse>(content);
Console.WriteLine("Azure AD authorization succeeded!");
return authResponse;
}
/// <summary>
/// Performs user authorization in the tenant using previously obtained refresh token.
/// </summary>
/// <param name="clientId">client id</param>
/// <param name="authResponse">AuthResponse object returned from the AzureADAuthorize method</param>
/// <returns>TenantAuthResponse object</returns>
public static async Task<TenantAuthResponse> AzureTenantAuthorize(string clientId, AuthResponse authResponse)
{
Console.WriteLine("Start Azure Tenant authorization...");
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", authResponse.GetTenantId());
var httpClient = new HttpClient();
var form = new Dictionary<string, string>();
form.Add("scope", "openid");
form.Add("grant_type", "refresh_token");
// https://github.com/Gerenios/AADInternals/blob/master/AccessToken.ps1#L1864
// 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 means urn:ms-drs:enterpriseregistration.windows.net
// More UUIDs: https://www.rickvanrousselt.com/blog/azure-default-service-principals-reference-table/
form.Add("resource", "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9");
form.Add("client_id", clientId);
form.Add("refresh_token", authResponse.refresh_token);
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(form));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
var tenantAuthResponse = TenantAuthResponse.FromString(content);
Console.WriteLine("Azure Tenant authorization succeeded!");
return tenantAuthResponse;
}
/// <summary>
/// Joins new device to the Azure AD. Automatically creates new device key/certificate, and id. The device id is
/// a randomly generated UUID. The private key password is the device id.
/// Saves them in corresponding files:
/// * `{device_id}.key` - the device private key; {device_id} is a password from the key;
/// * `{random_id}.csr` - the device certificate request that has been used to obtain the device certificate;
/// * `{device_id}.cer` - the device certificate;
/// </summary>
/// <param name="domain">Azure AD domain</param>
/// <param name="tenantAuthResponse">TenantAuthResponse object returned from the AzureTenantAuthorize method</param>
/// <returns>Device creds object</returns>
public static async Task<DeviceCreds> JoinDevice(string domain, TenantAuthResponse tenantAuthResponse)
{
Console.WriteLine("Start device joining...");
var url = "https://enterpriseregistration.windows.net/EnrollmentServer/device/?api-version=1.0";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tenantAuthResponse.access_token);
var deviceId = Guid.NewGuid();
// minimal key size = 2048
var deviceKey = new RSACng(2048);
var joinDeviceRequest = new JoinDeviceRequest(domain, deviceKey, deviceId);
var data = new StringContent(joinDeviceRequest.ToString());
data.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(url, data);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
var joinDeviceResponse = JoinDeviceResponse.FromString(content);
var certificate = new X509Certificate(Convert.FromBase64String(joinDeviceResponse.Certificate.RawBody));
var device = new DeviceCreds(deviceKey, certificate);
Console.WriteLine("The device transport key password: {0}", device.Id);
Console.WriteLine(
"The device transport key (PKCS8) has been written into the file: {0}",
CryptoUtils.RsaToPkcs8File(deviceKey, device.Id, device.Id)
);
Console.WriteLine(
"The device certificate (.pfx) has been written into the file: {0}",
CryptoUtils.PemCrtToPfxFile(joinDeviceResponse.Certificate.RawBody, deviceKey, device.Id, device.Id)
);
Console.WriteLine(
"The device certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(joinDeviceResponse.Certificate.RawBody, device.Id)
);
Console.WriteLine("Device joining succeeded!");
return device;
}
/// <summary>
/// Requests nonce from the Azure AD tenant.
/// </summary>
/// <param name="tenantId">Tenant UUID</param>
/// <returns>Base64 encoded nonce</returns>
private static async Task<string> RequestNonce(string tenantId)
{
Console.WriteLine("Start obtaining nonce...");
var httpClient = new HttpClient();
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
var nonceForm = new Dictionary<string, string>();
nonceForm.Add("grant_type", "srv_challenge");
var nonceResponse = await httpClient.PostAsync(url, new FormUrlEncodedContent(nonceForm));
var nonceContent = await nonceResponse.Content.ReadAsStringAsync();
if (nonceResponse.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", nonceResponse.StatusCode));
Console.WriteLine(nonceContent);
Environment.Exit(1);
}
Console.WriteLine(nonceContent);
var document = JsonDocument.Parse(nonceContent);
var nonce = document.RootElement.GetProperty("Nonce");
return nonce.ToString();
}
/// <summary>
/// Obtains refresh and access token with the TGT.
/// </summary>
/// <param name="user">Account credentials</param>
/// <param name="tenantId">Tenant id</param>
/// <param name="nonce">Previously obtained nonce</param>
/// <param name="device">Device creds (private key and certificate)</param>
/// <returns>JSON response from AzureAD as string</returns>
private static async Task<string> TokenWithTgt(
UserCreds user,
string tenantId,
string nonce,
DeviceCreds device
)
{
Console.WriteLine("Start TokenWithTGT...");
var httpClient = new HttpClient();
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
var certForm = new Dictionary<string, string>();
certForm.Add("request", JwtUtils.GenerateFRequestJwt(
nonce,
device,
user
));
certForm.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
certForm.Add("client_info", "1");
certForm.Add("tgt", "true");
certForm.Add("windows_api_version", "2.2");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(certForm));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
return content;
}
/// <summary>
/// Checks if the provided domain exists and active
/// </summary>
/// <param name="domain">AzureAD domain</param>
/// <returns></returns>
private static async Task CheckDomain(string domain)
{
var url = String.Format("https://login.microsoftonline.com/common/UserRealm/?user={0}&api-version=1.0&checkForMicrosoftAccount=false&fallback_domain={1}", domain, domain);
var httpClient = new HttpClient();
var response = await httpClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine("Error. Status code: {0}. Body:", response.StatusCode);
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
Console.WriteLine("Domain exist and active");
}
/// <summary>
/// Obtains client P2P certificates ans saves them into the corresponding files.
/// </summary>
/// <param name="user">Account credentials</param>
/// <param name="tenantId">Tenant id</param>
/// <param name="device">Device credentials (private key and certificate)</param>
/// <returns></returns>
public static async Task ObtainClientP2PCertificate(UserCreds user, string tenantId, DeviceCreds device)
{
Console.WriteLine("Start obtaining client P2P certificates...");
await CheckDomain(user.Domain);
var nonce = await RequestNonce(tenantId);
Console.WriteLine("First nonce: {0}", nonce);
var response = await TokenWithTgt(user, tenantId, nonce, device);
var responseJson = JsonDocument.Parse(response);
var refreshToken = responseJson.RootElement.GetProperty("refresh_token").ToString();
var sessionKeyJwe = responseJson.RootElement.GetProperty("session_key_jwe").ToString();
var sessionKey = CryptoUtils.DecryptSessionKeyFromJwe(sessionKeyJwe, device.Key);
nonce = await RequestNonce(tenantId);
Console.WriteLine("Second nonce: {0}", nonce);
// randomly generated nonce
// we can hard code it for our tool
byte[] context = { 25, 152, 185, 126, 55, 118, 199, 221, 254, 108, 255, 202, 88, 128, 76, 218, 200, 157, 211, 63, 242, 37, 152, 198 };
var contextBase64 = "GZi5fjd2x93+bP/KWIBM2sid0z/yJZjG";
var signingKey = CryptoUtils.DeriveSigningKey(sessionKey, context);
var certificateJwe = await RdpCertificate(
nonce,
contextBase64,
tenantId,
refreshToken,
signingKey,
user,
device
);
var certificates = CryptoUtils.DecryptCeritficate(sessionKey, CryptoUtils.ExtractContextFromJwe(certificateJwe), certificateJwe);
var id = string.Format("{0}_client_auth", device.Id);
Console.WriteLine(
"The client P2P certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(certificates[0], id + "_p2p")
);
Console.WriteLine(
"The client P2P CA certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(certificates[1], id + "_p2p_ca")
);
Console.WriteLine("Finished obtaining client P2P certificates!");
}
private static async Task<string> RdpCertificate(
string nonce,
string context,
string tenantId,
string refreshToken,
byte[] signingKey,
UserCreds user,
DeviceCreds device
)
{
Console.WriteLine("Start RDP certificate obtaining...");
var httpClient = new HttpClient();
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
var certForm = new Dictionary<string, string>();
certForm.Add("request", JwtUtils.GenerateRdpCertificateRequestJwt(
nonce,
context,
user.Username,
refreshToken,
signingKey,
device.Key
));
certForm.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
certForm.Add("windows_api_version", "2.2");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(certForm));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
return content;
}
/// <summary>
/// Obtains P2P certificate for server autherization using device key and certificate. Saves certificates in the
/// corresponding files:
/// * `{device_id}_server_auth_p2p.cer`
/// * `{device_id}_server_auth_p2p_ca.cer`
/// </summary>
/// <param name="domain">AzureAD domain</param>
/// <param name="tenantId">Tenant id</param>
/// <param name="device">Device credentials</param>
/// <returns></returns>
public static async Task ObtainServerP2PCertificate(string domain, string tenantId, DeviceCreds device)
{
Console.WriteLine("Start obtaining server P2P certificates...");
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
var nonce = await RequestNonce(tenantId);
var certForm = new Dictionary<string, string>();
certForm.Add("request", JwtUtils.GenerateP2PRequestJwt(
nonce,
device,
string.Format("tenjo.{0}", domain)
));
certForm.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
certForm.Add("windows_api_version", "2.0");
var httpClient = new HttpClient();
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(certForm));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
var p2pCerificatesResponse = P2PCertificatesResponse.FromString(content);
var id = string.Format("{0}_server_auth", device.Id);
Console.WriteLine(
"The server P2P certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(p2pCerificatesResponse.x5c, id + "_p2p")
);
Console.WriteLine(
"The server P2P CA certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(p2pCerificatesResponse.x5c_ca, id + "_p2p_ca")
);
Console.WriteLine("Finished obtaining server P2P certificates!");
}
/// <summary>
/// Obtains P2P certificates.
/// </summary>
/// <param name="deviceId">Device id</param>
/// <param name="domain">Azure AD domain</param>
/// <param name="tenantId">Azure AD tenant id</param>
/// <param name="device">Device credentials (private key and certificate)</param>
/// <returns></returns>
public static async Task ObtainP2PCertificates(string deviceId, string domain, string tenantId, DeviceCreds device)
{
Console.WriteLine("Start obtaining device P2P certificates...");
var httpClient = new HttpClient();
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
var nonceForm = new Dictionary<string, string>();
nonceForm.Add("grant_type", "srv_challenge");
var nonceResponse = await httpClient.PostAsync(url, new FormUrlEncodedContent(nonceForm));
var nonceContent = await nonceResponse.Content.ReadAsStringAsync();
if (nonceResponse.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", nonceResponse.StatusCode));
Console.WriteLine(nonceContent);
Environment.Exit(1);
}
Console.WriteLine(nonceContent);
var document = JsonDocument.Parse(nonceContent);
var nonce = document.RootElement.GetProperty("Nonce");
var certForm = new Dictionary<string, string>();
certForm.Add("request", JwtUtils.GenerateP2PRequestJwt(
nonce.ToString(),
device,
string.Format("mypc.{0}", domain)
));
certForm.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
certForm.Add("windows_api_version", "2.0");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(certForm));
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(string.Format("Error. Status code: {0}. Body:", response.StatusCode));
Console.WriteLine(content);
Environment.Exit(1);
}
Console.WriteLine(content);
var p2pCerificatesResponse = P2PCertificatesResponse.FromString(content);
Console.WriteLine(
"The device P2P certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(p2pCerificatesResponse.x5c, deviceId + "_p2p")
);
Console.WriteLine(
"The device P2P CA certificate (.cer) has been written into the file: {0}",
CryptoUtils.PemCrtToFile(p2pCerificatesResponse.x5c_ca, deviceId + "_p2p_ca")
);
Console.WriteLine("Finished obtaining device P2P certificates!");
}
}
}
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace AADJoin.Creds
{
public class DeviceCreds
{
public string Id { get; set; }
public RSA Key { get; set; }
public X509Certificate Certificate { get; set; }
public DeviceCreds(string id, RSA key, X509Certificate certificate)
{
Id = id;
Key = key;
Certificate = certificate;
}
public DeviceCreds(RSA key, X509Certificate certificate)
{
Console.WriteLine(certificate.Subject);
Id = certificate.Subject.Split('=')[1];
Key = key;
Certificate = certificate;
}
public static DeviceCreds Empty()
{
return new DeviceCreds(null, null, null);
}
public static DeviceCreds FromPfx(string pfxPath, string password)
{
X509Certificate2 cert = new X509Certificate2(pfxPath, password, X509KeyStorageFlags.Exportable);
return new DeviceCreds(
cert.GetRSAPrivateKey(),
new X509Certificate(Convert.FromHexString(cert.GetRawCertDataString()))
);
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AADJoin.Creds
{
public class UserCreds
{
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public UserCreds(string username, string password, string domain)
{
if (username == null)
{
throw new ArgumentNullException(nameof(username));
}
if (password == null)
{
throw new ArgumentNullException(nameof(password));
}
if (domain == null)
{
throw new ArgumentNullException(nameof(domain));
}
Username = username;
Password = password;
Domain = domain;
}
}
}
@@ -0,0 +1,39 @@
using System;
using System.Text.Json;
namespace AADJoin.Messages
{
public class AuthResponse
{
public string token_type { get; set; }
public string scope { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string not_before { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string id_token { get; set; }
public static AuthResponse fromString(string authResponse)
{
return JsonSerializer.Deserialize<AuthResponse>(authResponse);
}
public string GetTenantId()
{
var payload = id_token.Split('.')[1];
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
var document = JsonDocument.Parse(Convert.FromBase64String(payload));
var idToken = document.RootElement.GetProperty("tid");
return idToken.GetString();
}
}
}
@@ -0,0 +1,91 @@
using System;
using System.Text.Json;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using AADJoin.Utils;
namespace AADJoin.Messages
{
public class JoinDeviceRequest
{
public class Csr
{
[JsonInclude]
public string Type { get; set; }
[JsonInclude]
public string Data { get; set; }
public Csr(string type, string data)
{
Type = type;
Data = data;
}
}
public class Attrs
{
[JsonInclude]
public string ReuseDevice { get; set; }
[JsonInclude]
public string ReturnClientSid { get; set; }
[JsonInclude]
public string SharedDevice { get; set; }
public Attrs(bool reuse, bool returnClientSid, bool sharedDevice)
{
ReuseDevice = reuse.ToString();
ReturnClientSid = returnClientSid.ToString();
SharedDevice = sharedDevice.ToString();
}
}
public string TransportKey { get; set; }
public int JoinType { get; set; }
public string DeviceDisplayName { get; set; }
public string OSVersion { get; set; }
[JsonInclude]
public Csr CertificateRequest { get; set; }
public string TargetDomain { get; set; }
public string DeviceType { get; set; }
[JsonInclude]
public Attrs Attributes { get; set; }
public JoinDeviceRequest(string domain, RSACng key, Guid deviceId)
{
byte[] rsaPubKeyExport = key.Key.Export(CngKeyBlobFormat.GenericPublicBlob);
TransportKey = Convert.ToBase64String(rsaPubKeyExport);
// https://github.com/Gerenios/AADInternals/blob/master/PRT_Utils.ps1#L101
// 0 = Azure AD join
// 4 = Azure AD registered
// 6 = Azure AD hybrid join
JoinType = 0;
var now = DateTime.Now;
DeviceDisplayName = string.Format(
"Test Device {0}{1}{2} {3}{4}{5}",
now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second
);
OSVersion = "10.0.22000.978";
var csr = new CertificateRequest("CN=7E980AD9-B86D-4306-9425-9AC066FB014A", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var rawCsr = Convert.ToBase64String(csr.CreateSigningRequest());
CertificateRequest = new Csr("pkcs10", rawCsr);
Console.WriteLine("The device certificate request (CSR) has been written into the file: {0}", CryptoUtils.PemCsrToFile(rawCsr, deviceId));
TargetDomain = domain;
DeviceType = "Windows";
Attributes = new Attrs(true, true, false);
}
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
}
@@ -0,0 +1,39 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AADJoin.Messages
{
public class JoinDeviceResponse
{
public class Cert
{
[JsonInclude]
public string Thumbprint { get; set; }
[JsonInclude]
public string RawBody { get; set; }
}
public class Usr
{
[JsonInclude]
public string Upn { get; set; }
}
public class MembershipChgs
{
[JsonInclude]
public string LocalSID { get; set; }
[JsonInclude]
public string[] AddSIDs { get; set; }
}
public Cert Certificate { get; set; }
public Usr User { get; set; }
public MembershipChgs[] MembershipChanges { get; set; }
public static JoinDeviceResponse FromString(string data)
{
return JsonSerializer.Deserialize<JoinDeviceResponse>(data);
}
}
}
@@ -0,0 +1,21 @@
using System.Text.Json;
namespace AADJoin.Messages
{
public class P2PCertificatesResponse
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string x5c { get; set; }
public string cert_token_use { get; set; }
public string x5c_ca { get; set; }
public string resource { get; set; }
public static P2PCertificatesResponse FromString(string data)
{
return JsonSerializer.Deserialize<P2PCertificatesResponse>(data);
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Text.Json;
namespace AADJoin.Messages
{
public class TenantAuthResponse
{
public string token_type { get; set; }
public string scope { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string not_before { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string id_token { get; set; }
public static TenantAuthResponse FromString(string authResponse)
{
return JsonSerializer.Deserialize<TenantAuthResponse>(authResponse);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using CommandLine;
namespace AADJoin
{
public class Options
{
[Option('j', "join-new-device", Required = false, HelpText = "Join new device to the Azure AD")]
public bool JoinNewDevice { get; set; }
[Option('c', "client-p2p-cert", Required = false, HelpText = "Obtain P2P certificate for the client authorization")]
public bool ClientP2PCert { get; set; }
[Option('s', "server-p2p-cert", Required = false, HelpText = "Obtain P2P certificate for the server authorization")]
public bool ServerP2PCert { get; set; }
[Option('d', "domain", Required = true, HelpText = "Azure AD domain")]
public string Domain { get; set; }
[Option('u', "username", Required = true, HelpText = "User Azure AD username in FQDN format")]
public string Username { get; set; }
[Option('p', "password", Required = true, HelpText = "User password")]
public string Password { get; set; }
[Option('e', "existing-device", Required = false, HelpText = "Path to the PFX file with the device key + certificate")]
public string DevicePfx { get; set; }
[Option('f', "pfx-key-password", Required = false, HelpText = "PFX file password")]
public string PfxPassword { get; set; }
}
}
+54
View File
@@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using CommandLine;
using AADJoin.Creds;
namespace AADJoin
{
public class Program
{
static async Task Main(string[] args)
{
await Parser.Default.ParseArguments<Options>(args).WithParsedAsync(RunOptions);
}
static async Task RunOptions(Options opts)
{
var device = DeviceCreds.Empty();
var user = new UserCreds(opts.Username, opts.Password, opts.Domain);
// well-known client ids: https://github.com/Gerenios/AADInternals/blob/master/AccessToken_utils.ps1#L11
// graph_api: 1b730954-1685-4b74-9bfd-dac224a7b894
string clientId = "1b730954-1685-4b74-9bfd-dac224a7b894";
await AzureAD.CheckUsername(opts.Username);
var authResponse = await AzureAD.AzureAdAuthorize(clientId, user);
if (opts.JoinNewDevice)
{
var tenantAuthResponse = await AzureAD.AzureTenantAuthorize(clientId, authResponse);
device = await AzureAD.JoinDevice(opts.Domain, tenantAuthResponse);
} else if (opts.DevicePfx != null)
{
Console.WriteLine("Trying to load device PFX file...");
device = DeviceCreds.FromPfx(opts.DevicePfx, opts.PfxPassword);
} else
{
Console.WriteLine("Error: No device information provided.");
Environment.Exit(1);
}
if (opts.ServerP2PCert)
{
await AzureAD.ObtainServerP2PCertificate(user.Domain, authResponse.GetTenantId(), device);
} else if (opts.ClientP2PCert)
{
await AzureAD.ObtainClientP2PCertificate(user, authResponse.GetTenantId(), device);
} else
{
Console.WriteLine("Nothing left to do.");
}
}
}
}
@@ -0,0 +1,172 @@
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 };
}
}
}
+118
View File
@@ -0,0 +1,118 @@
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;
}
}
}
+96
View File
@@ -0,0 +1,96 @@
# AADJoin
### Purpose of this tool
This tool simulates new device joining to the Azure Active Directory and device certificates obtaining.
It can obtain three types of certificates:
* the device certificate;
* the client certificate for the P2P authorization
* the server certificate for the P2P authorization
All generated keys, CSRs, and certificates will be saved in appropriate files. You will see paths to them in logs.
### How use it
```
AADJoin 1.0.0
Copyright (C) 2022 AADJoin
-j, --join-new-device Join new device to the Azure AD
-c, --client-p2p-cert Obtain P2P certificate for the client authorization
-s, --server-p2p-cert Obtain P2P certificate for the server authorization
-d, --domain Required. Azure AD domain
-u, --username Required. User Azure AD username in FQDN format
-p, --password Required. User password
-e, --existing-device Path to the PFX file with the device key + certificate
-f, --pfx-key-password PFX file password
--help Display this help screen.
--version Display version information.
```
*Attention: To run this tool you need .Net 6.0.8 runtime*
### Example
Create and join a new device to the AzureAD:
```bash
AADJoin.exe --join-new-device --domain dataans.com --username s10@dataans.com --password wwwWWW222@@@
```
The comman above will create a three files:
* `<device id>.key` - the device private key;
* `<random guid>.csr` - the certificate request that had been used for the device certificate generation;
* `<device id>.cer` - the device certificate;
My example:
```
03b8620d-12ff-48ee-b036-e1cf4c598609.key
007bfc57-2504-404c-99f4-6160d1bfe2cb.csr
03b8620d-12ff-48ee-b036-e1cf4c598609.cer
```
We can obtain the server P2P sertificate with the following command:
```bash
AADJoin.exe --existing-device 03b8620d-12ff-48ee-b036-e1cf4c598609.pfx --pfx-key-password 03b8620d-12ff-48ee-b036-e1cf4c598609 --domain dataans.com --username s10@dataans.com --password wwwWWW222@@@ --server-p2p-cert
```
The command above will create two files:
* `<device id>_server_auth_p2p.cer` - the P2P certificate for the server authentication;
* `<device id>_server_auth_p2p_ca.cer` - the P2P CA certificate of the obtained certificate;
We can obtain the client P2P sertificate with the following command:
```bash
AADJoin.exe --existing-device 03b8620d-12ff-48ee-b036-e1cf4c598609.pfx --pfx-key-password 03b8620d-12ff-48ee-b036-e1cf4c598609 --domain dataans.com --username s10@dataans.com --password wwwWWW222@@@ --client-p2p-cert
```
The command above will create two files:
* `<device id>_client_auth_p2p.cer` - the P2P certificate for the client authentication;
* `<device id>_client_auth_p2p_ca.cer` - the P2P CA certificate of the obtained certificate;
And here are files I had:
```
03b8620d-12ff-48ee-b036-e1cf4c598609_client_auth_p2p.cer
03b8620d-12ff-48ee-b036-e1cf4c598609_client_auth_p2p_ca.cer
03b8620d-12ff-48ee-b036-e1cf4c598609_server_auth_p2p.cer
03b8620d-12ff-48ee-b036-e1cf4c598609_server_auth_p2p_ca.cer
```
+98
View File
@@ -0,0 +1,98 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AADJoin.Messages;
using System.Security.Cryptography;
using System;
using System.Text;
using AADJoin.Utils;
using System.Globalization;
using System.Numerics;
using System.Collections.Generic;
using System.Linq;
using AADJoin;
using System.Threading.Tasks;
using Newtonsoft.Json.Bson;
using Microsoft.IdentityModel.Tokens;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace UnitTests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestTenantIdParsing()
{
var authResponse = AuthResponse.fromString("{\"token_type\":\"Bearer\",\"scope\":\"user_impersonation\",\"expires_in\":\"3958\",\"ext_expires_in\":\"3958\",\"expires_on\":\"1660206842\",\"not_before\":\"1660202583\",\"resource\":\"https://graph.windows.net\",\"access_token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLndpbmRvd3MubmV0IiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvYTkyNTI0NDgtOWFiNy00OWIwLWJiNWMtZjJmOTIzYzg0NjcyLyIsImlhdCI6MTY2MDIwMjU4MywibmJmIjoxNjYwMjAyNTgzLCJleHAiOjE2NjAyMDY4NDIsImFjciI6IjEiLCJhaW8iOiJBU1FBMi84VEFBQUF4dHBFbTMvWnEwQlZPcnQ3M1BXazNYTjJzRHlrUGdRUjgvWENqLzA2OEpzPSIsImFtciI6WyJwd2QiXSwiYXBwaWQiOiIxYjczMDk1NC0xNjg1LTRiNzQtOWJmZC1kYWMyMjRhN2I4OTQiLCJhcHBpZGFjciI6IjAiLCJpcGFkZHIiOiIxOTQuNDQuNDUuMTIxIiwibmFtZSI6InM0Iiwib2lkIjoiZWE4YjhlODctNzM0OC00MzJhLWFlNTUtNGIyMjQ2MWE3NGJkIiwicHVpZCI6IjEwMDMyMDAyMUFFRDgzNjciLCJyaCI6IjAuQVhrQVNDUWxxYmVhc0VtN1hQTDVJOGhHY2dJQUFBQUFBQUFBd0FBQUFBQUFBQUNVQUkwLiIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6ImRzeU5ZNUp3SzYxbU5PbTAwakdNOFgtak5VdS00X1BuMmRIQmxoMnc4QkkiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiRVUiLCJ0aWQiOiJhOTI1MjQ0OC05YWI3LTQ5YjAtYmI1Yy1mMmY5MjNjODQ2NzIiLCJ1bmlxdWVfbmFtZSI6InM0QGRhdGFhbnMuY29tIiwidXBuIjoiczRAZGF0YWFucy5jb20iLCJ1dGkiOiJyRTl4QVdjWnlrSzJVT0VQeEdGWUFBIiwidmVyIjoiMS4wIn0.Ma5odOE1Lnm0C_5aCPPLdyTISO-1wNhTjLcvgXl4D_1LyxeXSYnMv5H0D2_HNjntDWPqBWzpXbujCE3rwZYeoSlrBSC2N3Ua0T38ceOCxS7fwA70Th4dgpFITPHokintL26eLXnlwr7erOOO_1T8RGcj9RRolF5gI1ArnL4nXmBsKisQBjZuFqKUYZgMzqBbBtcxbRGy_NduaOx_NKwxE_c7Pmo4mS6YX22n4z4p1hakCXqk3UmGin3basWHRoiD32Z7vJyXX1KWkvv8MIVupVkurW8KAvRfx4asSAsQn7Ii2_JsItxLCDoFXY--6VFaB4mwdp6xKQWchb1BVZi0dA\",\"refresh_token\":\"0.AXkASCQlqbeasEm7XPL5I8hGclQJcxuFFnRLm_3awiSnuJSUAI0.AgABAAEAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P-v1WHQotebkoyXd9fjaE78Ld2Esf6sLTg0AeeyRqOL7XekLSSdFiAncPZTwZYzG_E60e6DZYgrAhRxBt5QRgRLNtHDAJZ2h0tiXGdpSQsLvlXKWQEre1FeosL6KeAGPCCfphBX_1khW8tk-YIxXi_5kwUxZedbwPS3qA5VnQKvYvPMF9bQyHYUdk8AVfPsorcln4KTQdTaRpoGZCsdwTtat13jnOnfs6NSr7JV_l6U2ysCyZZDtS2Q327lGH8_MTwhf-rtxJe60T8dDFsPQhGPGXwYSKJoToLE3UXMIGcjMtkgsiPkku-XGDLvR4c2WlDYm4TwZUKQ5JUZvBk9jeFPLS2WCrdguK5pWKjZw02vLBu3eBxE7AesZXNew-bNvjyG65qBJRI_0du7z0NH5CaiqCeWmgc7bHejl3pkM6_WXhhBpj_OvnzwJFbPbmSW1_N24FaJiqralvPIyQqqBjkX1GTzHDLHONaGD5en2w2Z4m_4glTkko4z3IP3YGP_AN_WjdJ5NPrgBEYzAmlaMNJDEFejXPe2ifWjxucwqhVbPBu5_TcwJhuErx1adl4YQ_Pzq56BaP3Ybi6gNxdnnsqtcOPiDlMC5lv8BCIOT4kLW8bpksM1tqpxUrxHNk65C4qiHEVWABH8cpabmg\",\"id_token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIxYjczMDk1NC0xNjg1LTRiNzQtOWJmZC1kYWMyMjRhN2I4OTQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hOTI1MjQ0OC05YWI3LTQ5YjAtYmI1Yy1mMmY5MjNjODQ2NzIvIiwiaWF0IjoxNjYwMjAyNTgzLCJuYmYiOjE2NjAyMDI1ODMsImV4cCI6MTY2MDIwNjQ4MywiYW1yIjpbInB3ZCJdLCJpcGFkZHIiOiIxOTQuNDQuNDUuMTIxIiwibmFtZSI6InM0Iiwib2lkIjoiZWE4YjhlODctNzM0OC00MzJhLWFlNTUtNGIyMjQ2MWE3NGJkIiwicHVpZCI6IjEwMDMyMDAyMUFFRDgzNjciLCJyaCI6IjAuQVhrQVNDUWxxYmVhc0VtN1hQTDVJOGhHY2xRSmN4dUZGblJMbV8zYXdpU251SlNVQUkwLiIsInN1YiI6ImVzQzkwWWdfN3MyTVBXNTA0cE04UVp5Njl4ZGZPUVQxbExoQnZqYjdJRjgiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiRVUiLCJ0aWQiOiJhOTI1MjQ0OC05YWI3LTQ5YjAtYmI1Yy1mMmY5MjNjODQ2NzIiLCJ1bmlxdWVfbmFtZSI6InM0QGRhdGFhbnMuY29tIiwidXBuIjoiczRAZGF0YWFucy5jb20iLCJ2ZXIiOiIxLjAifQ.\"}");
Assert.AreEqual("a9252448-9ab7-49b0-bb5c-f2f923c84672", authResponse.GetTenantId());
}
[TestMethod]
public void TestP2PCertificatesResponseParsing()
{
var rawResponse = "{\"token_type\":\"x509\",\"expires_in\":\"86399\",\"ext_expires_in\":\"0\",\"expires_on\":\"1660480130\",\"x5c\":\"MIIDgDCCAmigAwIBAgIQYNQVBIdbLvF59KM1CaK9UzANBgkqhkiG9w0BAQsFADBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wHhcNMjIwODEzMTIyMzUwWhcNMjIwODE0MTIyODUwWjBlMTQwMgYKCZImiZPyLGQBGRYkYTkyNTI0NDgtOWFiNy00OWIwLWJiNWMtZjJmOTIzYzg0NjcyMS0wKwYDVQQDDCRkNzgyNTIyNS02YTAzLTQ1Y2YtOTAzOC01MGM2ODM0MWRmMWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiw1YUnKRxOMMOdBOc2DXgqR6qUp+4+43AURHRrez+lHu3/vi5LtQJZlh1SVLJxIRSzPo7LAvbD3lFga9f5TX5lzVn+fNUR6YmQxSGhNwt6XM6O+aiW/gS0QnS8CpLVI1rBkqA1HomHGAu6IWsIWBv+7BKLP50XaHP9HZLHeisJPWQqUkeZD319io20MM/prPCFCtvA70SFaZrFSGCtTPUbDeU57YgX/68Nwx34djD40CEein3q1cyGYRcPdJpS0W38hPLl9w5l6NVyXid5zRm4DFnICf+bqgsCOryx/LLLmoecI2Km51uELP1xlCl8A1rzyyv6O9H1oGP6lHsB9INAgMBAAGjRDBCMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAbBgkrBgEEAYI3FQoEDjAMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBcRy7zmvoWE8SvRQh1QJJf3sPvnmP6FnWkd5rU0oQrqeYx7Y++l7C0Hpl600FcsxMaEDi65uiW80vUpCTOjTlgCZv57Z7Ra8nTm0jI4Y+EiszoA8FZxPTjFv0hQ/f92sbjCLgi7TN6EcO8i+eGeez9uCAuvk2dITZBobOgEaqschpt0gnEeGHqHq3rIOuNqsFoBSmS3niIPULgtGElbhCNtsuGlAAdMch0gaYw6U29I+ZAQ0XRPSA+8IuC6hZcv4+ij3ItAkay544NCN4rEzAXiuEtee6YlBPjCqG3s4Gb0ebVFdEvZQ7k4Bh0h8O+hqDgde63Ki8o2Yu0bTMH3hbP\",\"cert_token_use\":\"device_cert\",\"x5c_ca\":\"MIIDRTCCAi2gAwIBAgIQFjJo2vHWmoNDMmy1tdDXjjANBgkqhkiG9w0BAQsFADBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wHhcNMjIwODA4MDAwMDAwWhcNMjMwODA4MDAwMDAwWjBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL5cI1sb5q4MzqePzFPJkD+cZILh1+pbTriwbSY7pwPNdEEVQ6Z1aBcdVxYUHXYX7eEDulwH+0CQNLw+RHtrajIBEEf3n1DgHPno54rBaCK6jGeDo8SdM03B6HzFnH2Z8zDbYog42jtMyLsGgo6eGpLpL2kHHTwPnkqcVKT5zDodqlHu6Cl7ZSYJlJMV0nqYsIEocEidw3QBwPM3Eb2ZUIU+aLg64GGoCfoXFnoXqMVRymZLyMXZaDfT4J3pFQl8f6yutqmBu6j7XDxIAEamkMxD1FWicOVwOZMK9BYTLgaXXGawlx8Nf//3QhCaCDSliWDw7OeONBp1BodWymqamRAgMBAAGjITAfMB0GA1UdDgQWBBQRWuiJpM68E5FnjQ1sbUpkoa9EJTANBgkqhkiG9w0BAQsFAAOCAQEAWKFD5lu244+UPfSTPpUOaePzs0ofuDjDhYzrx9dNPiOU1SOKfmwNXz2qgeaWaggc2/zEhpgJ5S7593HBHNT8LbKRmwlT9+4qO78B3JsN0hKzqChXV8rczS4rFylmoUJIpVNrip+CHDyP4VVynillduVYEQDt5bkkBovQjonlWPq+nMxZz+vvl7mGCSYeN2FhwiUWO4AaDv0NGSjR3YJ1mpeZa9uNw/pyr5iF1g0BfjmUIDgCBpof3ZEIxFMkzDhmscIOOtTRKPV8H2MP907boXn1oNeJhb7WeoEaW2iwYr8uEef82f8NFKvDIVnr2nhcfCSoo/7bF2hvLEXw432ouQ==\",\"resource\":\"urn:p2p_cert\"}";
var p2pCertificatesResponse = P2PCertificatesResponse.FromString(rawResponse);
Assert.AreEqual("x509", p2pCertificatesResponse.token_type);
Assert.AreEqual("86399", p2pCertificatesResponse.expires_in);
Assert.AreEqual("0", p2pCertificatesResponse.ext_expires_in);
Assert.AreEqual(
"MIIDgDCCAmigAwIBAgIQYNQVBIdbLvF59KM1CaK9UzANBgkqhkiG9w0BAQsFADBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wHhcNMjIwODEzMTIyMzUwWhcNMjIwODE0MTIyODUwWjBlMTQwMgYKCZImiZPyLGQBGRYkYTkyNTI0NDgtOWFiNy00OWIwLWJiNWMtZjJmOTIzYzg0NjcyMS0wKwYDVQQDDCRkNzgyNTIyNS02YTAzLTQ1Y2YtOTAzOC01MGM2ODM0MWRmMWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiw1YUnKRxOMMOdBOc2DXgqR6qUp+4+43AURHRrez+lHu3/vi5LtQJZlh1SVLJxIRSzPo7LAvbD3lFga9f5TX5lzVn+fNUR6YmQxSGhNwt6XM6O+aiW/gS0QnS8CpLVI1rBkqA1HomHGAu6IWsIWBv+7BKLP50XaHP9HZLHeisJPWQqUkeZD319io20MM/prPCFCtvA70SFaZrFSGCtTPUbDeU57YgX/68Nwx34djD40CEein3q1cyGYRcPdJpS0W38hPLl9w5l6NVyXid5zRm4DFnICf+bqgsCOryx/LLLmoecI2Km51uELP1xlCl8A1rzyyv6O9H1oGP6lHsB9INAgMBAAGjRDBCMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAbBgkrBgEEAYI3FQoEDjAMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBcRy7zmvoWE8SvRQh1QJJf3sPvnmP6FnWkd5rU0oQrqeYx7Y++l7C0Hpl600FcsxMaEDi65uiW80vUpCTOjTlgCZv57Z7Ra8nTm0jI4Y+EiszoA8FZxPTjFv0hQ/f92sbjCLgi7TN6EcO8i+eGeez9uCAuvk2dITZBobOgEaqschpt0gnEeGHqHq3rIOuNqsFoBSmS3niIPULgtGElbhCNtsuGlAAdMch0gaYw6U29I+ZAQ0XRPSA+8IuC6hZcv4+ij3ItAkay544NCN4rEzAXiuEtee6YlBPjCqG3s4Gb0ebVFdEvZQ7k4Bh0h8O+hqDgde63Ki8o2Yu0bTMH3hbP",
p2pCertificatesResponse.x5c
);
Assert.AreEqual("device_cert", p2pCertificatesResponse.cert_token_use);
Assert.AreEqual(
"MIIDRTCCAi2gAwIBAgIQFjJo2vHWmoNDMmy1tdDXjjANBgkqhkiG9w0BAQsFADBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wHhcNMjIwODA4MDAwMDAwWhcNMjMwODA4MDAwMDAwWjBNMUswSQYDVQQDHkIATQBTAC0ATwByAGcAYQBuAGkAegBhAHQAaQBvAG4ALQBQADIAUAAtAEEAYwBjAGUAcwBzACAAWwAyADAAMgAyAF0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL5cI1sb5q4MzqePzFPJkD+cZILh1+pbTriwbSY7pwPNdEEVQ6Z1aBcdVxYUHXYX7eEDulwH+0CQNLw+RHtrajIBEEf3n1DgHPno54rBaCK6jGeDo8SdM03B6HzFnH2Z8zDbYog42jtMyLsGgo6eGpLpL2kHHTwPnkqcVKT5zDodqlHu6Cl7ZSYJlJMV0nqYsIEocEidw3QBwPM3Eb2ZUIU+aLg64GGoCfoXFnoXqMVRymZLyMXZaDfT4J3pFQl8f6yutqmBu6j7XDxIAEamkMxD1FWicOVwOZMK9BYTLgaXXGawlx8Nf//3QhCaCDSliWDw7OeONBp1BodWymqamRAgMBAAGjITAfMB0GA1UdDgQWBBQRWuiJpM68E5FnjQ1sbUpkoa9EJTANBgkqhkiG9w0BAQsFAAOCAQEAWKFD5lu244+UPfSTPpUOaePzs0ofuDjDhYzrx9dNPiOU1SOKfmwNXz2qgeaWaggc2/zEhpgJ5S7593HBHNT8LbKRmwlT9+4qO78B3JsN0hKzqChXV8rczS4rFylmoUJIpVNrip+CHDyP4VVynillduVYEQDt5bkkBovQjonlWPq+nMxZz+vvl7mGCSYeN2FhwiUWO4AaDv0NGSjR3YJ1mpeZa9uNw/pyr5iF1g0BfjmUIDgCBpof3ZEIxFMkzDhmscIOOtTRKPV8H2MP907boXn1oNeJhb7WeoEaW2iwYr8uEef82f8NFKvDIVnr2nhcfCSoo/7bF2hvLEXw432ouQ==",
p2pCertificatesResponse.x5c_ca
);
Assert.AreEqual(
"urn:p2p_cert",
p2pCertificatesResponse.resource
);
}
[TestMethod]
public void TestJoinDeviceResponseParsing()
{
var rawJoinDeviceResponse = "{\"Certificate\":{\"Thumbprint\":\"63663A9293FD6CB5F108B1435C78136C36B0407B\",\"RawBody\":\"MIID8jCCAtqgAwIBAgIQFaOoakv7w5ZOUX3Jgk2P8TANBgkqhkiG9w0BAQsFADB4MXYwEQYKCZImiZPyLGQBGRYDbmV0MBUGCgmSJomT8ixkARkWB3dpbmRvd3MwHQYDVQQDExZNUy1Pcmdhbml6YXRpb24tQWNjZXNzMCsGA1UECxMkODJkYmFjYTQtM2U4MS00NmNhLTljNzMtMDk1MGMxZWFjYTk3MB4XDTIyMDgxMjIxMDQ1M1oXDTMyMDgxMjIxMzQ1M1owLzEtMCsGA1UEAxMkYTgwZWM0YjctODA4Ny00N2VmLWJjODYtZmNmY2FiOWJmNWQ5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwwiKdRD944dG340IpqnREL+DXVbLeiYSATsOFEegVAOB6UXVDTZ3QEEM6So6BqT0wCpDav7RxCmFUhkB6MH7PVv3FwqnYwxXSLBfHH9fVZXUISqqu3s2Vi5PbG0whw+OAchFATb1+/ttwHP+nnhhLOo9SJmNoznJ/ho3MJVLzn3/NJdZL2UIEVNPL+lPI01Wja6miDTbdHtmuByqqOYJJlxh2PYfW7LKSoV/5ZX7OYczowrsfxjuJBBj6NKctUB92LaTxtcwRoGKVZ8ARBO2mBmMHacRbqm2tVAVp4sK9c2nmKnSsVyVPkKdY0xNDIBy+JutPs/yA+RBWZrb6DK7/QIDAQABo4HAMIG9MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwIgYLKoZIhvcUAQWCHAIEEwSBELfEDqiHgO9HvIb8/Kub9dkwIgYLKoZIhvcUAQWCHAMEEwSBEIeOi+pIcypDrlVLIkYadL0wIgYLKoZIhvcUAQWCHAUEEwSBEEgkJam3mrBJu1zy+SPIRnIwFAYLKoZIhvcUAQWCHAgEBQSBAkVVMBMGCyqGSIb3FAEFghwHBAQEgQExMA0GCSqGSIb3DQEBCwUAA4IBAQCBiJ4r1FKrdn/iqfDsYLdr7TbIulLccwXZJz3pNlUN/9EeTY6sVrW7H2iLCLk9bVk2S9b8WGuC+gO+E6CRpOPwWHHXAhrGCoEdlRXaWSY0kDArhvdMR9xGJOTPO0m6q8nn1Z3G8EtwD8qIbgwPtE08zJ5ls6UKmDLk9jiKXH6vCgwtgI9WCxNTJYG9npHkK0C6chEa1QZlo3aRKhRPqAJ8B1kh1XRZigXKT8Rqt2YjD+SWjWOYWupAAylysQaJwW+gAIXRQJW3OVpeaP4ajQqpYV11VlVR8t/mIWL1PE+ONJqZc/YFBrT6E9wPYBJzjCMurTj8kHr8yXfZzjNQaGN8\"},\"User\":{\"Upn\":\"s4@dataans.com\"},\"MembershipChanges\":[{\"LocalSID\":\"S-1-5-32-544\",\"AddSIDs\":[\"S-1-12-1-161258645-1267993664-2068636298-1054913958\",\"S-1-12-1-1201544774-1151677024-232476811-374148655\",\"S-1-12-1-3935014535-1126855496-575362478-3178502726\"]}]}";
var joinResponse = JoinDeviceResponse.FromString(rawJoinDeviceResponse);
Assert.AreEqual("63663A9293FD6CB5F108B1435C78136C36B0407B", joinResponse.Certificate.Thumbprint);
Assert.AreEqual(
"MIID8jCCAtqgAwIBAgIQFaOoakv7w5ZOUX3Jgk2P8TANBgkqhkiG9w0BAQsFADB4MXYwEQYKCZImiZPyLGQBGRYDbmV0MBUGCgmSJomT8ixkARkWB3dpbmRvd3MwHQYDVQQDExZNUy1Pcmdhbml6YXRpb24tQWNjZXNzMCsGA1UECxMkODJkYmFjYTQtM2U4MS00NmNhLTljNzMtMDk1MGMxZWFjYTk3MB4XDTIyMDgxMjIxMDQ1M1oXDTMyMDgxMjIxMzQ1M1owLzEtMCsGA1UEAxMkYTgwZWM0YjctODA4Ny00N2VmLWJjODYtZmNmY2FiOWJmNWQ5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwwiKdRD944dG340IpqnREL+DXVbLeiYSATsOFEegVAOB6UXVDTZ3QEEM6So6BqT0wCpDav7RxCmFUhkB6MH7PVv3FwqnYwxXSLBfHH9fVZXUISqqu3s2Vi5PbG0whw+OAchFATb1+/ttwHP+nnhhLOo9SJmNoznJ/ho3MJVLzn3/NJdZL2UIEVNPL+lPI01Wja6miDTbdHtmuByqqOYJJlxh2PYfW7LKSoV/5ZX7OYczowrsfxjuJBBj6NKctUB92LaTxtcwRoGKVZ8ARBO2mBmMHacRbqm2tVAVp4sK9c2nmKnSsVyVPkKdY0xNDIBy+JutPs/yA+RBWZrb6DK7/QIDAQABo4HAMIG9MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwIgYLKoZIhvcUAQWCHAIEEwSBELfEDqiHgO9HvIb8/Kub9dkwIgYLKoZIhvcUAQWCHAMEEwSBEIeOi+pIcypDrlVLIkYadL0wIgYLKoZIhvcUAQWCHAUEEwSBEEgkJam3mrBJu1zy+SPIRnIwFAYLKoZIhvcUAQWCHAgEBQSBAkVVMBMGCyqGSIb3FAEFghwHBAQEgQExMA0GCSqGSIb3DQEBCwUAA4IBAQCBiJ4r1FKrdn/iqfDsYLdr7TbIulLccwXZJz3pNlUN/9EeTY6sVrW7H2iLCLk9bVk2S9b8WGuC+gO+E6CRpOPwWHHXAhrGCoEdlRXaWSY0kDArhvdMR9xGJOTPO0m6q8nn1Z3G8EtwD8qIbgwPtE08zJ5ls6UKmDLk9jiKXH6vCgwtgI9WCxNTJYG9npHkK0C6chEa1QZlo3aRKhRPqAJ8B1kh1XRZigXKT8Rqt2YjD+SWjWOYWupAAylysQaJwW+gAIXRQJW3OVpeaP4ajQqpYV11VlVR8t/mIWL1PE+ONJqZc/YFBrT6E9wPYBJzjCMurTj8kHr8yXfZzjNQaGN8",
joinResponse.Certificate.RawBody
);
Assert.AreEqual("s4@dataans.com", joinResponse.User.Upn);
Assert.AreEqual("S-1-5-32-544", joinResponse.MembershipChanges[0].LocalSID);
Assert.AreEqual("S-1-12-1-161258645-1267993664-2068636298-1054913958", joinResponse.MembershipChanges[0].AddSIDs[0]);
Assert.AreEqual("S-1-12-1-1201544774-1151677024-232476811-374148655", joinResponse.MembershipChanges[0].AddSIDs[1]);
Assert.AreEqual("S-1-12-1-3935014535-1126855496-575362478-3178502726", joinResponse.MembershipChanges[0].AddSIDs[2]);
}
[TestMethod]
public void TestJwtSignature()
{
byte[] key = {37, 150, 246, 91, 244, 248, 131, 90, 221, 132, 103, 95, 89, 45, 99, 167, 229, 126, 64, 98, 54, 106, 164, 199, 58, 196, 255, 251, 3, 198, 189, 231, 255, 37, 235, 58, 145, 190, 222, 58, 16, 117, 188, 67, 38, 97, 255, 119, 125, 2, 200, 124, 15, 119, 242, 48, 145, 86, 2, 101, 131, 60, 14, 60, 21, 112, 93, 234, 148, 196, 107, 51, 36, 135, 255, 65, 128, 43, 11, 99, 66, 248, 147, 27, 140, 222, 27, 43, 24, 50, 204, 253, 161, 77, 211, 105, 220, 142, 171, 131, 101, 251, 206, 53, 197, 163, 84, 80, 180, 87, 64, 235, 155, 72, 52, 247, 151, 25, 14, 36, 140, 203, 186, 104, 21, 74, 40, 178, 176, 36, 215, 244, 106, 196, 120, 225, 107, 197, 34, 170, 202, 227, 6, 163, 27, 140, 124, 76, 246, 174, 179, 100, 164, 197, 64, 254, 63, 157, 220, 17, 112, 188, 145, 55, 131, 106, 147, 43, 100, 170, 192, 36, 142, 40, 196, 141, 144, 23, 183, 29, 22, 130, 239, 56, 53, 34, 210, 186, 61, 142, 156, 72, 144, 67, 228, 247, 38, 78, 127, 51, 225, 11, 79, 62, 116, 43, 129, 69, 30, 176, 138, 189, 127, 135, 109, 105, 198, 200, 92, 254, 242, 33, 166, 251, 38, 81, 100, 252, 220, 49, 29, 13, 208, 125, 229, 143, 165, 127, 141, 89, 76, 215, 58, 114, 101, 79, 81, 107, 174, 8, 103, 238, 228, 207, 208, 142};
byte[] data = Encoding.ASCII.GetBytes("eyJhbGciOiJIUzI1NiIsICJrZGZfdmVyIjoyLCAiY3R4IjoiYVlKdEQ1NHZPN0h0R0dQMkp1Q0ZScnBQTk1kYUQ4SnYifQ.eyJpc3MiOiJhYWQ6YnJva2VycGx1Z2luIiwgImdyYW50X3R5cGUiOiJyZWZyZXNoX3Rva2VuIiwgImF1ZCI6ImxvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20iLCAicmVxdWVzdF9ub25jZSI6IkF3QUJBQUVBQUFBQ0FPel9CQUQwX3h0TnRoZkFwS2NvR1o2V1lQMVVpYnZfLXlOMmd1SS1vc0c3VXB2QUV3UmVGUGo3QndSTGJobFZLQnIzaEhWeElQUkhwMmVEWVl0SWJfYVdZM1hwd0g0Z0FBIiwgInNjb3BlIjoib3BlbmlkIGF6YSB1Z3MiLCAicmVmcmVzaF90b2tlbiI6IjAuQVhrQVNDUWxxYmVhc0VtN1hQTDVJOGhHY29jN3FqaHRvQmRJc25WNk1XbUkyVHVVQUZFLkFnQUJBQUVBQUFELS1ETEEzVk83UXJkZGdKZzdXZXZyQWdEc193UUE5UDlqSlZZb0IwaDk4R2ZvQ3Q3WlplcXU2UzV6WEktdDFTSjc5WXZLcUxBSEdtSzVGZ3VwMllMVWEzYnZWd0hTUngycnBWbnYybEozWVQ1MEF1eEpnVTIxYlc3MEtPREVCUWVKb3VNdkNPZFNiTS0yb2tRbWlJWnBBcjlaYlc5RGItZjZVZkFwRXJKdUZQMFh3SUxSSmo1c0xDUWluOTY5VDd2aTVLMXIzMzFxLXJhY2hSV1cta2owTWFSVnEyZmFPNWdKZjdQTGE2YS1WTEZLSFVzVjM5UklkamxEZkdIQl9TSWdrSHBaZnEyZTJqRTlsQjJuVlVpUWdYZ1ZiS2l6ZXZkbGdqU3lacW1kOG54RmFuZVdVQWRLR1U0VjUyNy0tMHg3c2hrQmN0clhaZnlTaFVrSTllSEZYWFI3cUZHbzdqQ2tXZ1MwRXlfQk5mdTNlTHV2cWhQR3RmcWpzT1dJUGlScmRzSDVMQWFIM0RtbHFyRHZGOURKQkRQMENOaU10LXZXR2dwc2FIdUZqSW9yMGp6eXF0REdHRVVJbFVvdjFSbmtDVW9sajFyREVPZGY4dlhrSkwzQ2x6dTJQaVJEMFhQY25mVWRwTmlCc295WHYzMFplbHI0WFY4ZXN0NXdKREQ4aDJETGJwSXJabEV1dUZDREJpZW9IM3IwUERQT0RnWVFzeHdNVjMxR1BIUVlvbmItbVlJNjhMVW9Ya0VObXF3d2pSN1c3aTBxVnVreHRfU3dSS3dNem1udWNadC1pek9aTWlROXpUTFhwZnd0cXpfMWU5UC1oX3NPS0d5clluZWMtZEIzY043QWl1RlRBbmxpUVZsZWNuZkh6LWNjaGdGazhFa0tidzlLRDdCLUZTaGhmRHVOV1U1MzhFdlVadVphUXJrbnk4VzlZaGNMY1NjMEdKYlJyZDAxLWZEbWV3UEs4R3k0MjNZNmZFT01KLUlSYlhlajlaMG5OMi0zTFoycWVPZWZjWm84UXFXVDdseFNlMnNqVzlNU25DM2RMUFh6c2hjSzc0eUdBY09Wa3VEYTlpWWFlTGxBM0MwTmdLcTcxRHBNNnVjIiwgImNsaWVudF9pZCI6IjM4YWEzYjg3LWEwNmQtNDgxNy1iMjc1LTdhMzE2OTg4ZDkzYiIsICJjZXJ0X3Rva2VuX3VzZSI6InVzZXJfY2VydCIsICJjc3JfdHlwZSI6Imh0dHA6XC9cL3NjaGVtYXMubWljcm9zb2Z0LmNvbVwvd2luZG93c1wvcGtpXC8yMDA5XC8wMVwvZW5yb2xsbWVudCNQS0NTMTAiLCAiY3NyIjoiTUlJQ2JEQ0NBVlFDQVFBd0p6RWxNQ01HQTFVRUF4NGNBSE1BTndCQUFHUUFZUUIwQUdFQVlRQnVBSE1BTGdCakFHOEFiVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLVmNmR0xTNmN4UlJwZzVvU1BYK1NUMU1kYkJIdXNTWnRcL1VNRkxqbVdDaDIwa2tTOFZVWVJjdTdrcDJFdllISjlPMis1Q2FJb3drbUp2aVNiRkdwUm45OWxKVktEYWFSY1o0WDk0VlZZOStuZzRvelVQejA2amtBR0VTVlwvM2JJQ0hsMUZ1NjZRc0NZVWtVb0I0UktNYlh6TzVKV1lzemFHWjBweVdWSE82XC9CU3RVZnVqSWUxbG5EcTVHNzl6MWU0dzk2TVBISTJcL05oNnNGc1VncWJwNGRmTW9UMklyYmloa1h4cURaeXNiS09BN2lvU2hcL3EwY1JyM2ZwcUZKczlnMWpLaGVWT29YWXQ4Z1JCb1pLaFFOWEtTQnJYUVhzQTFqTmoySmN0M3VGUjlRVkhxb0g2c2F4dnErdTV2dUZCSHNNSDBVa05GbStrWm81V0xRc3VaMENBd0VBQWFBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQit6aVpsYmcrWUFQYWs1Q3J2Z3RhSCttYzNCSUJOalhMV2FMNlwvUnFXOEJVSnBIclE5bGE0S2VlR2FcLytCZHJmbmlOTzdrOE85XC9qc0VkWXB1QSt5XC9tVkphRWNoT1JucUpwYUhUZ0JZMFJDMW5EXC9EVHBEXC9xZXFxd1lVSXdoZ2RidmxQQzF6VWJXWlVaUFB1d1AwYUNoUXNycldGWFVXRWg0NW5zRGN4SFl4VTRxcUI0WmQzbTduUHdhZThibGJjckk2YWNOZGtZNmRtaTkzQ29FaFlZdjA4ejlMQmIxQ3V4TzFPUndCQ2lNcDNFWkZyMVE5Sm9ZZW1NTjZFMVBWTnpISDQxMTlcL0IxWjhcL1I0NTB3UW5LY1wvdTN3K3ZYd0xXMjY2elhpXC84RWwrc2JCdk5kQ2VxQkpWUk9ZdkdFTDBCaHNIT2ZUUEtkbndvNHo4WU1MUExHSSIsICJ3aW5fdmVyIjoiMTAuMC4yMjAwMC42NTMifQ");
HMACSHA256 hmac = new HMACSHA256(key);
byte[] hash = hmac.ComputeHash(data);
foreach (var b in hash)
{
Console.Write("{0} ", b);
}
Console.WriteLine();
var base64Hash = Convert.ToBase64String(hash);
Console.WriteLine(base64Hash);
Console.WriteLine("AtxpdY3mOqjsWGTCsDNBKbKDTjQk-jAP7HHZZerX57E");
Assert.AreEqual("AtxpdY3mOqjsWGTCsDNBKbKDTjQk-jAP7HHZZerX57E", base64Hash);
}
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="coverlet.collector" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AADJoin\AADJoin.csproj" />
</ItemGroup>
</Project>