KeyVault generated certificate with exportable private key

If you'd like to retrieve your certificate along with its private key, then you can export it to a PFX file (with an empty password) on your disk via:

$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"

$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
[IO.File]::WriteAllBytes($pfxPath, $pfxUnprotectedBytes)

If you'd like to view just the private key itself in-memory without writing to disk, then try:

$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"

$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
$pfx = New-Object Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($pfxUnprotectedBytes, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$pfx.PrivateKey.ExportParameters($true)

which will show the private parameters in addition to the exponent and modulus.

If you'd like to protect the PFX file on disk with your own password (as per the "Retrieve pfx file & add password back" instructions in this blog post), then try:

$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"
$password = "my-password"

$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
$pfx = New-Object Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($pfxUnprotectedBytes, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$pfxProtectedBytes = $pfx.Export([Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $password)
[IO.File]::WriteAllBytes($pfxPath, $pfxProtectedBytes)

As mentioned in the REST API docs here and here, Azure Key Vault (AKV) represents a given X.509 certificate via three interrelated resources: an AKV-certificate, an AKV-key, and an AKV-secret. All three will share the same name and the same version - to verify this, examine the Id, KeyId, and SecretId properties in the response from Get-AzureKeyVaultCertificate.

Each of these 3 resources provide a different perspective for viewing a given X.509 cert:

  • The AKV-certificate provides the public key and cert metadata of the X.509 certificate. It contains the public key's modulus and exponent (n and e), as well as other cert metadata (thumbprint, expiry date, subject name, and so on). In PowerShell, you can obtain this via:
(Get-AzureKeyVaultCertificate -VaultName $vaultName -Name $certificateName).Certificate
  • The AKV-key provides the private key of the X.509 certificate. It can be useful for performing cryptographic operations such as signing if the corresponding certificate was marked as non-exportable. In PowerShell, you can only obtain the public portion of this private key via:
(Get-AzureKeyVaultKey -VaultName $vaultName -Name $certificateName).Key
  • The AKV-secret provides a way to export the full X.509 certificate, including its private key (if its policy allows for private key exporting). As demonstrated above, the current base64-encoded certificate can be obtained in PowerShell via:
(Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName).SecretValueText

Following is C# code to retrieve all versions of a certificate, including their private keys, from newest to oldest, given its certificate name and KeyVault connection info. It uses the new Azure.Core, Azure.Identity, and Azure.Security.KeyVault.[Certificates|Secrets] SDK packages.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Secrets;

public static class CertTools
{
    public static void MyMethod(string tenantId, string clientId, string clientSecret, Uri keyVaultUri)
    {
        var cred = new ClientSecretCredential(tenantId, clientId, clientSecret); // or any other means of obtaining Azure credential
        var certs = GetAllCertificateVersions(keyVaultUri, cred, "MyCert");
    }

    public static List<X509Certificate2> GetAllCertificateVersions(Uri keyVaultUri, TokenCredential credential,
        string certificateName)
    {
        var certClient = new CertificateClient(keyVaultUri, credential);
        var secretClient = new SecretClient(keyVaultUri, credential);

        var now = DateTimeOffset.UtcNow;

        var certs = new List<X509Certificate2>();

        foreach (var cert in certClient.GetPropertiesOfCertificateVersions(certificateName)
            .OrderByDescending(x => x.CreatedOn)
            // fetch all enabled, non-expired certificates. adjust this predicate if desired.
            .Where(x => x.ExpiresOn >= now && (x.Enabled ?? false)))
        {
            var secret = secretClient.GetSecret(certificateName, cert.Version).Value;
            certs.Add(new X509Certificate2(Convert.FromBase64String(secret.Value)));
        }

        return certs;
    }
}

Thanks to @Nandun's answer here for pointing me in the right direction of using the SecretClient instead of CertificateClient, but that post was marked as a duplicate so posting this extended code here.