如何从 PKCS#12 字节数组构造 X509Certificate2 抛出 CryptographicException(“系统找不到指定的文件。”)?

Posted

技术标签:

【中文标题】如何从 PKCS#12 字节数组构造 X509Certificate2 抛出 CryptographicException(“系统找不到指定的文件。”)?【英文标题】:How can constructing an X509Certificate2 from a PKCS#12 byte array throw CryptographicException("The system cannot find the file specified.")? 【发布时间】:2011-04-19 02:04:05 【问题描述】:

我正在尝试从字节数组中的 PKCS#12 blob 构造 X509Certificate2 并得到一个相当令人费解的错误。此代码在 Windows XP 上具有管理员权限的桌面应用程序中运行。

堆栈跟踪如下,但由于_LoadCertFromBlob 标记为[MethodImpl(MethodImplOptions.InternalCall)],我在尝试进行故障排除时迷路了。

System.Security.Cryptography.CryptographicException: The system cannot find the file specified.
  at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
  at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
  at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
  at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)

编辑: Blob 是由 BouncyCastle for C# 生成的真正 PKCS#12,包含 RSA 私钥和证书(自签名或最近注册到 CA)——我是尝试做的是将私钥和证书从 BouncyCastle 库转换为 System.Security.Cryptography 库,方法是从一个导出并导入另一个。该代码适用于它尝试过的绝大多数系统。我只是从未见过该构造函数抛出的特定错误。那个盒子上可能有某种环境怪异。

编辑 2: 错误发生在不同城市的不同环境中,我无法在本地重现它,因此我最终可能不得不将其归咎于 XP 损坏安装。

既然你问了,这里是有问题的片段。该代码采用 BouncyCastle 表示形式的私钥和证书,从个人密钥存储中删除相同专有名称的任何先前证书,并通过中间 PKCS#12 blob 将新的私钥和证书导入个人密钥存储。

// open the personal keystore
var msMyStore = new X509Store(StoreName.My);
msMyStore.Open(OpenFlags.MaxAllowed);

// remove any certs previously issued for the same DN
var oldCerts =
    msMyStore.Certificates.Cast<X509Certificate2>()
        .Where(c => X509Name
                        .GetInstance(Asn1Object.FromByteArray(c.SubjectName.RawData))
                        .Equivalent(CurrentCertificate.SubjectDN))
        .ToArray();
if (oldCerts.Length > 0) msMyStore.RemoveRange(new X509Certificate2Collection(oldCerts));

// build a PKCS#12 blob from the private key and certificate
var pkcs12store = new Pkcs12StoreBuilder().Build();
pkcs12store.SetKeyEntry(_Pkcs12KeyName,
                        new AsymmetricKeyEntry(KeyPair.Private),
                        new[] new X509CertificateEntry(CurrentCertificate));
var pkcs12data = new MemoryStream();
pkcs12store.Save(pkcs12data, _Pkcs12Password.ToCharArray(), Random);

// and import it.  this constructor call blows up
_MyCertificate2 = new X509Certificate2(pkcs12data.ToArray(),
                                       _Pkcs12Password,
                                       X509KeyStorageFlags.Exportable);
msMyStore.Add(_MyCertificate2);
msMyStore.Close();

【问题讨论】:

【参考方案1】:

您有 PKCS#12 还是只有 PFX 文件?在 Microsoft 世界中也是如此,但其他人认为不同(请参阅this archived page)。

你可以试试关注

X509Certificate2 cert = X509Certificate2(byte[] rawData, "password");
X509Certificate2 cert2 = X509Certificate2(byte[] rawData, "password",
              X509KeyStorageFlags.MachineKeySet |
              X509KeyStorageFlags.PersistKeySet |
              X509KeyStorageFlags.Exportable);

(X509Certificate2(Byte[])) 或

X509Certificate2 cert = X509Certificate2("C:\Path\my.pfx", "password");

(如果您需要使用一些标志,请参阅 Microsoft Docs 上的 X509Certificate2(String, String) 和 Import(String, String, X509KeyStorageFlags))

更新:如果您插入代码片段而不仅仅是异常堆栈跟踪,将会很有帮助。

您使用哪个X509KeyStorageFlags?您可以使用Process Monitor 找出哪个文件找不到X509Certificate2 构造函数。例如,在出现问题的 Windows XP 上,当前用户没有默认密钥容器。您可以创建它并重试导入。

【讨论】:

添加 X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable 从 byte[] 加载时可导出到构造函数对我有用。 我在 Windows Server 2012 上遇到了同样的错误,因为完全相同的代码在 Windows 7 或 Azure 云中没有问题。将标志更改为 MachineKeySet 解决了问题。 当心这个例子中的 PersistKetSet 标志 - 它在磁盘上留下了永远不会被清理的关键文件。如果您非常频繁地拨打此电话,那么您最终将完成一项巨大的清理任务。 (问我怎么知道的。)如果您只在内存中使用证书,那么只需指定 MachineKeySet。如果您在代码中完成了内存中的证书,请调用 Reset 方法以立即删除密钥文件。不过,最好添加一次到商店并重新加载。这就是它的用途。 @JamesMcLachlan:对不起,多年来我没有在这个方向上做任何事情。导入证书not once 的场景非常具体。如果PersistKeySet 在您的场景中不好,您不应该使用它。另一方面,如果使用PersistKeySet 导入证书,则密钥将作为文件(在用户配置文件中)放置在密钥存储中。 CertGetCertificateContextPropertyCERT_KEY_PROV_INFO_PROP_ID 获取信息。可以使用PP_UNIQUE_CONTAINER 获取文件名。文件在CommonApplicationData下(带Microsoft\Crypto\RSA\MachineKeys后缀)【参考方案2】:

我遇到了同样的问题。

根据这个old KB article,问题是构造函数试图将证书加载到当前用户的配置文件中,但是.Net 代码我正在模拟用户,因此它没有加载用户配置文件。构造函数需要加载的用户配置文件才能正常工作。

来自文章:

X509Certificate2 类构造函数尝试将证书导入到运行应用程序的用户帐户的用户配置文件中。很多时候,ASP.NET 和 COM+ 应用程序模拟客户端。当他们这样做时,出于性能原因,他们不会为模拟用户加载用户配置文件。因此,他们无法访问模拟用户的“用户”证书存储区。

加载用户配置文件修复了错误。

【讨论】:

有问题的代码段在桌面应用程序中运行,所以我怀疑未加载的用户配置文件有问题。 @Zain,感谢您的回答。您还知道这是解决此问题的最佳方法吗?由于性能影响,它让我考虑。谢谢 @curiousBoy 就像我在回答的最后一行所说的,调用 api 来加载用户配置文件。【参考方案3】:

我遇到了同样的问题。

    在托管该站点的服务器上打开 IIS。 找到该站点的应用程序池。 点击高级设置。 将“加载用户配置文件”更改为 true。 (可能需要重启或重启)

这允许crypto subsystem 工作。

【讨论】:

这是指向 IIS 应用程序池问题的第四个答案。我在桌面应用程序中遇到了这个问题。用户配置文件完全有可能以某种方式损坏,但它肯定已加载。 有趣,从您的问题来看,它不是桌面应用程序。此修复对我的 IdentityServer3 MVC 应用程序有效。该项目在本地运行良好,但一旦启动到服务器,它就会崩溃。这个标志是唯一的修复。权限可能在桌面应用程序上? 它在 XP 机器上的管理员组帐户中运行,因此权限和 UAC 都不应该有问题。我无法在任何其他机器上重现它,所以我现在最好的猜测是配置文件损坏或操作系统配置损坏。【参考方案4】:

在 Windows 2012 上的 Web 应用程序中遇到此问题,将应用程序池选项 Load User Profile 设置为 true 使其工作。

为此,请运行inetmgr.exe,转到Advanced Settings 以获得正确的应用程序池,将Process Model 下的Load User Profile 更改为true。

【讨论】:

这发生在桌面应用程序中,而不是 Web 应用程序或服务中。【参考方案5】:

我遇到了完全相同的问题。在特定用户下运行时,相同的代码和数据/证书在 Windows 2003 x86 上运行良好,但在另一个帐户下失败(该帐户也用于运行 IIS 应用程序池)。

显然,其他一些事情耗尽了 Windows 上的资源,因此失败的用户无法真正加载用户的个人资料(他的桌面看起来很奇怪),尽管 事件查看器中没有相关事件 .

重新启动暂时解决了问题。虽然这不是问题的永久解决方案,但它表明还有其他东西(例如,COM+ 组件、本机代码服务等)消耗资源需要调查。也说明了Windows平台的不稳定性……

【讨论】:

这种特殊的不稳定性似乎与严重依赖进程间通信的桌面软件和面对资源枯竭时的健壮性差有关。我过去曾在 Linux 上看到过与 GNOME 完全相同的问题,尤其是 Evolution。

以上是关于如何从 PKCS#12 字节数组构造 X509Certificate2 抛出 CryptographicException(“系统找不到指定的文件。”)?的主要内容,如果未能解决你的问题,请参考以下文章

java 从 PKCS12(比如pfx格式)证书中提取私钥证书(PrivateKey)和受信任的公钥证书(X509Certificate)的序列号(SerialNumber)

X509Certificate 构造函数异常

比较java中的2 x509证书

如何通过 PKCS#11 API 从 eToken 获取私钥?

如何从 .SPC(代码签名证书)和 .PKCS12(私钥)生成 PKCS12 (.p12)?

如何在 ios 上创建 PKCS #12 -> 更一般地说,如何跨平台传输 rsa 私钥/公钥对