所有不同的 X509KeyStorageFlags 的基本原理是什么?

What is the rationale for all the different X509KeyStorageFlags?

今天同事又遇到了一个和这些相关的bug!我自己过去发现这些标志真的很令人沮丧,因为如果您在实例化 X509Certificate2 对象、导出它们或将它们保存在 X509Store 中时稍微出错,您可能会遇到各种奇怪的错误,例如:

是的,它们都被记录在案了(有些文档似乎很有道理),但为什么一定要这么复杂?

主要是,今天一定要这么复杂,因为昨天这么复杂,没人想出更简单的东西。

我无法在这里进行线性叙述,所以请忍受所需要的来回编织。

什么是 PFX/PKCS#12 文件?

虽然我不能完全说出 PFX 的起源是什么,但在 Windows 函数的名称中有一条线索可以读取和写入它们:PFXImportCertStore and PFXExportCertStore。它们包含许多可以使用 属性 标识符相互关联的独立实体(证书、私钥和其他内容)。它们似乎被设计为整个证书存储的 export/import 机制,就像所有 CurrentUser\My 一样。但是由于一种存储是 "memory store"(任意集合),.NET import/export 是有意义的,但是出现了一些复杂情况(在 .NET 之前)。

Windows 私钥

Windows 支持私钥的许多不同位置,但对于遗留加密 API 它们归结为 4 部分寻址方案:

  • 加密提供者的名称
  • 密钥容器的名称
  • 如果这是机器相关密钥或用户相关密钥的标识符
  • 这是 "signature" 键还是 "exchange" 键的标识符。

这已简化为 CNG 的 3 部分方案:

  • 存储引擎的名称
  • 密钥名称
  • 这是一个机器相关密钥还是用户相关密钥的标识符。

为什么需要机器或非机器标识符?

CAPI 和 CNG 都支持直接与命名键交互。因此,您创建了一个名为 "EmailDecryption" 的密钥。系统上的另一个用户创建了一个同名密钥。那应该工作吗?我们很可能会。所以,Huzzah,确实如此!单独的密钥,因为它们保存在与创建它们的用户相关联的上下文中。

但现在您想要一个可供多个用户使用的密钥。这不是您通常想要的东西,因此它不是默认设置。这是一个选择。 CRYPT_MACHINE_KEYSET旗帜诞生。

我会继续在这里说,我听说现在不鼓励直接使用命名键; CAPI/CNG 团队更喜欢以 GUID 命名的密钥,并且您可以通过证书存储与它们交互。但这是进化的一部分。

导入 PFX 有什么作用?

PFXImportCertStore 将 PFX 中的所有证书复制到提供的存储中。它还会导入(CryptImportKey or BCryptImportKey,取决于它认为需要什么)。然后,对于它导入的每个密钥,它找到(通过 PFX 中的 属性 值)匹配的证书,并在 "this is my 4-part identifier"(CNG 密钥)的证书存储表示上设置 属性只需将第 4 部分设置为 0);这就是证书所知道的关于其私钥的全部信息。

(PFX 是一种非常复杂的文件格式,如果 "weird parts" 的 none 得到利用,此描述是正确的)

密钥生命周期

Windows 私钥永远存在,或者直到有人删除它们。

所以当 PFX 导入它们时,它们将永远存在。如果您要导入到 CurrentUser\My,这是有意义的。如果你在做一些暂时的事情,那就没意义了。

.NET 反转关系/使其成为 "Too Easy"

Windows 设计(主要)是您与证书商店交互,并从证书商店获取证书。 .NET 后来出现,并且(有人假设,基于看到应用程序真正在做什么)使证书成为顶级对象,并存储某种次要的东西。

因为 Windows 证书(实际上是 "store certificate elements")"know" 他们的私钥是什么,.NET 证书 "know" 他们的私钥是什么。

哦,但是 MMC 证书管理器说它可以用私钥导出证书(到 PFX),为什么证书构造函数不能接受 "just a cert" 格式以外的那些字节?好的,现在可以了。

协调生命周期

您将一些字节作为 X509Certificate/X509Certificate2 打开。这是一个带有 "no password" 的 PFX(通过任何可能为真的各种方式)。您发现它是错误的,然后您将证书交给垃圾收集器。该私钥永远存在,因此您的硬盘驱动器会慢慢填满,并且密钥存储访问变得越来越慢。然后你就生气了,把你的电脑重新格式化。

这似乎很糟糕,所以 .NET 所做的是当证书(的一个字段)被垃圾收集(实际上是最终确定)时,它告诉 CAPI(或 CNG)删除密钥。现在一切按预期工作,对吗?嗯,只要程序没有异常终止就可以了。

哦,你把它添加到持久存储中了?但是我打算在新的证书存储实体"knows"如何找到私钥之后删除私钥。好像不好。

输入X509KeyStorageFlags.PersistKeySet

PersistKeySet 表示 "don't do that deleting thing"。当您打算将证书添加到 X509Store 时。

如果您希望在不指定标志的情况下获得相同的行为,请调用 Environment.FailFast,或者在执行导入后拔下计算机。

关于那个机器或用户位

在 .NET 中,您可以轻松地在集合中获取一袋证书并对其调用 Export。如果有些人有机器密钥,而其他人有用户密钥怎么办? PFXExportCertStore 来拯救。当一个机器密钥被导出时,它会记下一个标识符,表明它是一个机器密钥,所以导入会将它放回同一个地方。

嗯,通常。也许你从一台机器上导出了一个机器密钥,而你只想在另一台机器上以非管理员身份检查它。好的,您可以指定 CRYPT_USER_KEYSET 又名 X509KeyStorageFlags.UserKeySet

哦,您是在一台机器上以用户身份创建的,但想在另一台机器上将其作为机器密钥?美好的。 CRYPT_MACHINE_KEYSET / X509KeyStorageFlags.MachineKeySet.

我真的需要 "Temporary" 个文件吗?

如果您只是检查 PFX 文件,或者想临时使用它们,为什么还要费心将密钥写入磁盘呢?好的,Windows Vista 说,我们可以直接将私钥加载到加密密钥对象中,我们会告诉您指针。

PKCS12_NO_PERSIST_KEY / X509KeyStorageFlags.EphemeralKeySet

我认为,如果 Windows 在 NT4 中具有此功能,那么这将成为 .NET 的默认设置。它现在不能是默认值,因为太多的事情取决于 "normal" 导入如何检测私钥是否可用的内部结构。

最后两个呢?

PFXImportCertStore 的默认模式是私钥不应被重新导出。要告诉它这是错误的,您可以指定 CRYPT_EXPORTABLE / X509KeyStorageFlags.Exportable.

CAPI 和 CNG 都支持一种机制,在这种机制中,软件密钥可以在使用私钥之前需要同意或密码(类似于智能卡的 PIN 提示),但您必须声明第一次创建(或导入)密钥时。因此 PFXImportCertStore 允许您指定 CRYPT_USER_PROTECTED(.NET 将其公开为 X509KeyStorageFlags.UserProtected)。

最后两个实际上只对 "one private key" PFX 有意义,因为它们适用于所有键。它们也不包含原始密钥可能具有的所有选项……CNG 和 CAPI 都支持 "archivable" 密钥,这意味着 "exportable once"。机器密钥上的自定义 ACL 在 PFX 中也没有得到任何支持。

总结

对于一个证书(或一组证书),一切都很简单。一旦涉及私钥,事情就会变得一团糟,Windows 证书(存储)的抽象变得有点单薄,您需要了解持久性模型和存储模型。