不使用外部库生成自签名证书

3

我很想知道是否有一种简单的方法来创建一个自签名证书,类似于下面的New-SelfSignedCertificate命令(其他提供商也可以)。我希望只使用.NET库,而不需要P/Invoke或外部库(如Bouncy Castle),也不需要从应用程序调用PowerShell。

New-SelfSignedCertificate -DnsName $certificateName -CertStoreLocation $certificateStore -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -NotAfter $certificateNotAfter

我想最简单的选择就是调用PowerShell或使用Nuget库,例如Bouncy Castle,如果没有外部设施就无法实现?虽然感觉如果我足够了解如何构建证书,就可以创建一个字节数组模板或类似的东西,并在X509Certificate2构造函数中使用它。
看起来需要执行以下操作:
public X509Certificate2 GenerateCertificate(string fileName, string password, string subjectName, StoreName storeName, DateTime endDate, DateTime notAfter, string provider = "Microsoft Enhanced RSA and AES Cryptographic Provider")
{
    //Could provider be taken from https://dev59.com/yqDia4cB1Zd3GeqPMv94?            
    var newCertificate = new X509Certificate2(fileName, password, X509KeyStorageFlags.Exportable);

     /*
      # The following creates a self-signed certificate with one year of running time.
     $currentDate = Get-Date
     $certificateEndDate = $currentDate.AddYears(1)
     $certificateNotAfter = $certificateEndDate.AddYears(1)

     $certificateName = "https://www.test.com/test"
     $certificateStore = "Cert:\LocalMachine\My"
     New-SelfSignedCertificate -DnsName $certificateName -CertStoreLocation $certificateStore -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -NotAfter $certificateNotAfter
     */
}

我发现了几个更多的选项:

Steve Syfuhs的博客文章在.NET中创建授权签名和自签名证书以及另一篇使用Mono扩展的SO帖子,Mono.Security无法设置多个KeyUsages。除了已经讨论过的选择外,它就是“类似于这样”。


如果您提供的是 PowerShell 代码,而又不使用 PowerShell 调用,这还有什么意义呢? - Crypt32
你最终想要实现什么?你在寻找PS解决方案吗? - Ranadip Dutta
似乎在 System.ServiceModel 中有一个 SelfSignedCertificate 类,但由于它是内部的,所以您只能通过反射来获取它 - 或者复制其代码,但此时会再次 P/Invoke。从它的存在来看,在 .NET 中更直接地创建一个可能没有更简单的方法。 - Damien_The_Unbeliever
@Crypt32 我想知道在.NET中是否有一种类似于提供的PowerShell代码的方法,而不需要额外的库或从.NET应用程序调用PowerShell脚本。Ranadip 我想使用这些参数创建一个自签名证书。最终我想将该证书与Azure一起使用,那个PowerShell片段是我创建具有主体的应用程序的更大脚本的一部分。 - Veksi
我认为除了创建一个之外,没有其他选择。请检查@Damien_The_Unbeliever提供的评论。如果您可以从服务模型中提取它。 - Ranadip Dutta
没错,显然有Mono扩展可以做到这一点,例如 https://stackoverflow.com/questions/40287336/mono-security-wont-set-multiple-keyusages。但是它对于 .NET 没有帮助,或者可以看看 https://syfuhs.net/2014/02/09/creating-authority-signed-and-self-signed-certificates-in-net/。 - Veksi
1个回答

12

在任何发布版本的.NET中都没有创建证书的功能。

.NET Core 2.0 (尚未发布,但应该很快就会发布)和.NET Framework 4.7.2通过CertificateRequest(一种可以自签名证书、链式签名证书或PKCS#10证书/认证签名请求的API)添加了此功能。

PowerShell 命令将三个内容组合在一起:

  • 密钥存储参数(CSP)
  • 证书创建
  • 证书存储(要添加证书的存储区)

创建一个“基本”的用于 localhost TLS 服务器身份验证的自签名证书:

X509Certificate2 certificate = null;
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName("localhost");

// New .NET Core Create(int) method.  Or use
// rsa = RSA.Create(), rsa.KeySize = newRsaKeySize,
// or (on .NET Framework) new RSACng(newRsaKeySize)
using (RSA rsa = RSA.Create(newRsaKeySize))
{
    var certRequest = new CertificateRequest(
        $"CN=localhost",
        rsa,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1);

    // Explicitly not a CA.
    certRequest.CertificateExtensions.Add(
        new X509BasicConstraintsExtension(false, false, 0, false));

    certRequest.CertificateExtensions.Add(
        new X509KeyUsageExtension(
            X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
            true));

    // TLS Server EKU
    certRequest.CertificateExtensions.Add(
        new X509EnhancedKeyUsageExtension(
            new OidCollection
            {
                new Oid("1.3.6.1.5.5.7.3.1"),
            },
            false));

    // Add the SubjectAlternativeName extension
    certRequest.CertificateExtensions.Add(sanBuilder.Build());

    DateTimeOffset now = DateTimeOffset.UtcNow;
    certificate = certRequest.CreateSelfSigned(now, now.AddDays(365.25));
}

创建的证书具有短暂的私钥,目前未写入磁盘。如果您不关心使用哪个密钥存储提供程序,那么在此时导出证书(和私钥)为 PFX/PKCS#12 是最好的选择,然后使用 PersistKeySet(并且加上 Exportable,因为您需要它),将导入的副本添加到您选择的 X509Store 中。

如果您关心密钥存储提供程序(在您的情况下是 CAPI CSP),或者想避免导出/导入,您可以使用预先持久化的密钥来创建它。因此,您需要将 RSA.Create(newRsaKeySize) 替换为

CAPI:

var cspParameters = new CspParameters(
    /* PROV_RSA_AES */ 24,
    "Microsoft Enhanced RSA and AES Cryptographic Provider",
    Guid.NewGuid().ToString());

using (RSA rsa = new RSACryptoServiceProvider(newRsaKeySize, cspParameters))

CNG:

var keyParams = new CngKeyCreationParameters();
keyParams.Parameters.Add(
    new CngProperty(
        "Length",
        BitConverter.GetBytes(newRsaKeySize),
        CngPropertyOptions.Persist));

using (CngKey rsaKey = CngKey.Create(CngAlgorithm.RSA, Guid.NewGuid().ToString(), keyParams)
using (RSA rsa = new RSACng(rsaKey))

然后按照常规方式将其添加到打开的X509Store实例中。

如果我足够了解如何构建证书,似乎可以创建一个字节数组模板或类似的东西,并在X509Certificate2构造函数中使用它。

没错。但这有点棘手。您需要学习ASN.1 (ITU-T X.680)、DER (ITU-T X.690) 和 X.509(任选RFC 5280ITU-T X.509)。如果您想创建与私钥配对的证书,那么您还需要学习PFX/PKCS#12 (RFC 7292,尽管仅限于一些旧选项,除非您只使用Win10),而这也需要一些先决知识。

我认为这些都是有趣的东西,值得学习...但是当我开始在白板上涂鸦DER时,我的同事会向我投来奇怪的眼神,所以我可能不能代表普通开发者对“有趣”的看法。


这是我很久以来接触到的最具教育性的内容。这真的非常好,可能会在未来几年回答很多人的问题。我非常感谢你花时间写这篇文章,谢谢! - Veksi

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