【问题标题】:X509Certificate2: determine if certificate private key belongs to an hardware device, and if it needs a PINX509Certificate2:判断证书私钥是否属于硬件设备,是否需要PIN
【发布时间】:2020-11-24 18:03:12
【问题描述】:

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

  • 程序必须使用 .Net Framework 4.5.2 :( 无法更改。
  • 我执行的加密操作没有问题 :)

序言

据我了解,当包含一个或多个证书的智能卡连接到计算机时,证书会出现在 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
        }
    }
}

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

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

  • HasPrivateKey 为真。
  • 如果智能卡已连接,则将 PrivateKey 转换为 RSACryptoServiceProvider 不会引发异常。
  • HardwareDevice 则为真。

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

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

问题

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

第二个问题

证书可能需要 PIN。

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

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

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

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

但我不确定。

问题

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

结论

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

非常感谢!

【问题讨论】:

  • 使用 Windows 资源管理器让卡第一次工作。当您设置驱动程序时,很多这些问题都会自动处理。阅读供应商用户手册。一旦卡与资源管理器一起使用,您就可以将其与 c# 代码一起使用。 Windows 资源管理器将解锁卡,然后您就可以读写了。智能卡具有不同的寻址模式和数据大小 (8,16,32,64)。因此,请确保您可以使用 windows 编写并使用 c# 阅读(反之亦然)。然后在c#中编写代码解锁。您需要知道能够解锁卡的尺寸。
  • This is very slow -- 有多慢?您可以考虑使用 Win32 互操作来查询证书属性,读取提供程序名称,然后查询提供程序信息,说明密钥是否存储在智能卡上。
  • @Crypt32 对于每次转换(certificate.PrivateKey as RSACryptoServiceProvider)慢至 2/3 秒。我的问题不是获取证书属性,除了 PrivateKey,因为上面的转换有时(当卡没有连接,或者智能卡软件有问题时)会出现问题。我想知道这是否是检索 CspKeyContainerInfo.HardwareDevice 属性并了解卡是否已连接的唯一方法。我将尝试使用 Win32 互操作,但我怀疑我会得到相同的结果。
  • 属性不是X509Certificate2对象的一部分,它们不能直接从.NET API访问,它们可以通过Win32互操作获得。

标签: c# smartcard x509certificate2


【解决方案1】:

在 cmets 中,我提供了 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 标志。

【讨论】:

  • 谢谢!我能够将您建议的第一部分的代码放在一起,它就像一个魅力。对于第二部分,我遇到了 CryptAcquireContext() 的问题...您对 dwProvType 和 dwFlags 参数有任何线索吗?
  • dwProvType in CryptAcquireContext call 与步骤 1 中返回的 CRYPT_KEY_PROV_INFO 中的 dwProvType 匹配。dwFlags 参数应为 CRYPT_SILENT
  • 感谢 Crypt32,您的帮助是无价的!我会尽快(几天)将代码放在问题下方。
猜你喜欢
  • 2021-11-09
  • 2013-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 1970-01-01
  • 2020-07-05
相关资源
最近更新 更多