从证书和密钥创建X509Certificate2,而不生成PFX文件

19

过去我一直通过导出带有密码的PFX证书来创建安全的TcpListener,但想知道是否可以跳过此步骤。

我没有使用商业SSL证书,而是拥有一个根CA,用于发行服务器证书。在C#中托管TcpListener时,这些服务器证书需要额外的步骤(我猜是因为未使用CSR)...但如果我确实有私钥和OpenSSL生成/使用的证书呢?

sslCertificate = new X509Certificate2("myExportedCert.pfx", "1234");

这很棒,但是我需要使用openssl命令从证书和私钥创建一个pfx文件,然后编写一些密码,最后将此密码包含在我的代码中。

我想知道这一步是否非常必要。是否有办法从证书创建X509Certificate2,然后应用私钥。构造函数参数只允许使用证书部分,但加密失败,因为没有私钥。

另外,我不想依赖OpenSSL或IIS来导出pfx....似乎很笨拙。

理想情况下:

sslCertificate = new X509Certificate2("myCert.crt");
sslCertificate.ApplyPrivateKey(keyBytes) // <= or "private.key" or whatever

sslStream.AuthenticateAsServer(sslCertificate, false, SslProtocols.Default, false);

我正在使用 .net 4,如果有任何区别的话。 - Conrad
嗨,康拉德,我知道这已经过时了,但我遇到了完全相同的问题,我们能聊一下并解决这个问题吗?谢谢。 - Clint
1
@Clint,我把我的解决方案留下了,其中包含OpenSSL调用。它可以毫无问题地工作,虽然是一个外部应用程序(不够干净或纯粹),但它能够正常运行! - Conrad
2个回答

32

你提出了几个不同的要求,难度不同。

将私钥附加到证书

从 .NET Framework 4.7.2 或 .NET Core 2.0 开始,您可以结合证书和密钥。 它不会修改证书对象,而是生成一个新的证书对象,该对象知道有关密钥的信息。

using (X509Certificate2 pubOnly = new X509Certificate2("myCert.crt"))
using (X509Certificate2 pubPrivEphemeral = pubOnly.CopyWithPrivateKey(privateKey))
{
    // Export as PFX and re-import if you want "normal PFX private key lifetime"
    // (this step is currently required for SslStream, but not for most other things
    // using certificates)
    return new X509Certificate2(pubPrivEphemeral.Export(X509ContentType.Pfx));
}

在.NET Framework(但不是.NET Core)中,如果您的私钥是RSACryptoServiceProviderDSACryptoServiceProvider,则可以使用cert.PrivateKey = key,但这会产生复杂的副作用,不建议使用。

加载私钥

这个问题较难,除非您已经解决了它。

在大多数情况下,答案在Digital signature in c# without using BouncyCastle中,但如果您能迁移到.NET Core 3.0,事情就变得容易得多。

PKCS#8 PrivateKeyInfo

从.NET Core 3.0开始,您可以相对简单地执行此操作:

using (RSA rsa = RSA.Create())
{
    rsa.ImportPkcs8PrivateKey(binaryEncoding, out _);
    // do stuff with the key now
}

当然,如果您有一个PEM文件,需要通过提取BEGIN和END定界符之间的内容并通过Convert.FromBase64String运行来"去除PEM",以便获得binaryEncoding

PKCS#8 EncryptedPrivateKeyInfo

从.NET Core 3.0开始,您可以相对简单地执行此操作:

using (RSA rsa = RSA.Create())
{
    rsa.ImportEncryptedPkcs8PrivateKey(password, binaryEncoding, out _);
    // do stuff with the key now
}

(如上所述,如果它是PEM格式,您需要首先进行"去PEM化")。

PKCS#1 RSAPrivateKey

从.NET Core 3.0开始,您可以相对简单地完成此操作:

using (RSA rsa = RSA.Create())
{
    rsa.ImportRSAPrivateKey(binaryEncoding, out _);
    // do stuff with the key now
}

(如果是PEM,则为“de-PEM”)相同。


3
谢谢您,@bartonjs。如果我使用.NET Core,我会使用这个方法。现在,我只需要使用Process()函数来获取OpenSSL制作.pfx文件,因为我手头上已经有.crt和.key文件了。不过,似乎在.NET4中没有这个功能,这似乎是一个非常基本的要求,用于托管具有CA、证书和私钥的安全TCP服务。 - Conrad
我对安全方面不是很熟悉。从一个.key文件中创建privateKey需要哪些步骤? - Nican
你不直接使用 X509Certificate2.CreateFromPem 有什么原因吗?你提到了需要将所有东西都解码,但是该类支持 PEM 编码的证书和密钥。还有 RSA.ImportFromPemRSA.ImportFromEncryptedPem 可以直接导入密钥。我问这些问题只是因为在阅读了你的答案后,我花费了大量时间构建自己的解码逻辑,然后才发现了这些方法的文档。 - b4ux1t3
@b4ux1t3 只是时间的线性本质。这个答案是在任何这些方法被创建之前编写的。 - bartonjs
啊,你说得对,看起来这些是在.NET 5中添加的!我的错误。 - b4ux1t3

-5
最终我做了这个,它运行良好:
...
if (!File.Exists(pfx)) {
    // Generate PFX
    string arguments = "openssl pkcs12 -export -in " + certPath + "" + certFile + ".crt -inkey " + certPath + "" + certFile + ".key -out " + certPath + "" + certFile + ".pfx -passout pass:" + pfxPassword;
    ProcessStartInfo opensslPsi = new ProcessStartInfo("sudo", arguments);
    opensslPsi.UseShellExecute = false;
    opensslPsi.RedirectStandardOutput = true;
    using (Process p = Process.Start(opensslPsi)) {
        p.WaitForExit();
    }
    // Set Permission
    ProcessStartInfo chmodPsi = new ProcessStartInfo("sudo", "chmod 644 " + certPath + "" + certFile + ".pfx");
    chmodPsi.UseShellExecute = false;
    chmodPsi.RedirectStandardOutput = true;
    using (Process p = Process.Start(chmodPsi)) {
        p.WaitForExit();
    }
}
sslCertificate = new X509Certificate2(pfx, pfxPassword);
...

6
这恰恰是问题要避免的。 - MFedatto

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