简体   繁体   English

使用存储在证书存储中并使用 BouncyCastle 和 C# 标记为不可导出的 X509Certificate2 的私钥

[英]Use PrivateKey of a X509Certificate2 stored in Certificate Store and marked as non-exportable using BouncyCastle and C#

I'm trying to build a small certificate authority using bouncycastle.我正在尝试使用 bouncycastle 构建一个小型证书颁发机构。

The certificate is generated by using BouncyCastle and is stored in the Certificate Store of the current user, along with the private key.该证书是使用 BouncyCastle 生成的,并与私钥一起存储在当前用户的证书存储中。

However for security reasons the key is marked as non-exportable.但是出于安全原因,密钥被标记为不可导出。 I know that it is easy to circumvent this, but it's meant to be an additional barrier.我知道规避这一点很容易,但这意味着要成为一个额外的障碍。

Now if a new certificate should be signed, I need to access the private key of the certificate.现在,如果应该签署新证书,我需要访问证书的私钥。 I tried using this code:我尝试使用此代码:

X509Certificate2 caCert = GetRootKey(); // Fetches the certificate from the store
AsymmetricCipherKeyPair caPrivKey = DotNetUtilities.GetKeyPair(caCert.PrivateKey);

Unfortunately I get an CryptographicException - because the private key is marked as not exportable, and this is what I intended ;-)不幸的是,我收到了 CryptographicException - 因为私钥被标记为不可导出,这就是我的意图;-)

My question is: How can I use the private key of the certificate without accessing it?我的问题是:如何在不访问证书的情况下使用证书的私钥? I'm pretty sure I'm missing some important point as it would not make any sense to have a private key stored without any way to access it...我很确定我错过了一些重要的点,因为在没有任何方式访问它的情况下存储私钥是没有任何意义的......

I'm searching now for hours for this, but don't find anything on stackoverflow or Google.我现在为此搜索了几个小时,但在 stackoverflow 或 Google 上找不到任何内容。

Can anyone tell me please what I do fundamentally wrong?谁能告诉我我做错了什么? Is there another approach to sign something?!有没有另一种签署的方法?!

Thanks in advance!提前致谢!

Chris克里斯

Short answer: Not with BouncyCastle.简短回答:BouncyCastle 不适用。

You can't put the key into the BouncyCastle (or OpenSSL or any other) library and use it from there.您不能将密钥放入 BouncyCastle(或 OpenSSL 或任何其他)库并从那里使用它。 There is no way to do this without circumventing the "no export" flag and exporting it, which as you point out is possible if you are an administrator and have the requisite Fu.如果不绕过“禁止导出”标志并将其导出,就无法做到这一点,正如您所指出的,如果您是管理员并拥有必要的 Fu,则这是可能的。

What you can do is get a CryptoAPI handle to the key, and ask CryptoAPI to do the signing or encryption operations for you.您可以做的是获取密钥的 CryptoAPI 句柄,并要求 CryptoAPI 为您执行签名或加密操作。 The DotNet crypto classes encapsulate CyptoAPI. DotNet 加密类封装了 CyptoAPI。

So essentially what you need is a tutorial on how to use the DotNet crypto classes.所以基本上你需要的是一个关于如何使用 DotNet 加密类的教程。 There are many, for example this MSDN Magazine article has an example of digital signatures about three quarters of the way down:有很多,例如这篇 MSDN 杂志文章有一个大约四分之三的数字签名示例:

http://msdn.microsoft.com/en-us/magazine/cc163454.aspx http://msdn.microsoft.com/en-us/magazine/cc163454.aspx

This question is exactly what I need, but the answer has a link that is not working.这个问题正是我所需要的,但答案有一个无效的链接。 After searching for some time I found this implementation using NCrypt library: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/security/cryptoapi/CertSign/CPP/Sign.cpp搜索了一段时间后,我使用 NCrypt 库找到了这个实现: https : //github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/security/cryptoapi/CertSign/CPP/Sign.cpp

Unfortunately this is written in C, while I need a C# implementation.不幸的是,这是用 C 编写的,而我需要一个 C# 实现。 So I created this C++\\CLI wrapper (SignerWrapper.h) that can be used in managed C# code: https://github.com/DmitriNymi/StoreCertificateSigner所以我创建了这个 C++\\CLI 包装器(SignerWrapper.h),可以在托管 C# 代码中使用: https : //github.com/DmitriNymi/StoreCertificateSigner

I also wrote a custom ISignatureFactory to be used in BouncyCastle, see PksAsn1SignatureFactory.cs in the same repo.我还编写了一个用于 BouncyCastle 的自定义 ISignatureFactory,请参阅同一个 repo 中的 PksAsn1SignatureFactory.cs。 It is there only to have my custom ISigner to be created (my class: PksEcdsaSigner ).只需要创建我的自定义 ISigner(我的课程: PksEcdsaSigner )。

Now you can pass PksAsn1SignatureFactory as ISignatureFactory to BouncyCastle for creating new certificates for example, see unit test PksAsn1SignatureFactoryTest.cs - explicit call to ISignatureFactory and CertificateIssuerTest.cs - using helper class to create new certificates signed by private keys from another certificate in the store现在,您可以将 PksAsn1SignatureFactory 作为 ISignatureFactory 传递给 BouncyCastle 以创建新证书,例如,请参阅单元测试 PksAsn1SignatureFactoryTest.cs - 显式调用 ISignatureFactory 和 CertificateIssuerTest.cs - 使用帮助程序类从商店中的另一个证书创建由私钥签名的新证书

I didn't want to add C++/CLI to my project, and so came up with a pure-c# solution using System.Security.Pkcs.SignedCms.我不想将 C++/CLI 添加到我的项目中,因此想出了一个使用 System.Security.Pkcs.SignedCms 的纯 C# 解决方案。 Similar to Searcher's answer, this provides an implementation of ISignatureFactory that can be used to sign certificates in BouncyCastle.与 Searcher 的回答类似,这提供了 ISignatureFactory 的实现,可用于在 BouncyCastle 中签署证书。

using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Crypto;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

public class WindowsSignatureFactory : ISignatureFactory
{
    public AlgorithmIdentifier Algorithm { get; }
    public X509Certificate2 Cert { get; }

    public object AlgorithmDetails => Algorithm;

    public WindowsSignatureFactory(AlgorithmIdentifier algorithm,
            X509Certificate2 cert)
    {
        Algorithm = algorithm;
        Cert = cert;
    }

    public IStreamCalculator CreateCalculator()
    {
        return new WindowsStreamCalculator(Algorithm, Cert);
    }

    private class WindowsStreamCalculator : IStreamCalculator
    {
        public AlgorithmIdentifier Algorithm { get; }
        public X509Certificate2 Cert { get; }
        private MemoryStream MemoryStream { get; } = new MemoryStream();
        public Stream Stream => MemoryStream;

        public WindowsStreamCalculator(AlgorithmIdentifier algorithm,
                X509Certificate2 cert)
        {
            Algorithm = algorithm;
            Cert = cert;
        }

        public object GetResult()
        {
            var signer = new System.Security.Cryptography.Pkcs.CmsSigner(Cert);
            // This maps e.g. "rsa with sha256" (OID 1.2.840.113549.1.1.11) to
            // "sha256" (OID 2.16.840.1.101.3.4.1)
            var hashAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(Algorithm);
            var hashOid = hashAlgorithm.Algorithm.Oid;
            signer.DigestAlgorithm = new System.Security.Cryptography.Oid(hashOid);

            var dataToSign = MemoryStream.ToArray();
            var info = new System.Security.Cryptography.Pkcs.ContentInfo(dataToSign);
            var cms = new System.Security.Cryptography.Pkcs.SignedCms(info, true);
            cms.ComputeSignature(signer, true);

            // This is the tricky part: usually you would call cms.Export() to get a signed
            // message, but we only want the signature
            var cmsSigner = cms.SignerInfos
                .Cast<System.Security.Cryptography.Pkcs.SignerInfo>().ToArray().Single();
            var signatureData = cmsSigner.GetSignature();

            return new SimpleBlockResult(signatureData);
        }
    }
}

It can be used as a substitute for Asn1SignatureFactory as follows:它可以用作 Asn1SignatureFactory 的替代品,如下所示:

public static Org.BouncyCastle.X509.X509Certificate SignCertificate(
        X509V3CertificateGenerator generator,
        X509Certificate2 nonExportableSigningCertificate)
{
    var signer = new WindowsSignatureFactory(
        new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha256),
        nonExportableSigningCertificate);

    var signed = generator.Generate(signer);

    // Make sure it worked
    var signingCertBouncy = DotNetUtilities.FromX509Certificate(nonExportableSigningCertificate);
    signed.Verify(signingCertBouncy.GetPublicKey());

    return signed;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM