0

I'm creating a solution to get info from our Azure AD tenant. I'm using powershell Invoke-Webrequest and OAuth 2.0 client credential grant to get the info. When I use a client secret my script is working fine but I want to make it more secure and use a self-signed certificate.

I use the following command to create a new certificate on my local pc:

$cert = New-SelfSignedCertificate -HashAlgorithm "SHA256" -Subject "CN=******" -CertStoreLocation "Cert:\Currentuser\My" -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears(5)

After creating I export this certificate to .cer using mmc and upload it to the Azure AD app. Image from uploaded certificate

I also creating a JSON Web Token using the following script:

############################################
## Variable
############################################
$appEndPoint = "https://login.microsoftonline.com/************/oauth2/token"
$appClientID = "*******************************"
$exportPath = [Environment]::GetFolderPath("desktop")
$guid = [guid]::NewGuid()

############################################
## JWT Token starttime/endtime
############################################
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()

$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()

###########################################
##Decoded Json JWT Token
###########################################
$jwtID = $guid.Guid
$decJwtHeader = '{"alg":"RS256","typ":"JWT","x5t":"' + [System.Convert]::ToBase64String(($cert.GetCertHash())) + '"}'
#$decJwtPayLoad = '{"ver":"2.0","aud":"'+ $appEndPoint + '","exp":' + $jwtEndTimeUnix + ',"iss":"' + $appClientID + '","jti":"' + $jwtID + '","nbf":' + $jwtStartTimeUnix + ',"sub":"' + $appClientID +'"}'
$decJwtPayLoad = '{
        "aud":"' + $appEndPoint + '"
    ,   "exp":"' + $jwtEndTimeUnix + '"
    ,   "iss":"' + $appClientID + '"
    ,   "jti":"' + $jwtID + '"
    ,   "nbf":"' + $jwtStartTimeUnix + '"
    ,   "sub":"' + $appClientID + '"
}'
##########################################
##Encode Json JWT Token
##########################################
$encJwtHeaderBytes = [system.text.encoding]::UTF8.GetBytes($decJwtHeader)
$encHeader = [system.convert]::ToBase64String($encJwtHeaderBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')

$encJwtPayLoadBytes = [system.text.encoding]::UTF8.GetBytes($decJwtPayLoad)
$encPayLoad = [system.convert]::ToBase64String($encJwtPayLoadBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')

$jwtToken = $encHeader + '.' + $encPayLoad

$toSign = [system.text.encoding]::UTF8.GetBytes($jwtToken)

#########################################
##Sign JWT Token
#########################################
$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = "2.16.840.1.101.3.4.2.1"

$dataBytes = [System.Text.Encoding]::UTF8.GetBytes($toSign)
$hashBytes = $HashAlgo.ComputeHash($dataBytes)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)

$sig = [System.Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '='

#$sig = [system.convert]::ToBase64String($cert.PrivateKey.SignData($toSign,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '='

$jwtToken = $jwtToken + '.' + $sig

$exportPath = $exportPath + "\jwtToken.txt"

$jwtToken | Out-File $exportPath

This JSON Web Token is the client assertion in my Invoke-Webrequest

#################################################################
## ENKEL VOOR TESTING, NIET IN AMP GEBRUIKEN ####################
#################################################################

$tenantid = '**********'
$subscriptionid = '**********'
$clientid = '**********'
$client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
$client_assertion = '********'

##################################################################
##################################################################
##################################################################

$return = Invoke-Command -ScriptBlock { 
param($tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion)    

Add-Type -AssemblyName System.Web

$enc_client_assertion_type = [System.Web.HttpUtility]::UrlEncode($client_assertion_type)
$encScope = [System.Web.HttpUtility]::UrlEncode('https://management.azure.com/.default')
$enc_client_assertion = [System.Web.HttpUtility]::UrlEncode($client_assertion)

$body = "scope=$encScope&client_id=$clientid&client_assertion_type=$enc_client_assertion_type&client_assertion=$enc_client_assertion&grant_type=client_credentials"

$auth = Invoke-WebRequest "https://login.microsoftonline.com/$tenantid/oauth2/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing

$token = ($auth | ConvertFrom-Json).access_token
$headers = @{
    'Authorization'="Bearer $($token)"
}

$data = Invoke-WebRequest "https://management.azure.com/subscriptions/$subscriptionid/providers/Microsoft.Advisor/recommendations?api-version=2017-04-19" -Method GET -Headers $headers -UseBasicParsing

New-Object PSObject -Property @{
    content=$data.content
}

} -ArgumentList $tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion

$content = $return.content

Write-Host $content

I'm receiving the following error but can't find out why. Anyone an idea?

Invoke-WebRequest : {"error":"invalid_client","error_description":"AADSTS700027: Client assertion contains an invalid signature. [Reason - The provided signature value did not match the expected signature value., 
Thumbprint of key used by client: '******************', Found key 'Start=10/14/2019 09:02:58, End=10/14/2024 09:12:59', Please visit 'https://developer.microsoft.com/en-us/graph/graph-explorer' 
and query for 'https://graph.microsoft.com/beta/applications/***********' to see configured keys]\r\nTrace ID: 085758a5-7470-4a3d-91ba-6f98518e7100\r\nCorrelation ID:
1ab6c7d2-4e46-4e7b-a56f-42720a24286a\r\nTimestamp: 2019-10-14 09:43:15Z","error_codes":[700027],"timestamp":"2019-10-14
09:43:15Z","trace_id":"085758a5-7470-4a3d-91ba-6f98518e7100","correlation_id":"1ab6c7d2-4e46-4e7b-a56f-42720a24286a","error_uri":"https://login.microsoftonline.com/error?code=700027"}
3

1 Answer 1

1

You are almost right!

The only problem is that you forget using your own certificate:

$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = "2.16.840.1.101.3.4.2.1"

$dataBytes = [System.Text.Encoding]::UTF8.GetBytes($toSign)
$hashBytes = $HashAlgo.ComputeHash($dataBytes)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)

The right way is that:

#Get cert from your cert store. Please make sure that the private key is exportable
$Cert = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Subject -eq "CN=AADApplicationWithCert"}

$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = [System.Security.Cryptography.CryptoConfig]::MapNameToOID("SHA256");

#Use your private key
$RSACryptoSP.FromXmlString($Cert.PrivateKey.ToXmlString($true))

$hashBytes = $HashAlgo.ComputeHash($toSign)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
$signedBytes = [Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '=' 

My whole sample:

$appEndPoint = "https://login.microsoftonline.com/hanxia.onmicrosoft.com/oauth2/token"
$appClientID = "dc175b96-c196-43cf-aa0b-ea03e56da5e7"
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
$jwtID = [guid]::NewGuid().Guid

$Cert = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Subject -eq "CN=AADApplicationWithCert"}

$decJwtHeader = @{
    alg="RS256";
    typ="JWT";
    x5t=[System.Convert]::ToBase64String($Cert.GetCertHash())
} | ConvertTo-Json -Compress

$decJwtPayLoad = @{
    aud = $appEndPoint;
    exp = $jwtEndTimeUnix;
    iss = $appClientID;
    jti = $jwtID;
    nbf = $jwtStartTimeUnix;
    sub = $appClientID
} | ConvertTo-Json -Compress

$encJwtHeaderBytes = [system.text.encoding]::UTF8.GetBytes($decJwtHeader)
$encHeader = [system.convert]::ToBase64String($encJwtHeaderBytes) -replace '\+','-' -replace '/','_' -replace '='

$encJwtPayLoadBytes = [system.text.encoding]::UTF8.GetBytes($decJwtPayLoad)
$encPayLoad = [system.convert]::ToBase64String($encJwtPayLoadBytes) -replace '\+','-' -replace '/','_' -replace '='

$jwtToken = $encHeader + '.' + $encPayLoad
$toSign = [system.text.encoding]::UTF8.GetBytes($jwtToken)

$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = [System.Security.Cryptography.CryptoConfig]::MapNameToOID("SHA256");

$RSACryptoSP.FromXmlString($Cert.PrivateKey.ToXmlString($true))
$hashBytes = $HashAlgo.ComputeHash($toSign)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
$sig = [Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '=' 

$jwtToken = $jwtToken + '.' + $sig
$jwtToken
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.