我对 Key Vault 和证书不太了解,并且正在解决一个问题。我正在利用 PFX 文件生成 JWT 令牌来调用外部 Web 服务。它工作正常。现在我需要将 PFX 存储在 Key Vault 中并执行相同的操作。
我正在使用 Az DevOps 和 Az Cli 命令上传证书
az keyvault certificate import --file $(filename.secureFilePath) --name pfx-cert-name --vault-name "keyvault-name" --password "password"
现在当我尝试在我的 .net core 中使用 PFX 时。我正在使用 CertificateClient 类和 GetCertificateAsync 方法来获取 PFX 文件的字节数组。
var client = new CertificateClient(new Uri(kvUri), new DefaultAzureCredential()); var cert = await client.GetCertificateAsync(certName);
certInBytes = cert.Value.Cer;
代码失败。在线阅读后,我明白了这是因为“获取证书”获取了 PFX 文件的公共详细信息。因此,我开始在线阅读并使用 powershell 上的 Az Cli 命令进行导入和下载。
我尝试了另一种技术来使用以下命令下载 PFX 的原始形式:
az keyvault secret download --file inputCert.pfx --vault-name keyvault-name --encoding base64 --name pfx-cert-name
该命令给了我另一个 pfx,但它仍然不是 PFX 的原始形式。当我尝试使用此证书获取 JWT 令牌时,收到密码无效的错误。
我有两个替代方案,但我不想使用其中任何一个,因为它们都不是干净的解决方案:
要获取带有私钥的证书,您需要将其作为秘密下载,而不是作为证书。是的,这听起来确实很奇怪,你就是这样做的。
这是我用来从 AKV 下载带有私钥的证书的代码:
/// <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:ClientSecret"] ?? "";
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}");
}
}
上面的代码依赖于这些 NuGet 包:
有两种方法可以解决这个问题。一种是在 DefaultCredentials 类的帮助下,另一种解决方案是在使用 ClientSecretCredentials 类的 SPN 的帮助下。
我已经写了一篇关于这两个解决方案的详细文章。由于最初的问题与 DefaultCredentials 有关,所以我首先写了它