为 Identity Server 4 生成自签名证书时出现“X509 证书没有私钥”的错误。

3

这是我想实现的目标。我们正在Kubernetes上开发一个微服务应用程序。其中一个微服务是IdentityServer实例。最初,我想在Docker上本地测试解决方案以确保其正常工作。为此,我想将证书粘贴到appsettings.json中。最终,这个值将被Kubernetes secret替换。在我的启动类中,我尝试加载我的证书:

 services.AddIdentityServer()
         .AddSigningCredential(GetIdentityServerCertificate())
         .AddConfigurationStore(...

    private X509Certificate2 GetIdentityServerCertificate()
    {
        var clientSecret = Configuration["Certificate"];
        var pfxBytes = Convert.FromBase64String(clientSecret);
        var certificate = new X509Certificate2(pfxBytes);
        return certificate;
    }

证书是我使用openssl生成的:

openssl req –newkey rsa:2048 –nodes –keyout XXXXX.key –x509 –days 365 –out XXXXX.cer

openssl pkcs12 –exportin XXXX.cer –inkey XXXX.key –out XXXX.pfx

然后我通过以下方式获取证书:

openssl pkcs12 -in XXXX.pfx -info -nokeys

-----BEGIN CERTIFICATE-----
i take this content and paste into appconfig.json
-----END CERTIFICATE-----

当我进行调试时,结果显示: System.InvalidOperationException:'X509证书没有私钥。'

1
你只提取了pkcs#12中的证书内容(以base64格式)。你应该将整个pkcs#12内容以DER编码作为pfxBytes - pepo
当将PKCS#12加载到X509Certificate2构造函数中时,您还需要提供密码作为构造函数的第二个参数。 - pepo
我尝试最初加载整个证书,但是遇到了不同的错误。我已经创建了另一个问题来解决这个问题:https://dev59.com/xVQJ5IYBdhLWcg3wl3Ba 我不确定如何处理DER编码。我应该将证书编码为DER字符串,然后在我的C#类中使用与FromBase64String方法不同的方法从JSON中读取它吗? - skyrunner
您是使用openssl创建的。如果我没记错,它应该已经是DER编码(原始字节)。 - pepo
你解决了吗? - Charlie
这与第一个评论中描述的一样。最终,我决定不从字符串值加载证书,而是加载.pfx文件,自那以后就没有遇到任何问题了。 - skyrunner
1个回答

2

这里是一个在 .net core 中可行的示例:

使用 openssl 工具。打开终端并输入以下命令:

openssl genrsa -out private.pem 2048 
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

这将会创建2个文件。

在我们能够在.NET Core应用程序中使用RSA密钥之前,还有一件事情需要做。我们需要将它们转换为XML格式。使用"RSA PEM to XML Converter"进行转换。 可以在此处完成:在线RSA密钥转换器 在将XML复制到private-rsa-key.xml和public-rsa-key.xml文件之前,使用XML格式化工具进行格式化。

私钥仅适用于负责生成令牌的服务。不能在项目之外共享该密钥。

Startup.cs :

public class Startup
{
    public IConfiguration Configuration { get; }
    private SigningCredentials _signingCredentials;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // other code
        services
                .AddIdentityServer()
                .AddSigningCredential(_signingCredentials)
                .AddInMemoryApiResources(_identityConfig.GetApiResources())
                .AddInMemoryClients(_identityConfig.GetClients());
        // other code
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMvc();
        app.UseIdentityServer();
    }

    private void InitializeRsaKey()
    {
        try
        {
            RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(2048);
            var rsaParametersPrivate = RSAExtensions.RSAParametersFromXmlFile(Configuration.GetSection("JwtSettings:rsaPrivateKeyXml").Value);
            rsaProvider.ImportParameters(rsaParametersPrivate);
            var securityKey = new RsaSecurityKey(rsaProvider);
            _signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256);
        }
        catch(Exception ex)
        {
            throw new Exception("Identity Server RSA Key initialization failed. " + ex.ToString());
        }
    }
}

RSAExtensions类:

public static class RSAExtensions
{
    /// <summary>
    /// Gets RSA Parameters from XML file.
    /// </summary>
    /// <param name="xmlFilePath">The XML file path.</param>
    /// <returns>RSAParameters.</returns>
    /// <exception cref="Exception">Invalid XML RSA key.</exception>
    public static RSAParameters RSAParametersFromXmlFile(string xmlFilePath)
    {
        RSAParameters parameters = new RSAParameters();

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(File.ReadAllText(xmlFilePath));

        if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
        {
            foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
            {
                switch (node.Name)
                {
                    case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                }
            }
        }
        else
        {
            throw new Exception("Invalid XML RSA key.");
        }

        return parameters;
    }
}

在 appsettings.json 中添加以下内容:

"JwtSettings": {
    "rsaPrivateKeyXml": "RSAKeys/private-rsa-key.xml",
},

我不确定您是否可以在appsettings.json中存储原始的XML。如果不能,您可以编写一个帮助方法将pem转换为xml,并将原始的pem内容存储在配置变量中。

我在GitHub上找到了一个示例项目,可能会对您进行转换有所帮助:Rsa-Dotnet-Core


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接