在 .NET 4 中,在根、LocalMachine 证书存储中插入证书(带有私钥)失败

发布于 2024-09-16 15:23:55 字数 1478 浏览 16 评论 0 原文

我在本地计算机的根证书存储中插入带有私钥的新 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 ();

证书被插入,一切看起来都很漂亮:(看!) note it said it has a private key

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

所以你会说人们可以通过 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只愚蠢的狗参考)

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

使用以下代码段模拟管理员时运行此代码: 点击此处

我很想知道为什么?

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

我该死!

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

怎么办?

I'm having problems inserting a new CA certificate with privatekey in the Root certificate store of the localmachine.

This is what happens:

//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 ();

The certificate gets inserted and it all looks dandy: (see!) note it says it has a private key

Note: is says it has a privatekey.

So you'd say one would be able to find it with 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 

It's cute .... BUT IT'S WRONG!! (2 stupid dogs reference)

And the Certificate export dialog gives me this very fine message:
alt text

This code is run while impersonating an administrator using this snippet: click here

I'd just love to know WHY?

(tested on Windows Server 2008 R2 & Windows 7)

I'll be damned!

It works when I compile it to v3.5!!!!

What to do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

绾颜 2024-09-23 15:23:55

我遇到了完全相同的问题,而且解决方案非常简单。
我所要做的就是传递

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet

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

并像这样安排私钥:

var cspParams = new CspParameters
{
      KeyContainerName = Guid.NewGuid().ToString(),
      KeyNumber = (int)KeyNumber.Exchange,
      Flags = CspProviderFlags.UseMachineKeyStore
};

var rsaProvider = new RSACryptoServiceProvider(cspParams);

我希望这会有所帮助。

I had exactly the same problem and the solution turned out to be really simple.
All I had to do is to pass

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet

to X509Certificate2's ctor.
Now you are using the DotNetUtilities to convert the bouncycastle certificate to the .net one, but the helper method creates the .net cert with the DefaultKeySet (instead of MachineKeySet + PersistKeySet
).

And arrange the private key like this:

var cspParams = new CspParameters
{
      KeyContainerName = Guid.NewGuid().ToString(),
      KeyNumber = (int)KeyNumber.Exchange,
      Flags = CspProviderFlags.UseMachineKeyStore
};

var rsaProvider = new RSACryptoServiceProvider(cspParams);

I hope this helps.

欢你一世 2024-09-23 15:23:55

在我看来,你应该以其他方式导入密钥。有关示例,请参阅 http://support.microsoft.com/kb/950090

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

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

更新:首先,很抱歉回答太长。

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

我建议将代码的第二部分替换为更标准和简单的代码:从 rootcert.pfx 中导入带有私钥的证书,如 http://support.microsoft.com/kb/950090。它工作得很好。

我自己不使用 BouncyCastle,所以我无法评论代码的第一部分,但一般来说,您在代码中所做的事情也可以针对 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)导出带或不带私钥的证书。在上面的示例中,我没有限制 CA 对于某些特殊的 EKU,因此您可以不受任何限制地使用它,但如果您确实需要限制,您只需将其他参数添加到 MakeCert.exe。您还可以使用 MakeCert.exe 创建使用 CA 证书签名的其他证书。因此,您只能使用 MakeCert.exe 来创建小型 PKI。

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

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

  • 您应该在每个(或您组织的许多)计算机,但您应该导入不带私钥的证书。您可以按照以下步骤执行此操作

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

  • 您应该在一个计算机上导入带有私钥的 CA 证书> 计算机并且仅适用于将颁发其他证书的用户(将使用 CA 的私钥签署新证书)。一种用于导入 CurrentUser我的证书存储中的证书。因此,计算机上的代码可能如下所示

// 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 只知道密钥容器其中私钥(确切地说是密钥对) )将针对 CSP 和证书所在的证书存储进行保存已保存(请参阅 http://msdn.microsoft.com/en-us/library/ bb204781.aspx 关于商店的位置)。为了能够在证书存储中保存有关密钥容器的信息,Microsoft 引入了名为 证书扩展属性。如果您在 X509Certificate2 的 .NET 属性中使用,例如 ThumbprintFriendlyNameHasPrivateKeyArchived 等等,您可以使用证书的扩展属性。所以我建议您导入两次CA证书。 RootAuthRoot 中的一个 没有设置 CERT_KEY_PROV_INFO_PROP_ID 证书扩展属性并在我的存储中再次信息设置关于带有私钥的密钥容器的位置(CERT_KEY_PROV_INFO_PROP_ID)。此外,您可以考虑在使用后直接删除私钥,仅在确实需要使用时才导入,并且不会永久持有。所有这些对于获得更好的安全性都很重要。

It seems to me you should import the key in a little other way. See http://support.microsoft.com/kb/950090 for an example.

Moreover I find not good to save private key in UseMachineKeyStore. In the most cases you need import certificate with the private key in My store of some user and import in Root only certificate without private key.

It you do need save private key on Machine key store, that you should at least protect the key for reading only for some selected users and not from Everyone. The key container is just a file in the file system (see files in the diriectory "%ALLUSERSPROFILE%\Microsoft\Crypto\Keys") which has security descriptors like other files in NTFS. To change security descriptors of the files you can use CspKeyContainerInfo.CryptoKeySecurity property and AddAccessRule, RemoveAccessRule and so on.

UPDATED: First of all sorry for the long answer.

I could divide your program code in two parts. In the first part you generate a self-signed certificate which can be used as a CA certificates and you save it as rootcert.pfx file. In the second part you import the certificate, but use RSACryptoServiceProvider filled with properties of previous created key instead of using rootcert.pfx.

I suggest to replace the second part of your code to more standard and simple code: import certificate with the private key from rootcert.pfx like it described in http://support.microsoft.com/kb/950090. It works very well.

I don't use myself the BouncyCastle, so I could not comment the first part of your code, but in general what you do in the code you could do also with respect of MakeCert.exe utility from the Windows SDK. You can do like following

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"

Then you can export certificate with or without private key with respect of Certificate Snap-In (for mmc.exe). In the example above I don't restrict CA for some special EKU, so you can use it without any restriction, but if you do need the restrictions you can just add additional parameters to MakeCert.exe. You can also use MakeCert.exe to create other certificate which are signed with the CA certificate. So you are able to make small PKI with respect of MakeCert.exe only.

It seems to me that creating of the certificate is really a separate part of your code. Your main problem is in the second part.

If you want import CA certificate you should take in consideration some important things:

  • You should import it in Root or AuthRoot in localMachine on every (or many) computer of your organization, but you should import the certificate without the private key. You can do this with respect of following

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

  • You should import CA certificate with private key on the computer on one computer and only for the user who will issue other certificates (who will sign new certificates with the private key of CA). One use to import the certificate in the My certificate store of CurrentUser. So the code on the computer could looks like

following:

// 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);

The code will work if you do will change StoreName.My and StoreLocation.CurrentUser to another values, but I don't recommend you to do this.

In general importing of certificates in .NET code look like a little strange and not shows what will be done under the hood. Windows knows only Key Containers where private keys (to be exactly the key pair) will be saved with respect of CSP and Certificate Stores where certificates will be saved (see http://msdn.microsoft.com/en-us/library/bb204781.aspx about location of the store). To be able to save information about the key container in the certificate store Microsoft introduced so named Certificate Extended Properties. If you use in .NET properties of X509Certificate2 like Thumbprint, FriendlyName, HasPrivateKey, Archived and so on you work with the Extended Properties of the certificate. So I recommend you to import CA certificate twice. One in Root or AuthRoot without setting CERT_KEY_PROV_INFO_PROP_ID Certificate Extended Properties and one more time in My store with the setting of information about the place of Key Container with the private key (CERT_KEY_PROV_INFO_PROP_ID). Moreover you can consider to remove private key directly after the usage, import it only if you really need to use it and not hold it permanently. All this is important to have better security.

浊酒尽余欢 2024-09-23 15:23:55

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

更多信息请参见:

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

I have encountered this problem and it seems that even the user with which you are running the FindPrivateKey tool does not have access to the key and therefore you would get the "Unable to obtain private key file name" message. You could run the tool as LocalSystem process.

More information here:

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

Dinko

茶底世界 2024-09-23 15:23:55

我遇到了同样的问题(在.NET 6.0中),

通过更改 使用CertificateRequest(..)创建的证书
商店.添加(证书);

到:

store.Add(new X509Certificate2(cert.Export(X509ContentType.Pkcs12), "", X509KeyStorageFlags.Exportable));

成功了。

I had the same problem (in .NET 6.0) , with certificate created with CertificateRequest(..)

by changing
store.Add(cert);

to:

store.Add(new X509Certificate2(cert.Export(X509ContentType.Pkcs12), "", X509KeyStorageFlags.Exportable));

did the trick.

下壹個目標 2024-09-23 15:23:55

新的 X509Certificate2(localPFXPath、inputPass、X509KeyStorageFlags.MachineKeySet & X509KeyStorageFlags.PersistKeySet);与 &而不是|为我工作。

new X509Certificate2(localPFXPath, inputPass, X509KeyStorageFlags.MachineKeySet & X509KeyStorageFlags.PersistKeySet); with the & instead of the | worked for me.

沉睡月亮 2024-09-23 15:23:55

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

Usually Certificates in Root won't have private key to manage. You should import to My folder if you are associating key in the web request. I have TLS/SSl exception where I have chain of client certificates. If you store all the chain of certificates in My store then I got rid of that exception. Where the problem is with user accounts. Utility to store the certificates uses current user account and the actual application runs on system account.

想挽留 2024-09-23 15:23:55

基本问题是 .NET 证书 API 只是 C++ advapi32 证书管理器 api 的包装器,因此您无需指定传递给该 api 的所有选项,这些选项实际上负责将证书粘贴到 Windows 中证书存储并保留密钥。最重要的是,“UseMachineStore”选项需要传递给CspProviderFlags,而后者又传递给CAPI.CRYPT_MACHINE_KEYSET。这是决定密钥是否真正持久存在的小家伙。即使您设置了X509KeyStorageFlags.PersistKeySetMachineKeySetExportable,但未设置此选项的原因似乎有多种。所有这些选项仅在愚蠢密钥保留在 C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ 文件夹中时才存在。如果在导入时未设置 CRYPT_MACHINE_KEYSET,则一旦 GC 处理了证书句柄,advapi32 就会将密钥吹走。

解决方案:在将证书导入个人计算机存储之前,将证书添加到受信任的根。 在从 CAPI2 读取日志时,我实际上看到每次“导入”证书时都会对“X509 对象”进行两次调用。一个始终具有 (我们想要的),但另一个则没有,除非“验证链策略”不返回错误。因此,看起来 advapi32 正在检查证书的“有效性”,并且返回一个被 X509Certificate2 吞没的异常(我喜欢他们在该代码中有多少空的 catch 块)或 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。

The basic problem is that the .NET certificates API is just a wrapper around the C++ advapi32 certificate manager api, so you don’t get to specify all the options that get passed to this api that is actually responsible for sticking the cert into the Windows cert store and persisting the keys. The bottom line is that the “UseMachineStore” option needs to get passed to the CspProviderFlags which in turn gets passed to the CAPI.CRYPT_MACHINE_KEYSET. This is the little guy that determines whether the key gets persisted for real or not. There seem to be several different reasons why this option doesn’t get set even though you set the X509KeyStorageFlags.PersistKeySet and MachineKeySet and Exportable. All these options only live as long as the stupid key stays in the C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ folder. If CRYPT_MACHINE_KEYSET doesn’t get set at time of import then advapi32 blows the key away as soon as the certificate handle gets disposed by GC.

Solution: Add the certificate to the Trusted Root BEFORE you import the certificate into the Personal machine store. In reading the logs from CAPI2, I actually see two calls to “X509 Objects” every time the Certificate is “Imported”. One always has the <Flags value="20" CRYPT_MACHINE_KEYSET="true"/>, (what we want) but the other does not UNLESS “Verify Chain Policy” returns no errors. So it looks like advapi32 is checking the “validity” of the cert and either returns an exception that gets swallowed by X509Certificate2 (I love how many empty catch blocks they have in that code) or advapi32 just unilaterally decides to not persist the keys for untrusted certificates. (By the way, I suspect this is a behavior change between 2008 and 20012, but I haven’t proven that.) To work around this, I added an If-check to my code to add the certificate that if the Issuer equals the Subject (it is a self-signed cert) then add the cert to the Root before adding it to My.

    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();
    }

Note: I have found that this is unneccessary if using a certificate that does not have a Subject Key Identifier already in it. Somehow when you trigger the api to actually generate the SKI instead of handing it in, it triggers the conditional to pass the magic CRYPT_MACHINE_KEYSET flag to advapi32.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文