我的思考过程是:
这是C#代码
string keyVaultUrl = Configuration.GetValue<string>("AzureSettings:KeyVaultUrl");
var identityServerConfig = Configuration.GetSection("IdentityServer:Key");
string keyName = identityServerConfig["Name"];
var client = new KeyClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
KeyVaultKey keyVaultKey = client.GetKey(keyName);
// Ensure the key is an RSA key
if (keyVaultKey.KeyType != KeyType.Rsa && keyVaultKey.KeyType != KeyType.RsaHsm)
{
throw new InvalidOperationException("The key retrieved from Key Vault is not an RSA key.");
}
// Convert KeyVault's RSA key to RsaSecurityKey
var rsa = new RSAParameters
{
Modulus = keyVaultKey.Key.N,
Exponent = keyVaultKey.Key.E
};
var rsaCryptoServiceProvider = RSA.Create();
rsaCryptoServiceProvider.ImportParameters(rsa);
RsaSecurityKey key = new(rsaCryptoServiceProvider);
identityServiceBuild.AddSigningCredential(key, IdentityServerConstants.RsaSigningAlgorithm.RS256);
这是在 Azure Key Vault 中创建 RSA 密钥
这是抛出的异常:(
[01:51:18 信息] UnhandledExceptionEvent { 详细信息:“System.Security.Cryptography.CryptographyException:未知错误(0xc100000d) 在 System.Security.Cryptography.RSABCrypt.TrySignHash(ReadOnlySpan
1 hash, Span
1 目的地,HashAlgorithmName hashAlgorithm,RSASignaturePadding 填充,Int32& bytesWritten)
在 System.Security.Cryptography.RSA.TrySignData(ReadOnlySpan1 data, Span
1 目的地,HashAlgorithmName hashAlgorithm,RSASignaturePadding 填充,Int32& bytesWritten)
在 Microsoft.IdentityModel.Tokens.AmetryAdapter.SignUsingSpanRsa(ReadOnlySpan1 data, Span
1 目标,Int32& bytesWritten)
在 Microsoft.IdentityModel.Tokens.AmetryAdapter.SignUsingSpan(ReadOnlySpan1 data, Span
1 目标,Int32& bytesWritten)
在 Microsoft.IdentityModel.Tokens.AmetrySignatureProvider.Sign(ReadOnlySpan1 input, Span
1 签名,Int32& bytesWritten)
在 Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateSignature(ReadOnlySpan1 data, Span
1 目标、SigningCredentials 签名Credentials、Int32& bytesWritten)
我还尝试使用 Putty 密钥生成器生成 RSA 密钥并上传该密钥,但抛出了相同的异常。
我以前见过这个问题,当你获取密钥时,你只得到公钥,而不是私钥。要获取私钥,您需要将其作为秘密获取。当我将密钥存储在 AKV 中时,我将它们上传并存储为证书而不是原始密钥,当您尝试获取证书时,您只能获得公钥而不能获得私钥。那么也许您也看到同样的问题?
下面是我几年前编写的从 Azure Key Vault 下载密钥的代码(密钥存储为证书)
namespace Infrastructure
{
/// <summary>
/// Extension methods and helper methods to access the Azure Key Vault
///
/// Written by Tore Nestenius, https://tn-data.se , https://nestenius.se
/// </summary>
public static class KeyVaultExtensions
{
/// <summary>
/// Add Azure Key vault to the ASP.NET Configuration system
/// </summary>
/// <param name="config"></param>
public static void AddAzureKeyVaultSupport(this IConfigurationBuilder config)
{
var builtConfig = config.Build();
string vaultUrl = builtConfig["Vault:Url"] ?? "";
string clientId = builtConfig["Vault:ClientId"] ?? "";
string tenantId = builtConfig["Vault:TenantId"] ?? "";
string secret = builtConfig["Vault:ClientSecret"] ?? "";
Console.WriteLine("Adding AzureKeyVault Support");
CheckIfValueIsProvided(vaultUrl, nameof(vaultUrl), sensitiveValue: false);
CheckIfValueIsProvided(clientId, nameof(clientId), sensitiveValue: true);
CheckIfValueIsProvided(tenantId, nameof(tenantId), sensitiveValue: true);
CheckIfValueIsProvided(secret, nameof(secret), sensitiveValue: true);
config.AddAzureKeyVault(new Uri(vaultUrl), new ClientSecretCredential(tenantId, clientId, secret));
Console.WriteLine("- Vault configured");
}
private static void CheckIfValueIsProvided(string value, string parameterName, bool sensitiveValue)
{
if (string.IsNullOrEmpty(value))
{
Console.WriteLine($"Fatal: - {parameterName} not found");
throw new Exception($"Fatal: {parameterName} not found");
}
else
{
//To assist troubleshooting in production, we print out the first character for each config value
if (sensitiveValue)
Console.WriteLine($"- {parameterName}: {value.Substring(0, 1)}...");
else
Console.WriteLine($"- {parameterName}: {value}");
}
}
/// <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}");
}
}
}