简化了 Windows 私钥生存期

当工作负荷在 Windows 上加载 PKCS#12/PFX 而不设置 PersistKeySetEphemeralKeySet 存储选项时,.NET 将确定何时不再需要私钥并应将其擦除。 在早期版本的 .NET(和 .NET Framework 中),使用了两组不同的逻辑。 在 .NET 9 中,只有一组逻辑。

旧行为

以前,当从具有 new X509Certificate2(pfx, password, flags) 的 PKCS#12/PFX 加载证书(及其私钥)时,加载的证书表示私钥的生存期。 当此证书对象被释放(或者如果被垃圾回收而没有被丢弃,则最终确定),关联的私钥被删除。 没有发生所有权共享或所有权转移。

当从具有 X509Certificate2Collection.Import(pfx, password, flags) 的 PKCS#12/PFX 加载证书(及其私钥)时,每个加载的具有私钥的证书都会跟踪其生存期,就像单个证书加载一样。 但是此外,在证书的本机副本上放置了一个标记,以指示任何副本也应跟踪私钥生存期。 如果第二个 X509Certificate2 对象是根据相同的基础 PCERT_CONTEXT 值创建的,那么首先处理(或最终确定)的任何一个副本都会从另一个副本下擦除私钥。

以下代码由于私钥被删除而失败(使用 CryptographicExceptionNullReferenceException):

X509Certificate2Collection coll = new X509Certificate2Collection(pfx, password, X509KeyStorageFlags.DefaultKeySet);
X509Certificate2Collection coll2 = coll.Find(X509FindType.FindBySubjectName, "", false);

coll2 = null;
GC.Collect();
GC.WaitForPendingFinalizers();

using (RSA key = coll[0].GetRSAPrivateKey())
{
    key.SignData(pfx, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}

新行为

从 .NET 9 开始,生存期始终与直接从 PKCS#12/PFX 加载生成的 X509Certificate2 实例相关联。

现在,先前行为部分中的相同代码片段成功了。

引入的版本

.NET 9 预览版 7

中断性变更的类型

此更改为行为更改

更改原因

加载 PKCS#12/PFX 的大多数工作负荷都使用单个证书加载,并了解与该方法关联的生存期机制。 与收集负荷相关的机制往往令人惊讶,有时会导致过早的键擦除。

如果了解集合负荷生存期管理,并依赖于对克隆调用 Dispose 来导致键擦除,请确保你也(或相反)对原始加载的对象调用 Dispose

受影响的 API