今天,一位同事遇到了另外一个与这些相关的错误!我发现这些标志在过去我自己真的很令人沮丧,因为如果你在实例化X509Certificate2对象或导出它们或将它们保存在X509Store中时会略微出错,你可能会遇到各种各样奇怪的错误,例如:
意外地无法告诉NETSH.exe或ASP.net使用某个SSL证书[通过其指纹],即使您的机器商店中有该证书
您可以意外地导出证书数据,但使用.Export()导出它而不使用私钥
意外地,您的单元测试开始在较新的Windows版本上失败,显然是因为您没有使用正确的标志
是的,它们是有记录的,而且所有文档(以及一些文档似乎都有意义),但为什么它必须这么复杂?
1> bartonjs..:
主要是,它今天必须这么复杂,因为昨天这个很复杂,没有人提出任何更简单的东西.
我不能在这里提出一个线性叙述,所以请忍受来回的编织.
什么是PFX/PKCS#12文件?
虽然我不能完全说出PFX的起源是什么,但是在Windows函数的名称中有一个线索是读取和写入它们:PFXImportCertStore和PFXExportCertStore.它们包含许多可以使用属性标识符相互关联的单独实体(证书,私钥和其他内容).它们看起来像是一个整个证书库的导出/导入机制,就像所有的CurrentUser\My一样.但是由于一种存储是"内存存储"(任意集合),.NET导入/导出是有意义的,但是有些复杂功能(来自.NET之前).
Windows私钥
Windows支持许多不同的私钥位置,但对于传统的加密API,它们归结为一个由4部分组成的寻址方案:
加密提供程序的名称
密钥容器的名称
这是机器相对密钥还是用户相对密钥的标识符
如果这是"签名"密钥或"交换"密钥的标识符.
这简化为CNG的3部分方案:
存储引擎的名称
密钥的名称
这是机器相对密钥还是用户相对密钥的标识符.
为什么需要机器或非标识符?
CAPI和CNG都支持直接与命名密钥交互.因此,您创建了一个名为"EmailDecryption"的密钥.系统上的另一个用户创建一个同名的密钥.那会有用吗?我们可能会.所以,huzzah,确实如此!单独的键,因为它们被保持在与制作它们的用户相关的上下文中.
但现在您需要一个可供多个用户使用的密钥.这不是你通常想要的东西,所以它不是默认的.这是一个选择.该CRYPT_MACHINE_KEYSET
标志诞生了.
我会继续说这里,我听说现在不鼓励直接使用命名密钥; CAPI/CNG团队更喜欢GUID命名的密钥,并且您通过证书存储区与它们进行交互.但它是进化的一部分.
导入PFX有什么作用?
PFXImportCertStore将PFX中的所有证书复制到提供的商店中.它还会导入(CryptImportKey或BCryptImportKey,具体取决于它认为需要的内容).然后,对于它导入的每个键,它(通过PFX中的属性值)找到匹配的证书,并在证书存储表示上设置"这是我的4部分标识符"的属性(CNG键仅设置第4个部分到0); 这真的是证书所知道的私钥.
(PFX是一种非常复杂的文件格式,如果没有使用"怪异部分",这种描述都是正确的)
关键生命周期
Windows私钥永远存在,或直到有人删除它们.
因此,当PFX进口时,它们会永远存在.如果您要导入CurrentUser\My,这是有意义的.如果你做一些短暂的事情,那就不那么有意义了.
.NET颠覆关系/使其"太容易"
Windows设计(主要)是您与证书商店进行交互,并从证书商店获得证书..NET后来出现了,并且(基于看到应用程序真正在做什么)的一个假设使证书成为顶级对象,并存储了一些次要的东西.
因为Windows证书(实际上是"存储证书元素")"知道"它们的私钥是什么,所以.NET证书"知道"它们的私钥是什么.
哦,但是MMC证书管理器说它可以使用私钥导出证书(进入PFX),除了"只是证书"格式之外,为什么cert构造函数不能接受这些字节?好的,现在可以.
协调终身
您打开一些字节作为X509Certificate/X509Certificate2.它是一个没有密码的PFX(通过各种方式可以是真的).你看它是错误的,你让证书去垃圾收集器.这个私钥永远存在,所以你的硬盘慢慢填满,密钥存储访问变得越来越慢.然后你生气了,重新格式化你的电脑.
这看起来很糟糕,所以.NET所做的是当一个证书(一个字段)获得垃圾收集(实际上,最终确定)时,它告诉CAPI(或CNG)删除该密钥.现在事情按预期工作了,对吗?好吧,只要程序没有异常终止.
哦,你把它添加到一个持久的商店?但是,在新证书存储实体"知道"如何查找私钥后,我将删除私钥.这看起来很糟糕.
输入
X509KeyStorageFlags.PersistKeySet
PersistKeySet说"不要删除东西".当您打算将证书添加到X509Store时.
如果要在不指定标志的情况下执行相同的操作,请在执行导入后调用Environment.FailFast或拔下计算机电源插头.
关于该机器或用户位
在.NET中,您可以轻松地在一个集合中获得一系列证书并对其进行调用Export
.如果有人有机器密钥,而其他人有用户密钥怎么办?PFXExportCertStore来救援.当导出机器密钥时,它会记下一个标识符,表明它是一个机器密钥,因此import会将其放回到同一个地方.
嗯,通常.也许您从一台机器上导出了一个机器密钥,并且您只想在另一台机器上将其作为非管理员进行检查.好的,你可以指定CRYPT_USER_KEYSET
aka X509KeyStorageFlags.UserKeySet
.
哦,你在一台机器上创建了这个用户,但是想把它作为另一台机器上的机器密钥?精细. CRYPT_MACHINE_KEYSET
/ X509KeyStorageFlags.MachineKeySet
.
我真的需要"临时"文件吗?
如果你只是检查PFX文件,或者想要临时使用它们,为什么还要把密钥写入磁盘呢?好的,Windows Vista说,我们可以直接将私钥加载到加密密钥对象中,我们会告诉你指针.
PKCS12_NO_PERSIST_KEY
/X509KeyStorageFlags.EphemeralKeySet
我想如果Windows在NT4中具有此功能,那么这将是.NET的默认设置.它现在不能是默认值,因为太多的东西依赖于"正常"导入如何检测私钥是否可用的内部结构.
最后两个怎么样?
PFXImportCertStore的默认模式是私钥不应该可以重新导出.要说明它是错的,你可以指定CRYPT_EXPORTABLE
/ X509KeyStorageFlags.Exportable
.
CAPI和CNG都支持在使用私钥之前软件密钥可能需要同意或密码的机制(如智能卡的PIN提示),但您必须在首次创建(或导入)密钥时声明.所以PFXImportCertStore允许你指定CRYPT_USER_PROTECTED
(和.NET公开它X509KeyStorageFlags.UserProtected
).
最后两个真正只对"一个私钥"PFXes有意义,因为它们适用于所有键.它们也不包含原始密钥可能具有的全部选项...... CNG和CAPI都支持"可存档"键,这意味着"可导出一次".机器密钥上的自定义ACL也不会在PFX中获得任何支持.
摘要
对于证书(或证书集合),一切都很容易.一旦涉及到私钥,事情就会变得混乱,并且对Windows证书(存储)的抽象会变得有点薄,您需要了解持久性模型和存储模型.