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:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user