如何使用 Duende Identity Server v7 的 RSA 密钥

问题描述 投票:0回答:1

我的思考过程是:

  1. 在 Azure Key Vault --> 密钥中创建 RSA 密钥
  2. 使用 KeyClient 检索密钥
  3. 将密钥传递给 AddSigningCredential

这是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 密钥

enter image description here

这是抛出的异常:(

[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(ReadOnlySpan
1 data, Span
1 目的地,HashAlgorithmName hashAlgorithm,RSASignaturePadding 填充,Int32& bytesWritten) 在 Microsoft.IdentityModel.Tokens.AmetryAdapter.SignUsingSpanRsa(ReadOnlySpan
1 data, Span
1 目标,Int32& bytesWritten) 在 Microsoft.IdentityModel.Tokens.AmetryAdapter.SignUsingSpan(ReadOnlySpan
1 data, Span
1 目标,Int32& bytesWritten) 在 Microsoft.IdentityModel.Tokens.AmetrySignatureProvider.Sign(ReadOnlySpan
1 input, Span
1 签名,Int32& bytesWritten) 在 Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateSignature(ReadOnlySpan
1 data, Span
1 目标、SigningCredentials 签名Credentials、Int32& bytesWritten)

我还尝试使用 Putty 密钥生成器生成 RSA 密钥并上传该密钥,但抛出了相同的异常。

cryptography azure-keyvault duende-identity-server
1个回答
0
投票

我以前见过这个问题,当你获取密钥时,你只得到公钥,而不是私钥。要获取私钥,您需要将其作为秘密获取。当我将密钥存储在 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}");
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.