Sunday, October 9, 2022

[SOLVED] PowerShell Core: Get cert object from PFX file

Issue

Using PowerShell on Linux, how do I get an X509Certificate2 object from a PKCS#12 file (aka PFX file) ?

On Windows, it's done like this:

$cert = (Get-PfxData -FilePath "mycert.p12" -Password $secstr).EndEntityCertificates[0]
# Pass cert object to some cmdlet, in this case MS Graph API:
Connect-MgGraph -Certificate $cert .....

The problem is that the PKI module is not available for Linux. I tried to dig into the background for all of this mess, and I think it is a consequence of the shortcuts that .NET Core is taking when it claims to be "cross-platform". (hint: it ain't true cross-platform as Microsoft has decided to rely on OS specific libraries for a lot of the security related stuff, unlike for example the route Java is taking). When the background .NET features are not there then consequently it is not possible for Microsoft to create the parity on PowerShell either.

Apart from my rant, can you answer my question? How would I do it?

Update (solution)

As per Toni's answer this will work on Linux:

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("myfile.p12",$secstr)

(not aware that there's any PowerShell binding for this, calling directly into .Net from PowerShell is not my favorite, but it does the job)

The PKCS#12 format allows a file to include an unlimited number of certificates. I'm assuming that the above will only work when the PKCS#12 file only has one certificate (chain) in it or that the behaviour in case of multiple certificate chains is un-specified. But one certificate (chain) is most typically the case. Or at least the case in my scenario.

The above works for me using Linux and PowerShell 7.2. To be certain I've tested with a .p12 file not created by PowerShell.


Solution

On Windows if the certificate is stored in the certificate store you can access the cert by using get-childitem, e.g.:

$cert = Get-ChildItem  -Path 'Cert:\LocalMachine\MY' | ?{$_.Thumbprint -eq $thumbprint}

If the certificate is stored plain on the disk I think its the same for Linux and Windows:

#Without Password
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([path])

#With Password
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([path],[password])

X509Certificate2 Class is part of the .NET Core, so it works on Linux too.

https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?view=netcore-3.1

Alternatively you can also use a password as secret to connect to Azure by using a ServicePrincipal, e.g.:

$tenandId = #fill the Id of your Tenant here
$clientId = #fill in the Id of the ServicePrincipal here
$secret = #fill in the password of the ServicePrincipal
#HashTable used for splatting
$paramsHt = @{}

$body = @{
    Grant_Type    = 'client_credentials'
    Scope         = 'https://graph.microsoft.com/.default'
    Client_Id     = $ClientID
    Client_Secret = $Secret
}
#Establish connection and obtain Bearer Token
$con = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token" -Method 'Post' -Body $body
#Get Token issued
$token = $con.access_token
#Add token to HashTable
$paramsHT.add('AccessToken',$token)
#Connect to GraphAPI
Connect-MgGraph @paramsHT

But by doing so you have to handle the token refresh by yourself.



Answered By - Toni
Answer Checked By - David Marino (WPSolving Volunteer)