无法使用从AzureKeyVault读取的证书写入JWTToken
我们正在使用自签名证书创建JwtSecurityToken。我们目前正在手动将证书上载到Azure应用程序服务,然后使用此代码查找它无法使用从AzureKeyVault读取的证书写入JWTToken,jwt,azure-keyvault,x509certificate2,Jwt,Azure Keyvault,X509certificate2,我们正在使用自签名证书创建JwtSecurityToken。我们目前正在手动将证书上载到Azure应用程序服务,然后使用此代码查找它 X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); certStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certCollection = certStore.Certificates
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
signingCertThumbprint,
false);
return new X509SigningCredentials(certCollection[0]);
这可以很好地工作,但是我们不想让证书安装在运行appservice的机器上,而是从Azure Key Vault读取证书。附加的好处是,这意味着应用程序服务可以在开发者机器上本地运行,而无需共享和安装证书
我们可以使用从Azure密钥库获取证书
var certificateClient = new CertificateClient(new Uri("https://ourkeyvault.vault.azure.net/"), new DefaultAzureCredential());
var b2cInviteCertificate = certificateClient.GetCertificate("B2CInvite");
return new X509SigningCredentials(new X509Certificate2(b2cInviteCertificate.Value.Cer));
FWIW我还尝试了加载到X509Certificate2
接受密码的ctor
根据这些凭证,我们创建了一个JwtSecurityToken
JwtSecurityToken token = new JwtSecurityToken(
issuer.ToString(),
audience,
claims,
DateTime.Now,
DateTime.Now.AddDays(7),
JwtService.signingCredentials.Value);
然后,我们使用JwtSecurityTokenHandler获取令牌字符串
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
return jwtHandler.WriteToken(token);
调用WriteToken会导致以下错误消息
InvalidOperationException:IDX10638:无法创建
SignatureProvider“key.HasPrivateKey”为false,无法创建签名
为什么会发生这种情况?这不起作用的原因是从KeyVault检索的证书上的PrivateKey为空。如果需要私钥,则需要将证书作为“机密”提取
为了解释原因并更详细地引用另一个答案同意Pat Longs的答案,只有当您将公钥作为证书获得时,才能获得公钥。是的,这毫无意义!通过秘密绕道只是感觉很愚蠢 无论如何,我最终从Azure key Vault获得私钥证书的代码如下所示:
/// <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}");
}
}
//
///从Azure密钥库加载证书(带私钥)
///
///获取带有私钥的证书有点困难,但下面的代码解决了这一问题。
///
///获取密钥库证书的私钥
/// https://github.com/heaths/azsdk-sample-getcert
///
///另请参见以下GitHub问题:
/// https://github.com/Azure/azure-sdk-for-net/issues/12742
/// https://github.com/Azure/azure-sdk-for-net/issues/12083
///
///
///
///
公共静态X509Certificate2加载证书(IConfiguration配置,字符串certificateName)
{
字符串vaultUrl=config[“Vault:Url”]??“”;
字符串clientId=config[“Vault:clientId”];
字符串tenantId=config[“Vault:tenantId”];
字符串secret=config[“Vault:secret”];
WriteLine($“正在从Azure密钥库加载证书“{certificateName}”);
var-credentials=new-ClientSecretCredential(tenantId:tenantId,clientId:clientId,clientSecret:secret);
var certClient=new CertificateClient(新Uri(Vault URL)、凭据);
var secretClient=新secretClient(新Uri(Vault URL)、凭据);
var cert=GetCertificateAsync(certClient、secretClient、certificateName);
Console.WriteLine(“已加载证书”);
返回证书;
}
///
///获取证书的助手方法
///
///来源https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs
///
///
///
///
///
专用静态X509Certificate2 GetCertificateAsync(CertificateClient CertificateClient,
SecretClient SecretClient,
字符串(名称)
{
KeyVault CertificateWithPolicy certificate=certificateClient.GetCertificate(certificateName);
//如果私钥不可导出,则返回仅包含公钥的证书。
if(certificate.Policy?.Exportable!=true)
{
返回新的X509Certificate2(certificate.Cer);
}
//解析机密ID和版本以检索私钥。
string[]segments=certificate.SecretId.AbsolutePath.Split(“/”,StringSplitOptions.RemoveEmptyEntries);
如果(segments.Length!=3)
{
抛出新的InvalidOperationException($“段数不正确:{segments.Length},URI:{certificate.SecretId}”);
}
字符串secretName=段[1];
字符串secretVersion=段[2];
KeyVaultSecret-secret=secretClient.GetSecret(secretName,secretVersion);
//对于PEM,您需要提取base64编码的消息体。
//.NET 5.0预览版引入了System.Security.Cryptography.PemEncoding类以简化此操作。
if(“application/x-pkcs12”.Equals(secret.Properties.ContentType、StringComparison.InvariantCultureInogoreCase))
{
字节[]pfx=Convert.FromBase64String(secret.Value);
返回新的X509Certificate2(pfx);
}
抛出新的NotSupportedException($“仅支持PKCS#12。找到的内容类型:{secret.Properties.ContentType}”);
}
}