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 {
///
/// Checks if the account associated with provided username exists and active
///
/// Account username
///
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");
}
///
/// Performs basic authorization in the Azure AD using account username and password.
/// It's just account authorization and not related to any tenant.
///
/// client id
/// Account credentials
/// AuthResponse object
public static async Task 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();
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(content);
Console.WriteLine("Azure AD authorization succeeded!");
return authResponse;
}
///
/// Performs user authorization in the tenant using previously obtained refresh token.
///
/// client id
/// AuthResponse object returned from the AzureADAuthorize method
/// TenantAuthResponse object
public static async Task 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();
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;
}
///
/// 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;
///
/// Azure AD domain
/// TenantAuthResponse object returned from the AzureTenantAuthorize method
/// Device creds object
public static async Task 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;
}
///
/// Requests nonce from the Azure AD tenant.
///
/// Tenant UUID
/// Base64 encoded nonce
private static async Task 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();
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();
}
///
/// Obtains refresh and access token with the TGT.
///
/// Account credentials
/// Tenant id
/// Previously obtained nonce
/// Device creds (private key and certificate)
/// JSON response from AzureAD as string
private static async Task 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();
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;
}
///
/// Checks if the provided domain exists and active
///
/// AzureAD domain
///
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");
}
///
/// Obtains client P2P certificates ans saves them into the corresponding files.
///
/// Account credentials
/// Tenant id
/// Device credentials (private key and certificate)
///
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 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();
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;
}
///
/// 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`
///
/// AzureAD domain
/// Tenant id
/// Device credentials
///
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();
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!");
}
///
/// Obtains P2P certificates.
///
/// Device id
/// Azure AD domain
/// Azure AD tenant id
/// Device credentials (private key and certificate)
///
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();
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();
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!");
}
}
}