Chendrayan Venkatesan

2 minute read

Of late, I was in need to use a PFX in the Azure DevOps pipeline. Well, it’s not super complex, and having said that, it’s not relatively easy. What do we learn in this blog post? A simple PowerShell script to use a PFX certificate in the Azure DevOps PowerShell script task. Let me try to explain the steps as most effortless as possible.

Note: All the parameters CLIENTID, TENANTID , CERTIFICATECREDENTIAL and SECUREFILE are a release variables. So, you need to set it up-front.

STEP 1

Install the below packages in any desired location

1. Microsoft.IdentityModel.JsonWebTokens.6.9.0
2. Microsoft.IdentityModel.Logging.6.9.0
3. Microsoft.IdentityModel.Tokens.6.9.0

You can choose to install from the Nuget directly or store in the local folder and use it in your code. In my case, I stored it in the assemblies folder. Here is the project scaffolding.

Scaffolding

Step 2

The below snippet is to load the assemblies in the current execution context.

[System.Reflection.Assembly]::LoadFrom((Get-ChildItem .\ASSEMBLIES\Microsoft.IdentityModel.JsonWebTokens.6.9.0\lib\net45\Microsoft.IdentityModel.JsonWebTokens.dll).fullname)
[System.Reflection.Assembly]::LoadFrom((Get-ChildItem .\ASSEMBLIES\Microsoft.IdentityModel.Logging.6.9.0\lib\net45\Microsoft.IdentityModel.Logging.dll).fullname)
[System.Reflection.Assembly]::LoadFrom((Get-ChildItem .\ASSEMBLIES\Microsoft.IdentityModel.Tokens.6.9.0\lib\net45\Microsoft.IdentityModel.Tokens.dll).fullname)

Step 3

Load the PFX file from the secure file.

$SecureFile = $SECUREFILE
$TempDirectory = $ENV:AGENT_TEMPDIRECTORY
$PFXFilePath = Join-Path $TempDirectory $SecureFile
$X509Certificate2 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($PFXFilePath, "$($CERTIFICATECREDENTIAL)")

Step 4

Client Assertion – This is a bit tricky, at least for me, because I spent hours understanding. Here is the documentation for your reference.

$Aud = "https://login.microsoftonline.com/$($TENANTID)/oauth2/token"
$X509Certificate2 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($PFXFilePath, "$($CERTIFICATECREDENTIAL)")
$Claims = [System.Collections.Generic.Dictionary[string, object]]::new()
$Claims['aud'] = $Aud
$Claims['iss'] = $CLIENTID
$Claims['sub'] = $CLIENTID
$Claims['jti'] = [GUID]::NewGuid().ToString('D')
$SecurityTokenDescriptor = [Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor]::new()
$SecurityTokenDescriptor.Claims = $Claims
$SecurityTokenDescriptor.SigningCredentials = [Microsoft.IdentityModel.Tokens.X509SigningCredentials]::new($X509Certificate2, 'RS256')
$Handler = [Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler]::new()
$CreateToken = $Handler.CreateToken($SecurityTokenDescriptor)
$Token = [Microsoft.IdentityModel.JsonWebTokens.JsonWebToken]::new($CreateToken)

Step 5

Retrieve bearer token

$Body = @{
    "resource"              = "https://management.core.windows.net/"
    "client_id"             = $ClientID
    "tenant_id"             = $TenantID
    "grant_type"            = "client_credentials"
    "client_assertion_type" = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    "client_assertion"      = $Token.EncodedToken
}
$Result = Invoke-RestMethod -Uri https://login.microsoftonline.com/$($TENANTID)/oauth2/token `
                            -Method Post `
                            -Body $Body `
                            -ContentType 'application/x-www-form-urlencoded'

Yes, this can be used to access the resources. Now, you may ask, Why not Azure PowerShell Task in Az Do? There are some use cases where I need to authenticate to access resources.

comments powered by Disqus