在 .NET 4 中在 Root 中插入证书(带私钥),本地计算机证书存储失败

Posted

技术标签:

【中文标题】在 .NET 4 中在 Root 中插入证书(带私钥),本地计算机证书存储失败【英文标题】:Inserting Certificate (with privatekey) in Root, LocalMachine certificate store fails in .NET 4 【发布时间】:2011-04-07 05:44:50 【问题描述】:

我在本地机器的根证书存储中插入带有私钥的新 CA 证书时遇到问题。

会发生这样的事情:

//This doesn't help either.
new StorePermission (PermissionState.Unrestricted)  Flags = StorePermissionFlags.AddToStore .Assert();
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
privkey.PersistKeyInCsp = true;
//This shouldn't be necessary doesn't make a difference what so ever.
RSACryptoServiceProvider.UseMachineKeyStore = true;
cert.PrivateKey = privkey;
store.Open (OpenFlags.MaxAllowed);
store.Add (cert);
store.Close ();

证书被插入,看起来很漂亮:(看!)

注意:是说它有一个私钥。

所以你会说可以通过FindPrivateKey 找到它

C:\Users\Administrator\Desktop>FindPrivateKey.exe Root LocalMachine -t "54 11 b1 f4 31 99 19 d3 5a f0 5f 01 95 fc aa 6f 71 12 13 eb"
FindPrivateKey failed for the following reason:
Unable to obtain private key file name

Use /? option for help 

它很可爱....但它是错误的! (2只笨狗参考)

证书导出对话框给了我这个非常好的信息:

此代码在使用此 sn-p 模拟管理员时运行:click here

我很想知道为什么?

(在 Windows Server 2008 R2 和 Windows 7 上测试)

我会被诅咒的!

当我将它编译到 v3.5 时它可以工作!!!!

怎么办?

【问题讨论】:

您是否尝试使用我的回答中的建议?工作吗?您有任何问题或问题吗? 你解决过这个问题吗?我正在尝试修复类似的问题,谢谢! @TJB 不,我没有在 .net v3.5 中制作了一个小命令行应用程序,它确实可以工作。我在 microsoft connect 上发布了它,但我认为它在他们的优先级列表中并不高。 代码应该发布在问题中......你所有的链接现在似乎都被破坏了...... 老兄,这已经 7 岁了,这些东西都是你自己运行时可能需要的额外东西,但对这个问题并不重要。没有链接,这个问题仍然很有意义。 【参考方案1】:

在我看来,您应该以其他方式导入密钥。示例见http://support.microsoft.com/kb/950090。

此外,我发现将私钥保存在UseMachineKeyStore 中并不好。在大多数情况下,您需要在某些用户的 My store 中导入带有私钥的证书,并在没有私钥的情况下仅导入 Root 证书。

如果您确实需要将私钥保存在机器密钥存储中,那么您至少应该保护密钥以供某些选定用户读取,而不是从所有人读取。密钥容器只是文件系统中的一个文件(参见目录“%ALLUSERSPROFILE%\Microsoft\Crypto\Keys”中的文件),它与 NTFS 中的其他文件一样具有安全描述符。要更改文件的安全描述符,您可以使用CspKeyContainerInfo.CryptoKeySecurity 属性和AddAccessRuleRemoveAccessRule 等。

已更新:首先对冗长的回答表示抱歉。

我可以将your program code 分成两部分。在第一部分中,您生成一个可用作 CA 证书的自签名证书,并将其保存为 rootcert.pfx 文件。在第二部分中,您导入证书,但使用 RSACryptoServiceProvider 填充先前创建的密钥的属性,而不是使用 rootcert.pfx

我建议您将代码的第二部分替换为更标准和更简单的代码:使用 rootcert.pfx 中的私钥导入证书,就像 http://support.microsoft.com/kb/950090 中描述的那样。效果很好。

我自己不使用 BouncyCastle,所以我无法评论您的代码的第一部分,但总的来说,您在代码中所做的操作也可以使用 Windows SDK 中的 MakeCert.exe 实用程序。你可以这样做

MakeCert.exe -pe -ss MY -a sha1 -cy authority -len 2048 -m 120 -r -# 1
             -n "CN=Some Root CA, C=NL, OU=BleedingEdge, ST=Somewhere, L=Somelane"

然后您可以导出带有或不带有与证书管理单元相关的私钥的证书(用于 mmc.exe)。在上面的示例中,对于某些特殊的 EKU,我没有限制 CA,因此您可以不受限制地使用它,但如果您确实需要限制,您可以在 MakeCert.exe 中添加额外的参数。您还可以使用 MakeCert.exe 创建其他使用 CA 证书签名的证书。因此,您只能针对 MakeCert.exe 制作小型 PKI。

在我看来,创建证书实际上是代码的一个单独部分。你的主要问题在第二部分。

如果你想导入 CA 证书,你应该考虑一些重要的事情:

您应该在您组织的每台(或多台)计算机上将其导入RootAuthRoot 中的localMachine,但您应该不带私钥导入证书。您可以在以下方面做到这一点

CertMgr.exe -add -c CA.cer -s -r localMachine AuthRoot

您应该在一台计算机上的计算机上使用私钥导入 CA 证书,并且仅适用于将颁发其他证书的用户(谁将使用 CA 的私钥签署新证书)。一种用于在 CurrentUserMy 证书存储中导入证书。所以计算机上的代码可能看起来像

以下:

// import PFX
X509Certificate2 cert = new X509Certificate2 (@"c:\Oleg\rootcert.pfx", "password",
    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// save certificate and private key
X509Store storeMy = new X509Store (StoreName.My, StoreLocation.CurrentUser);
storeMy.Open (OpenFlags.ReadWrite);
storeMy.Add (cert);

// get certificate without private key
// one can import certificate from rootcert.cer instead
byte[] certBlobWithoutPrivateKey = cert.Export (X509ContentType.Cert);
// save pure certificate in Root of the local machine
X509Certificate2 certWithoutPrivateKey = new X509Certificate2 (certBlobWithoutPrivateKey);
X509Store storeRoot = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
storeRoot.Open (OpenFlags.ReadWrite);
storeRoot.Add (certWithoutPrivateKey);

如果您将StoreName.MyStoreLocation.CurrentUser 更改为其他值,代码将起作用,但我不建议您这样做。

一般来说,在 .NET 代码中导入证书看起来有点奇怪,并且没有显示在后台会做什么。 Windows 只知道Key Containers 将保存关于 CSP 的私钥(确切地说是密钥对)和Certificate Stores 将保存证书的位置(请参阅http://msdn.microsoft.com/en-us/library/bb204781.aspx 关于存储的位置)。为了能够将有关密钥容器的信息保存在 Microsoft 引入的名为 Certificate Extended Properties 的证书存储中。如果您在 .NET 中使用 X509Certificate2 的属性,例如 ThumbprintFriendlyNameHasPrivateKeyArchived 等,则可以使用证书的扩展属性。所以我建议你导入两次 CA 证书。一次在RootAuthRoot 没有设置CERT_KEY_PROV_INFO_PROP_IDCertificate Extended Properties 一次在My 存储with关于Key所在位置的信息的设置带有私钥 (CERT_KEY_PROV_INFO_PROP_ID) 的容器。此外,您可以考虑在使用后直接删除私钥,仅在确实需要使用且不要永久保留时才导入。所有这些对于提高安全性都很重要。

【讨论】:

所有好的建议,但我真的认为你应该更好地阅读我的问题我没有导入我正在生成的 pfx 文件并在代码中插入证书。我确实告诉私钥保留在 CSP 中,问题是它看起来不像。如果您不告诉私钥持久存在,那么只要您的应用程序运行,它就会一直留在那里。这不是发生的事情,它永远不会到达:) 一旦我插入证书,它看起来就像插入了密钥但它没有被存储。 @the_ajp:你想做什么可能有理解问题。此外,您使用诸如“插入证书”或“在代码中插入证书”之类的词,这不是标准术语。有“生成密钥par和相应的证书”和“导入带或不带私钥的证书”。还不清楚为什么您需要为每个代码生成证书,而不是针对 Windows SDK 的 MakeCert.exe 实用程序。你想生成很多 CA 证书吗?尽管如此,在您的程序中执行 1 步 - 生成证书和 2 步使用私钥导入证书。 此外,大多数情况下,您需要在多台计算机上不带私钥拥有根证书,并在您将使用证书进行签名的一台计算机上拥有带私钥的证书。更好的是,我会更新我的答案,以描述我如何理解您可能遇到的问题。【参考方案2】:

我遇到了这个问题,似乎即使您运行 FindPrivateKey 工具的用户也无权访问密钥,因此您会收到“无法获取私钥文件名”消息。您可以将该工具作为 LocalSystem 进程运行。

更多信息在这里:

http://www.itsolutionbraindumps.com/2011/02/finding-private-key-for-your.html

丁哥

【讨论】:

【参考方案3】:

我遇到了完全相同的问题,结果证明解决方案非常简单。 我所要做的就是通过

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet

到 X509Certificate2 的 ctor。 现在您正在使用 DotNetUtilities 将 bouncycastle 证书转换为 .net 证书,但辅助方法使用 DefaultKeySet(而不是 MachineKeySet + PersistKeySet )。

并像这样安排私钥:

var cspParams = new CspParameters

      KeyContainerName = Guid.NewGuid().ToString(),
      KeyNumber = (int)KeyNumber.Exchange,
      Flags = CspProviderFlags.UseMachineKeyStore
;

var rsaProvider = new RSACryptoServiceProvider(cspParams);

我希望这会有所帮助。

【讨论】:

我会调查的。我已经尝试过这样的事情,但没有对它们进行 or-ing :)。谢谢! 谢谢你完全正确。迟到总比不对:P 我正在使用充气城堡生成证书,我将在哪里添加您提供的代码?代码在***.com/questions/47417262/…【参考方案4】:

new X509Certificate2(localPFXPath, inputPass, X509KeyStorageFlags.MachineKeySet & X509KeyStorageFlags.PersistKeySet);用 & 代替 |为我工作。

【讨论】:

【参考方案5】:

通常 Root 中的证书没有要管理的私钥。如果您在 Web 请求中关联密钥,您应该导入到我的文件夹。我有 TLS/SSl 异常,我有客户端证书链。如果您将所有证书链存储在我的商店中,那么我就摆脱了该异常。问题出在用户帐户上。存储证书的实用程序使用当前用户帐户,实际应用程序在系统帐户上运行。

【讨论】:

【参考方案6】:

基本问题是 .NET 证书 API 只是 C++ advapi32 证书管理器 api 的包装器,因此您无法指定传递给实际负责粘贴证书的该 api 的所有选项进入 Windows 证书存储并保存密钥。底线是“UseMachineStore”选项需要传递给 CspProviderFlags,而后者又传递给 CAPI.CRYPT_MACHINE_KEYSET。这是决定密钥是否真实持久的小家伙。即使您设置了 X509KeyStorageFlags.PersistKeySetMachineKeySetExportable,这个选项也没有设置似乎有几个不同的原因。只要愚蠢的密钥保留在 C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ 文件夹中,所有这些选项就有效。如果 CRYPT_MACHINE_KEYSET 在导入时未设置,则 advapi32 会在证书句柄被 GC 处理后立即将密钥删除。

解决方案:在将证书导入个人计算机存储之前,将证书添加到受信任的根。 In reading the logs from CAPI2,每次“导入”证书时,我都会看到两次对“X509 对象”的调用。一个总是有<Flags value="20" CRYPT_MACHINE_KEYSET="true"/>,(我们想要的),但另一个没有,除非“验证链策略”没有返回错误。所以看起来 advapi32 正在检查证书的“有效性”,或者返回一个被 X509Certificate2 (I love how many empty catch blocks they have in that code) 吞噬的异常,或者 advapi32 只是单方面决定不保留不受信任的证书的密钥。 (顺便说一句,我怀疑这是 2008 年和 20012 年之间的行为变化,但我还没有证明这一点。)为了解决这个问题,我在我的代码中添加了一个 If-check 以添加证书,如果颁发者等于主题(它是一个自签名证书)然后将证书添加到根,然后再将其添加到我的。

    if (certificate.Issuer.Equals(certificate.Subject))
    
        using (X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))  
            store.Open(OpenFlags.ReadWrite);
            store.Add(certificate);
            store.Close();
        
    

    using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) 
        store.Open(OpenFlags.ReadWrite);
        store.Add(certificate);
        store.Close();
    

注意:我发现如果使用的证书没有已经包含主题密钥标识符,那么这是不必要的。不知何故,当您触发 api 实际生成 SKI 而不是将其提交时,它会触发条件将魔术 CRYPT_MACHINE_KEYSET 标志传递给 advapi32。

【讨论】:

以上是关于在 .NET 4 中在 Root 中插入证书(带私钥),本地计算机证书存储失败的主要内容,如果未能解决你的问题,请参考以下文章

Linux中在当前用户(root用户)用户主目录下创建一目录(new)?

mysql查询一次在单个查询中在多个表中插入记录

在shell osascript命令中在''之间插入变量?

如何在IOS开发中在自己的framework中添加.bunble文件

Laravel - 在数据库中在一列中插入动态图像数组 JQuery

使用 C# 从 PKI 智能卡读取证书