首页 > 解决方案 > X509Certificate2:判断证书私钥是否属于硬件设备,是否需要PIN

问题描述

我正在编写一个程序来进行加密,使用存储在硬件设备(智能卡)上的证书。

前言

据我了解,当包含一个或多个证书的智能卡连接到计算机时,证书会出现在 Windows 证书存储区(CurrentUser/My)中。

这是由必须安装的智能卡软件管理的。

由于我的软件必须适用于任何硬件设备,最简单的解决方案是弹出一个窗口,让用户选择要使用的证书。

第一个问题

我不想显示 CurrentUser/My 上的所有证书,而只显示那些将相关硬件连接到计算机的证书。

这或多或少是我正在使用的代码:

X509Store storeObject = new X509Store(StoreName.My, StoreLocation.CurrentUser);

storeObject.Open(OpenFlags.ReadOnly);

foreach (var certificate in storeObject.Certificates)
{
    bool isValid = false;
    if (certificate.HasPrivateKey)
    {
        try
        {
            var rsaKey = certificate.PrivateKey as RSACryptoServiceProvider;
            
            if (rsaKey.CspKeyContainerInfo.HardwareDevice)
            {
                isValid = true;
            }
        }
        catch (Exception ex)
        {
            // just log
        }
    }
}

尽管这个解决方案非常难看,但这是我想出的最好的。

如果满足以下条件,则证书属于智能卡:

问题出在 PrivateKey 演员表上:似乎很多事情都是由 CryptoApi 系统在此演员表期间执行的(当然包括对智能卡软件的调用)。

非常慢,如果智能卡软件出现某种故障,则会显示无法控制的错误消息,...

问题

有没有其他方法可以确定证书是否属于硬件设备?并确定硬件当前是否已连接?

第二个问题

证书可能需要 PIN。

我必须(这是一个先决条件)避免 Windows 输入框要求输入 PIN。

收集 PIN 并在我的加密操作期间使用它对我来说不是问题。

我的问题是了解是否需要 PIN。

我在网上找到的任何示例都假定如果 rsaKey.CspKeyContainerInfo.HardwareDevice == true 则需要 PIN。

但我不确定。

问题

有什么方法可以确定证书是否需要 PIN 码?

结论

我也喜欢外部库,只要它们是免费的(最好是开源的)和/或来自 Microsoft。

非常感谢!

标签: c#smartcardx509certificate2

解决方案


在评论中,我提供了 Win32 互操作解决方案,它可能比延迟 OP 体验更快。提议的解决方案将扩展证书属性以从那里读取提供程序名称(我相信,.NET 在内部的某个地方也是如此?)。然后打开指定的提供者(CSP 或 KSP)并从提供者那里查询硬件能力。此解决方案不接触私钥,也不提示输入 PIN。

调用顺序:

  1. 调用CertGetCertificateContextProperty函数和查询CERT_KEY_PROV_INFO_PROP_ID属性。
  2. 函数返回一个指向CRYPT_KEY_PROV_INFO结构的指针。
  3. 结构包含pwszProvName存储提供者名称的字段。
  4. 调用NCryptOpenStorageProvider函数并将提供程序名称作为参数传递。
  5. 调用NCryptGetProperty函数并传递在上一步中获取的提供程序句柄和属性名称以进行检索。你需要NCRYPT_IMPL_TYPE_PROPERTY财产。
  6. 接收到的值将存储实现类型的按位标志组合。检查返回值(pbOutput参数将存储 4 个字节的整数值)是否NCRYPT_IMPL_HARDWARE_FLAG启用了标志。

调用完成后释放所有非托管资源。虽然这看起来有点复杂,但根据我的经验,这是确定给定证书的密钥存储位置的最快方法。

重要说明

步骤 4 和 5 可能不适用于所有智能卡提供商(假设您使用旧版 CSP),但适用于我的。在这种情况下,将步骤 4 和 5 替换如下:

  1. 调用CryptAcquireContext函数以获取您的提供者的句柄(在步骤 3 中检索)
  2. 调用CryptGetProvParam函数来检索提供程序属性。查询PP_IMPTYPE属性。pbData参数将存储一个 4 字节长的字节数组,表示实现类型的按位组合。检查此值是否CRYPT_IMPL_HARDWARE1启用了标志。

推荐阅读