#powershell #certificate #azure-keyvault
#powershell #сертификат #azure-keyvault
Вопрос:
Я пытаюсь загрузить сертификаты, которые у меня есть на нескольких KeyVaults, включая их закрытые ключи. Через портал Azure я могу сделать это без проблем, просто перейдя в KeyVault, выбрав сертификат и нажав «Загрузить в формате PFX / PEM»
Поскольку мне приходится повторять одну и ту же операцию с несколькими ключами, я искал автоматический способ сделать это. Пока я прихожу к следующему:
$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
Проблема в том, что он загружает сертификат только с открытым ключом, и мне также нужен закрытый ключ, включенный в него так же, как при загрузке его через портал.
Вы знаете, чего мне может не хватать?
Ответ №1:
Чтобы получить закрытый ключ, вам нужно получить его как секретный (да, это странно), у меня нет ответа в PowerShell, но я надеюсь, что мой приведенный ниже код на C # может дать вам несколько советов о том, как это сделать.
/// <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}");
}
}
}
Комментарии:
1. Спасибо, определенно помогает. Никогда не думайте, что закрытый ключ будет храниться как секретный, поскольку он не отображается как таковой на портале Azure
Ответ №2:
Вы должны загружать сертификат как секретный, а не как сертификат. Из 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: $currentDirectoryKeyVaultCertificate.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"
Ответ №3:
Спасибо за пример Torre!
Для дальнейшего использования, существует проблема с вашим примером кода, вы упоминаете PEM, но используете Psk12. Я думаю, это должно быть что-то вроде:
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);
}