使用存储在证书存储中并使用 BouncyCastle 和 C# 标记为不可导出的 X509Certificate2 的私
本文关键字:存储 的私 X509Certificate2 记为 证书 BouncyCastle | 更新日期: 2023-09-27 18:34:26
我正在尝试使用bouncycastle构建一个小型证书颁发机构。
证书是使用 BouncyCastle 生成的,与私钥一起存储在当前用户的证书存储中。
但是,出于安全原因,密钥被标记为不可导出。我知道规避这一点很容易,但它意味着一个额外的障碍。
现在,如果应该对新证书进行签名,我需要访问证书的私钥。我尝试使用此代码:
X509Certificate2 caCert = GetRootKey(); // Fetches the certificate from the store
AsymmetricCipherKeyPair caPrivKey = DotNetUtilities.GetKeyPair(caCert.PrivateKey);
不幸的是,我得到了一个加密异常 - 因为私钥被标记为不可导出,这就是我的意图;-(
我的问题是:如何在不访问证书的情况下使用证书的私钥?我很确定我错过了一些重要的点,因为在没有任何方法可以访问的情况下存储私钥没有任何意义......
我现在正在搜索几个小时,但在stackoverflow或Google上找不到任何东西。
谁能告诉我我根本做错了什么?有没有另一种签名的方法?!
提前感谢!
克里斯
简短的回答:不是与BouncyCastle。
您不能将密钥放入BouncyCastle(或OpenSSL或任何其他(库中并从那里使用它。如果不绕过"禁止导出"标志并将其导出,就无法做到这一点,正如您指出的那样,如果您是管理员并且具有必要的 Fu,这是可能的。
您可以做的是获取密钥的 CryptoAPI 句柄,并要求 CryptoAPI 为你执行签名或加密操作。DotNet加密类封装了CyptoAPI。
因此,从本质上讲,您需要的是有关如何使用DotNet加密类的教程。有很多,例如,这篇 MSDN 杂志文章有一个数字签名的示例,大约四分之三
:http://msdn.microsoft.com/en-us/magazine/cc163454.aspx
这个问题正是我所需要的,但答案有一个链接不起作用。搜索了一段时间后,我发现了使用 NCrypt 库的实现:https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/security/cryptoapi/CertSign/CPP/Sign.cpp
不幸的是,这是用 C 编写的,而我需要一个 C# 实现。因此,我创建了这个可以在托管 C# 代码中使用的 C++''CLI 包装器 (SignerWrapper.h(:https://github.com/DmitriNymi/StoreCertificateSigner
我还编写了一个自定义的ISignatureFactory用于BouncyCastle,请参阅同一存储库中的PksAsn1SignatureFactory.cs。 它只是为了创建我的自定义ISigner(我的类:PksEcdsaSigner(。
现在你可以将 PksAsn1SignatureFactory 作为 ISignatureFactory 传递给 BouncyCastle 以创建新证书,例如,请参阅单元测试 PksAsn1SignatureFactoryTest.cs - 显式调用 ISignatureFactory和证书颁发者测试.cs - 使用帮助程序类创建由存储中另一个证书中的私钥签名的新证书
我不想将C++/CLI添加到我的项目中,因此想出了一个使用System.Security.Pkcs.SignedCms的纯c#解决方案。 与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);
}
}
}
它可以用作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;
}