Intro

For a work project, I needed to migrate a large number of TLS certificates from one Azure Key Vault to another. This would have taken a long time to do manually via clickops. I am all about the devops, so this seemed like a good excuse to automate it with Powershell. I am not well versed in Powershell, so this was a good learning experience for me.

In this post, I will show you how to migrate certifiates from one Azure Key Vault to another using Powershell.

Software Versions

The following software versions were used in this post.

  • Powershell - 7.3.4
  • Az (Azure Powershell) - 9.6.0

Pre-Flight Check

Since I ran the powershell scripts on my local machine, I used the Connect-AzAccount cmdlet To authenticate to the Azure tenancy. This allows you to connect to Azure with an authenticated account for use with cmdlets from the Az PowerShell modules.

Simply run the Connect-AzAccount command in Powershell. A browser window will open and you will be prompted to login to your Azure account.

Export

There were some examples online, but most of them related to older versions of Powershell. Even the Azure documentation was not correct. I found the solution to export the certificates that worked for me in this legends Powershell module. Although, I didn't use the module, I used the code as a reference.

The first step in the migration is to export the certificates. The following script will export the certificates to a local directory. Check the commants in the script for implentation details.

Note
Exporting the certificates requires IAM permission to access both the Certificates and Secrets in the Key Vault. This is because the private key is stored as a secret in the Key Vault.
certificate-export.ps1
# My source and destination Key Vaults are in different subscriptions.
# This set the subscription context for the rest of the script.
Set-AzContext -Subscription "source-subscription"

# The name of the Key Vault.
$vault_name = "source-keyvault"

# Get a list of certificates in the Key Vault.
$certificates = Get-AzKeyVaultCertificate -VaultName $vault_name

# Loop through the certificates and export them to a local directory.
foreach ($cert in $certificates) {

  # Get the certificate from the Key Vault
  $certificate = Get-AzKeyVaultCertificate -VaultName $vault_name -Name $cert.Name

  # You cannot import an expired certificate into Azure Key Vault.
  # This checks if the certificate is expired and sets the path accordingly.
  # Note: The paths need to be created before running the script.
  if ($certificate.Expires -lt (Get-Date)) {
    "Certificate {0} expired" -f $certificate.Name
    $path = ".\certs\src\expired\{0}.pfx" -f $cert.Name
  }
  else {
    "Certificate {0} current" -f $certificate.Name
    $path = ".\certs\src\current\{0}.pfx" -f $cert.Name
  }
  # Get the certificates secret from the Key Vault.
  $pfx_secret = Get-AzKeyVaultSecret -VaultName $vault_name -Name $certificate.Name -AsPlainText

  # Convert the secret to a byte array.
  $secret_byte = [Convert]::FromBase64String($pfx_secret)

  # Create and x509 certificate object that can be imported locally.
  $x509_cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection

  # Import the certificate into the x509 object, marking it as exportable.
  $x509_cert.Import($secret_byte, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

  # Export the certificate as a PFX file in PKCS12 format.
  $pfx_file_byte = $x509_cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12)
  
  # Write the certificate to a file.
  [IO.File]::WriteAllBytes($path, $pfx_file_byte)
}

Now grab a coffee, if you have alot of certificates, this may take a while.

Important
The export script, does NOT export the certificates with a passowrd. This allows the private key to be used by anyone who has access to it. Be sure to save it to a location that is not automatically backed up, and permanently delete the local copies after the migration is complete.

Import

Now the certificates are exported, we can import them into the destination Key Vault. The certificates we can import (the non-expired ones) are located in the .\certs\src\current\ directory.

certificate-import.ps1
# My source and destination Key Vaults are in different subscriptions.
# This set the subscription context for the rest of the script.  
Set-AzContext -Subscription "destination-subscription"

# Name of the destination Key Vault.
$vault_name = "destination-keyvault"

# Name of the resource group the destination Key Vault is in.
$resource_group = "destination-resource-group"

# Path to the certificates to import.
$certificate_path = ".\certs\src\current\"

# Get the Key Vault object
$key_vault = Get-AzKeyVault -ResourceGroupName $resource_group -VaultName $vault_name

# Get a list of filenames in the certificate path.
$filenames = Get-ChildItem -Path $certificate_path -Name

# Loop through the filenames and import the certificates.
foreach ($filename in $filenames) {
  # This sets the certificate name in the keyvault from the filename of the certificate.
  $certificate = ".\certs\src\current\{0}" -f $filename
  $cert_name = $filename.Replace(".pfx", "")

  # Import the certificate into the Key Vault.
  Import-AzKeyVaultCertificate -VaultName $key_vault.VaultName -Name $cert_name -FilePath $certificate
}

Now grab another coffee, if you have alot of certificates, this may take a while too.

Outro

And that's it. The certificates are now migrated from one Key Vault to another. If you are reading this post and looking to do the same thing, I hope this helps you save some cycles. Peace out ✌🏾