KeyVault 生成的带有可导出私钥的证书

Posted

技术标签:

【中文标题】KeyVault 生成的带有可导出私钥的证书【英文标题】:KeyVault generated certificate with exportable private key 【发布时间】:2017-10-05 20:22:34 【问题描述】:

我正在尝试使用“Self”颁发者创建一个自签名证书 KeyVault。

$policy = New-AzureKeyVaultCertificatePolicy -SubjectName "CN=$($certificateName)" -IssuerName "Self" -ValidityInMonths 12 

$policy.Exportable = $true

Add-AzureKeyVaultCertificate -VaultName $vaultName -Name $certificateName -CertificatePolicy $policy

但是,在取回证书时,它似乎没有私钥。

在深入研究其他 API 文档和 powershell cmdlet 的源代码之后,在 KeyVault 中直接创建证书似乎并没有在网上得到广泛报道,我被难住了。

我希望这是我错过的一些简单的事情,因为我希望避免在本地创建证书..

【问题讨论】:

【参考方案1】:

如果您想检索证书及其私钥,则可以通过以下方式将其导出到磁盘上的 PFX 文件(密码为空):

$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)

如果您只想查看内存中的私钥本身而不写入磁盘,请尝试:

$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)

这将显示除指数和模数之外的私有参数。

如果您想使用自己的密码保护磁盘上的 PFX 文件(按照 this blog post 中的“检索 pfx 文件并重新添加密码”说明),请尝试:

$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)

如 REST API 文档 here 和 here 中所述,Azure Key Vault (AKV) 通过三个相互关联的资源表示给定的 X.509 证书:AKV 证书、AKV 密钥和 AKV-秘密。这三个将共享相同的名称和相同的版本 - 要验证这一点,请检查来自 Get-AzureKeyVaultCertificate 的响应中的 IdKeyIdSecretId 属性。

这 3 种资源中的每一种都为查看给定 X.509 证书提供了不同的视角:

AKV 证书提供 X.509 证书的公钥和证书元数据。它包含公钥的模数和指数(ne),以及其他证书元数据(指纹、到期日期、主题名称等)。在 PowerShell 中,您可以通过以下方式获取:
(Get-AzureKeyVaultCertificate -VaultName $vaultName -Name $certificateName).Certificate
AKV 密钥提供 X.509 证书的私钥。如果相应的证书被标记为不可导出,则它对于执行加密操作(例如签名)很有用。在 PowerShell 中,您只能通过以下方式获取此私钥的公共部分
(Get-AzureKeyVaultKey -VaultName $vaultName -Name $certificateName).Key
AKV-secret 提供了一种导出完整 X.509 证书的方法,包括其私钥(如果其策略允许导出私钥)。如上所示,当前的 base64 编码证书可以通过以下方式在 PowerShell 中获取:
(Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName).SecretValueText

【讨论】:

非常彻底的回应,感谢阿德里亚诺!我开始意识到我需要找回秘密,因为 powershell API 并不是最容易理解的 - .Certificate 是一个陷阱 :-) 这有助于很好地解释事情!【参考方案2】:

以下是 C# 代码,用于根据证书名称和 KeyVault 连接信息从最新到最旧检索证书的所有版本,包括其私钥。它使用新的Azure.CoreAzure.IdentityAzure.Security.KeyVault.[Certificates|Secrets] SDK 包。

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;
    

感谢@Nandun's answer here 为我指出了使用 SecretClient 而不是 CertificateClient 的正确方向,但该帖子被标记为重复,因此在此处发布此扩展代码。

【讨论】:

以上是关于KeyVault 生成的带有可导出私钥的证书的主要内容,如果未能解决你的问题,请参考以下文章

使用 powershell 导出带有私钥的证书,包括路径中的所有证书

各种格式SSH 公钥和私钥之间的转换

如何从自签名证书的密钥库中导出私钥

jks 证书文件的生成步骤

在 c# 中使用 ECDSA 生成证书

使用keytool 生成证书