Reputation: 639
I am trying to download the certificates that I have on several KeyVaults including their private keys. Through Azure Portal I can do it without issues just going to the KeyVault, selecting the certificate and clicking on "Download in PFX/PEM format"
As I have to repeat the same operation on several keyvaults I was looking for an automated way to do it. So far I arrive to the following:
$objCertificate = (Get-AzKeyVaultCertificate -VaultName <Key Vault> -Name <Certificate Name>).Certificate
$bytCertificate = $objCertificate.Export('pfx',<Password>)
$strCertificate = [System.Convert]::ToBase64String($bytCertificate)
$strPath = Join-Path $env:TEMP "$($objCertificate.Subject).pfx"
$bytCertificate | Set-Content -Path $strPath -Force -Encoding Byte
The issue is that it download the certificate with the Public Key only and I also need the Private Key included in it on the same way as when I download it through the portal. Do you know what I could be missing?
Upvotes: 2
Views: 18558
Reputation: 633
Here an example on how to do it with bash
Public key -> Download from kv certificate Private key -> Download from kv secret
# Define the passphrase for the PKCS#12 file.
passphrase=""
pkcs12_file="client_certificate.pfx"
# Retrieve the client certificate from the Key Vault
certificate_pfx_base64=$(az keyvault secret show --name ${certificate_name} --vault-name ${KeyVaultName} --query value --output tsv)
echo "$certificate_pfx_base64" | base64 --decode > client_certificate.pfx
# Extract private key and certificates
openssl pkcs12 -in "$pkcs12_file" -passin "pass:$passphrase" -nocerts -nodes -out key.pem
openssl pkcs12 -in "$pkcs12_file" -passin "pass:$passphrase" -nokeys -out cert.pem
# Print the private key
echo "Private Key:"
cat key.pem
# Print the certificates
echo "Certificates:"
cat cert.pem
# Retrieve the public key certificate from the Key Vault
az keyvault certificate download --vault-name ${KeyVaultName} --name ${certificate_name} --encoding PEM -f public.pem
# display the public certificate
cat public.pem
Upvotes: 1
Reputation: 1
If you don't care about the saved PFX file having a password, you don't need to create a nee X509Certificate from the secret string and Export it, you can simply use something like this:
$certBase64 = Get-AzKeyVaultSecret -VaultName $keyvaultname -Name $secretName -Version $version -AsPlainText
$certBytes = [Convert]::FromBase64String($certBase64)
[System.IO.File]::WriteAllBytes($pfxPath, $certBytes)
Upvotes: 0
Reputation: 321
Thanks for the example Torre!
For future reference, there is an issue with you code example, you mention PEM, but use Psk12. I think that should be something like:
if (secret.Properties.ContentType.Equals(CertificateContentType.Pkcs12.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
return Convert.FromBase64String(secret.Value);
}
// For PEM, we need to extract the base64-encoded message body.
if (secret.Properties.ContentType.Equals(CertificateContentType.Pem.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
return Convert.FromBase64String(secret.Value);
}
Upvotes: 0
Reputation: 2275
You have to download the certificate as a secret rather than as a certificate. From https://azidentity.azurewebsites.net/post/2018/05/17/azure-key-vault-app-service-certificates-finding-downloading-and-converting:
Connect-AzAccount
$vaultName = "<NameOfKeyVault>"
$keyVaultSecretName = "<NameOfTheSecretWhereCertificateIsStored>"
$secret = Get-AzureKeyVaultSecret -VaultName $VaultName -Name $keyVaultSecretName
$pfxCertObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @([Convert]::FromBase64String($secret.SecretValueText),"",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$pfxPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 50 | % {[char]$_})
$currentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
[Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
[io.file]::WriteAllBytes(".\KeyVaultCertificate.pfx", $pfxCertObject.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $pfxPassword))
Write-Host "Created an App Service Certificate copy at: $currentDirectory\KeyVaultCertificate.pfx"
Write-Warning "For security reasons, do not store the PFX password. Use it directly from the console as required."
Write-Host "PFX password: $pfxPassword"
Upvotes: 2
Reputation: 19921
To get the private key you need to get it as a secret (yes, its strange), I don't have the answer in PowerShell but I hope my C# code below can give you some hints on how to do it.
/// <summary>
/// Load a certificate (with private key) from Azure Key Vault
///
/// Getting a certificate with private key is a bit of a pain, but the code below solves it.
///
/// Get the private key for Key Vault certificate
/// https://github.com/heaths/azsdk-sample-getcert
///
/// See also these GitHub issues:
/// https://github.com/Azure/azure-sdk-for-net/issues/12742
/// https://github.com/Azure/azure-sdk-for-net/issues/12083
/// </summary>
/// <param name="config"></param>
/// <param name="certificateName"></param>
/// <returns></returns>
public static X509Certificate2 LoadCertificate(IConfiguration config, string certificateName)
{
string vaultUrl = config["Vault:Url"] ?? "";
string clientId = config["Vault:ClientId"] ?? "";
string tenantId = config["Vault:TenantId"] ?? "";
string secret = config["Vault:Secret"] ?? "";
Console.WriteLine($"Loading certificate '{certificateName}' from Azure Key Vault");
var credentials = new ClientSecretCredential(tenantId: tenantId, clientId: clientId, clientSecret: secret);
var certClient = new CertificateClient(new Uri(vaultUrl), credentials);
var secretClient = new SecretClient(new Uri(vaultUrl), credentials);
var cert = GetCertificateAsync(certClient, secretClient, certificateName);
Console.WriteLine("Certificate loaded");
return cert;
}
/// <summary>
/// Helper method to get a certificate
///
/// Source https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs
/// </summary>
/// <param name="certificateClient"></param>
/// <param name="secretClient"></param>
/// <param name="certificateName"></param>
/// <returns></returns>
private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient,
SecretClient secretClient,
string certificateName)
{
KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName);
// Return a certificate with only the public key if the private key is not exportable.
if (certificate.Policy?.Exportable != true)
{
return new X509Certificate2(certificate.Cer);
}
// Parse the secret ID and version to retrieve the private key.
string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length != 3)
{
throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}");
}
string secretName = segments[1];
string secretVersion = segments[2];
KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion);
// For PEM, you'll need to extract the base64-encoded message body.
// .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier.
if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase))
{
byte[] pfx = Convert.FromBase64String(secret.Value);
return new X509Certificate2(pfx);
}
throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}");
}
}
}
Upvotes: 12