Created
July 19, 2024 18:02
-
-
Save JohnLBevan/57cd36de0f5fd5e6b629c21b58e0a992 to your computer and use it in GitHub Desktop.
Fetches a certificate from Azure Key Vault and exports its full chain (client>intermediate>root) and private key to a PFX with a private key export password.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Function Export-AzKeyVaultCertificateToPfx { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory)] | |
[string]$Subscription | |
, | |
[Parameter(Mandatory)] | |
[string]$VaultName | |
, | |
[Parameter(Mandatory)] | |
[string]$CertificateName | |
, | |
[Parameter(Mandatory)] | |
[string]$Path | |
, | |
[Parameter(Mandatory)] | |
[SecureString]$Password | |
) | |
[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]$privateKeyIsExportable = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable | |
[Security.Cryptography.X509Certificates.X509ContentType]$pfxFormat = [Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12 | |
$originalContext = Get-AzContext | |
Set-AzContext -Subscription $Subscription | Out-Null | |
try { | |
# fetch the cert info from KV | |
[string]$certSecret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $CertificateName -AsPlainText -ErrorAction Stop | |
[byte[]]$certBytes = [Convert]::FromBase64String($certSecret) | |
# note: A lot of the below can be avoided via `$fullChain.Import($certBytes, $null, $privateKeyIsExportable)`... though that only gets part of the chain, and doesn't guarantee the correct order. | |
[System.Security.Cryptography.X509Certificates.X509Certificate2]$clientCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certBytes, $null, $privateKeyIsExportable) | |
# Build the full chain | |
[System.Security.Cryptography.X509Certificates.X509Chain]$chainBuilder = [System.Security.Cryptography.X509Certificates.X509Chain]::new() | |
$chainBuilder.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck | |
if (!$chainBuilder.Build($clientCert)) { | |
throw 'Error building certificate chain: certificate is invalid' | |
} | |
[System.Security.Cryptography.X509Certificates.X509Certificate2Collection]$fullChain = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new() | |
$certs = $chainBuilder.ChainElements | Select-Object -ExpandProperty 'Certificate' | Sort-Object 'NotAfter' -Descending # hacky way to ensure our certs are in order; works on the assumption that a cert will never expire before its issuer; could improve this by comparing subject vs issuer thumbprints, but for my needs the quick and dirty ws good enough. For a more complex version, see https://github.com/PKISharp/ACME-PS/pull/129/files. Note; we load the certs in reverse order! | |
foreach ($cert in $certs) { | |
Write-Verbose "Processing $($cert.Subject) by $($cert.Issuer)" | |
if ($cert.Thumbprint -eq $clientCert.Thumbprint) { | |
$fullChain.Add($clientCert) | Out-Null # add this one rather than the one from our chain, as this one contains the private key | |
} else { | |
$fullChain.Add($cert) | Out-Null | |
} | |
} | |
# export to file | |
[byte[]]$pfxBytes = $fullChain.Export($pfxFormat, ($Password | ConvertFrom-SecureString -AsPlainText)) # Note: using the SecureString parameter of this method seemed to have a bug, whilst converting to plaintext avoided it | |
[System.IO.File]::WriteAllBytes($Path, $pfxBytes) | |
} finally { | |
# revert to original subscription to avoid causing side effects | |
Set-AzContext -Subscription ($originalContext.Subscription) | Out-Null | |
} | |
} | |
# Example Usage: | |
# Export-AzKeyVaultCertificateToPfx -Subscription 'MySubscription' -VaultName 'MyKeyVault' -CertificateName 'MyCertificate' -Path 'c:\temp\certs\myCertificate.pfx' -Password ('demoN0t$ecure' | ConvertTo-SecureString -AsPlainText -Force) -Verbose |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment