代码之家  ›  专栏  ›  技术社区  ›  Tim Lovell-Smith

所有不同X509KeysorageFlags的原理是什么?

  •  7
  • Tim Lovell-Smith  · 技术社区  · 6 年前

    今天,一位同事发现了另一个与此相关的bug!我发现这些标志在过去确实令人沮丧,因为如果您在实例化X509Certificate2对象、导出它们或将它们保存在X509Store中时将它们弄错,那么您可能会遇到各种奇怪的错误,例如:

    • 意外地无法告诉NETSH.exe或ASP.net使用某个SSL证书[通过其指纹],即使您的计算机存储中有该证书
    • 意外地,您可以导出证书数据,但使用.export()导出时没有私钥
    • 意外的是,你的单元测试在更新的Windows版本上开始失败,显然是因为你没有使用正确的标志

    是的,它们都有文档记录(有些文档似乎很有意义),但为什么一定要这么复杂呢?

    1 回复  |  直到 6 年前
        1
  •  53
  •   bartonjs    6 年前

    主要是,今天必须如此复杂,因为昨天如此复杂,没有人想出更简单的办法。

    我不能在这里提出一个线性的叙述,所以请忍受来回编织的要求。

    什么是PFX/PKCS#12文件?

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

    Windows支持许多不同的位置来存放私钥,但对于传统的crypto API,它们可以归结为4部分寻址方案:

    • 密钥容器的名称
    • 如果这是机器相对键还是用户相对键的标识符

    这简化为CNG的三部分方案:

    • 存储引擎的名称
    • 如果这是机器相对键还是用户相对键的标识符。

    CAPI和CNG都支持直接与命名密钥交互。所以您创建了一个名为“EmailDecryption”的密钥。系统上的另一个用户创建了一个同名的密钥。这样行吗?嗯,可能吧。所以,哈扎,是的!单独的键,因为它们被保存在与生成它们的用户相关的上下文中。

    CRYPT_MACHINE_KEYSET 旗帜诞生了。

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

    导入PFX做什么?

    CryptImportKey BCryptImportKey ,取决于它认为需要什么)。然后,对于它导入的每个密钥,它找到(通过PFX中的属性值)匹配的证书,并在cert store表示上为“this is my 4-part identifier”设置一个属性(CNG keys只是将第4部分设置为0);这实际上是cert对其私钥的全部了解。

    (PFX是一种非常复杂的文件格式,只要没有“奇怪的部分”被利用,这种描述是正确的)

    关键生命周期

    因此,当PFX导入它们时,它们将永远存活。如果您正在导入到CurrentUser\My,这是有意义的。如果你在做一些短暂的事情,那就没什么意义了。

    .NET颠倒了关系/使之“太简单”

    Windows的设计(主要)是与证书存储交互,并从证书存储获得证书。NET后来出现了,并且(我们假设,基于看到应用程序真正在做什么)将证书作为顶级对象,而存储则是次要的。

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

    调和寿命

    您打开了一些字节作为X509Certificate/X509Certificate2。它是一个带有“无密码”的PFX(通过各种可能是真的方式)。你看它是错的,你让证书去垃圾收集器。私钥会永远存在,所以硬盘会慢慢填满,密钥存储访问也会越来越慢。然后你生气了,重新格式化你的电脑。

    这看起来很糟糕,所以.NET所做的是当证书的(一个字段)被垃圾收集(实际上是最终确定)时,它告诉CAPI(或CNG)删除密钥。现在一切正常了,对吧?好吧,只要程序没有异常终止。

    输入 X509KeyStorageFlags.PersistKeySet

    如果希望在不指定标志的情况下实现相同的行为,请调用 Environment.FailFast ,或在完成导入后拔下计算机。

    关于那个机器或用户位

    Export 在上面。如果有的有机器钥匙,有的有用户钥匙呢?PFXExportCertStore前往救援。当导出一个机器密钥时,它会写下一个表示它是一个机器密钥的标识符,因此import会将它放回同一个位置。

    CRYPT_USER_KEYSET 又称作 X509KeyStorageFlags.UserKeySet .

    / X509KeyStorageFlags.MachineKeySet .

    我真的需要“临时”文件吗?

    PKCS12_NO_PERSIST_KEY / X509KeyStorageFlags.EphemeralKeySet

    我想,如果Windows在NT4中有这个功能,那么这将是.NET的默认设置。它现在不能是默认值,因为太多的事情依赖于“正常”导入如何工作的内部结构来检测私钥是否可用。

    最后两个呢?

    CRYPT_EXPORTABLE / X509KeyStorageFlags.Exportable .

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

    最后两个实际上只对“一个私钥”pfx有意义,因为它们适用于所有密钥。它们也不包含原点键可能拥有的全部选项。。。CNG和CAPI都支持“可存档”键,即“可导出一次”。计算机密钥上的自定义ACL在PFX中也不受任何支持。

    摘要

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