将驻留在智能卡上的证书添加到具有私钥所有权的 Microsoft 商店

信息安全 密码学 公钥基础设施 智能卡
2021-08-15 23:22:48

将证书从智能卡添加到本地用户存储时,我们如何解决CryptAcquireCertificatePrivateKey失败?0x8009200B

在用户向 CA 在线生成智能卡证书请求的注册系统中,证书在智能卡中“离线”加载,例如在请求发出几天后,因此certenrolllib用于创建请求的对象不能用于安装卡上的证书和卡生成的私钥永远不会也不能导出到卡外。

当我们在卡中加载证书时,我们使用 Minidriver API,我们有用于生成密钥的密钥容器的名称(通常是 GUID,类似lr-e46f1586-7133-4284-895d-557e2261c24d)我们开始读取 cmapfile 并获得密钥容器与该 GUID 对应的索引 XX,我们在卡微型驱动程序文件系统的 mscp 文件夹中创建一个 kscXX 证书,我们使用 zlib 压缩从 CA 获得的 DER 二进制证书,我们添加一个标头并将其加载到 kscXX 文件中。

我们可以看到卡上证书的格式很好,我们可以使用各种工具查看它。问题是此证书未出现在 Microsoft 用户存储中。事实上,它“可能”有时会出现,特别是如果容器是默认容器,但无论如何不会立即出现,也不会出现在所有操作系统中。

我们使用 MSCAPI 创建了一个库,它获取证书上下文并通过在以下代码中使用CryptAcquireCertificatePrivateKey来证明私钥所有权(例如:addCardCertToStore.exe 工具)

使用 PIN 登录、获取用户密钥等:

 fStatus = CryptGetKeyParam(
    hKey,                  // HCRYPTKEY hKey,
    KP_CERTIFICATE,        // DWORD dwParam,
    pCertBlob,             // BYTE* pbData,
    &dwCertLen,            // DWORD* pdwDataLen,
    0                      // DWORD dwFlags
    );

if (!fStatus)
{
    return 1;
}


pCertContext = CertCreateCertificateContext(
    PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
    pCertBlob,
    dwCertLen);

    //trying to prove privatekey ownership by calling CryptAcquireCertificatePrivateKey


HCRYPTPROV_OR_NCRYPT_KEY_HANDLE  hCrypt;

    DWORD  hInfo;

fStatus=CryptAcquireCertificatePrivateKey(pCertContext,0,NULL,&hCrypt,&hInfo,NULL);

     if (!fStatus)
{
     return 1;
}



    hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"My");

// 保存证书上下文以存储

如果CryptAcquireCertificatePrivateKey未调用,则证书始终导出到存储,但没有私钥所有权,然后不能用于签名等操作。

但是,当我们CryptAcquireCertificatePrivateKey确实使用“50% 的时间”时,我们可以将证书保存到与私钥所有权一起存储,但并非总是如此,在某些情况下,我们会看到0x8009200B错误(CRYPT_E_NO_KEY_PROPERTY,例如“找不到用于解密的证书和私钥。 ') 调用时CryptAcquireCertificatePrivateKey例如第一次启动 addCardCertToStore.exe 时可能会发生此错误,然后如果我们第二次重新启动 addCardCertToStore.exe 它可能会工作,CryptAcquireCertificatePrivateKey返回 OK,并且证书已安装在商店中,有时我们必须调用它 3 次,有时似乎它永远不会完成,有时在我们重新插入卡后它可以工作,有时它会失败,因为证书在商店中一段时间​​后自动安装并删除了此证书CryptAcquireCertificatePrivateKey 工作并再次在商店中添加证书。

我知道 CSP 正在维护一个缓存,并且它会定期扫描卡以更新该缓存,并且它可能会或可能不会检测到卡上的新证书并将其单独添加到存储中,并且某些 MSCAPI 操作可能会产生以下效果让 CSP 独立于操作本身从卡证书更新存储。

最后的问题是:为什么会出现这种奇怪的行为,CryptAcquireCertificatePrivateKey以及在这种情况下我们如何解释错误代码0x8009200B(访问被拒绝)?
另一点是我们是否应该在卡上安装 DER 证书而不是使用 minidriver API 而是使用 MSCAPI 函数(如果可能的话)?
我们怎样才能有一个稳定的方式将卡证书添加到商店?

1个回答

这些事情可能有点复杂,因为拼图有很多部分,并且到处都有缓存。

理论上,您应该使用CertEnroll(由操作系统提供)并让它发挥所有作用。CertEnroll 可以很好地支持请求生成和证书导入之间的长时间延迟(甚至几天);但是,请求生成和随后的证书导入必须在同一台机器上使用相同的帐户完成。原因是 CertEnroll 通过将请求本身的副本保存在专用存储(卡外部)中来跟踪正在进行的注册操作,这允许它在证书返回时找到私钥并设置所有链接。

(理论上,如果操作是作为“普通用户”在他的商店而不是本地机器商店中完成的,那么请求应该以“漫游配置文件”结束,因此不需要使用同一台机器;只有条件在帐户上保持不变。不过,这需要一些测试。)

由于多种原因,实践可能会有所不同例如,证书安装可能是在某些专用硬件系统上完成的。如果您不能使用 CertEnroll,或者某些缓存出错(发生),那么您必须考虑以下几点:

  • 从证书到私钥的链接是在 Windows 方面发生的事情。当您调用CryptAcquireCertificatePrivateKey()时,它实际上遵循证书上下文的属性,即 Windows 端对象,以在该 CSP 中定位 CSP 和容器名称。在内部,该属性包含 CSP名称和容器名称。您可以使用CertSetCertificateContextProperty()进行设置;使用CERT_KEY_PROV_INFO_PROP_ID.

    这样的属性通常是持久的(它写在存储结构中,通常是用户漫游配置文件深处的一些文件),但您只能将其保存在 RAM 中CERT_SET_PROPERTY_INHIBIT_PERSIST_FLAG当该属性仅在 RAM 中设置时,它只会影响给定的CERT_CONTEXT结构,当然会仅限于单个进程。

  • 在卡上,证书和私钥之间的关系以cmapfilekxc*文件不同的方式保存,如MiniDriver API 规范中所述。当您将卡插入 Windows 系统时,该系统应该检查卡中的证书,并将它们推送到本地用户的存储中,并将链接设置为私钥。“证书传播服务”正在执行此操作,因此请确保它已在您的系统上启动。否则,自动提取证书将不起作用。

    插入卡时触发“证书传播”。如果您修改卡的内容,尤其是“直接”这样做时,您必须指示用户拔出/重新插入卡。我知道触发证书传播的唯一其他方法是停止并重新启动证书传播服务,这需要管理员权限。

    证书传播是异步的,可能需要相当长的时间(最多一分钟左右),尤其是与远程桌面结合使用以及同时使用多个 USB 读卡器时。

  • 有一个缓存。“智能卡服务”(SCardSvr.exe)维护它。缓存是基于 RAM 的,但服务在停止时会将其写入注册表,因此缓存会阻止重新启动(注册表项为HKLM\SOFTWARE\Microsoft\Cryptography\Calais\Cache\Cache)。

    缓存旨在加快速度:从卡中读取数据会导致 I/O 速度变慢。证书、容器名称,甚至公钥都存储在缓存中。缓存保持同步至关重要,否则会出现各种问题,这些问题与您描述的症状兼容(您也可以在计算签名时获取其他问题,例如 NTE_BAD_SIGNATURE,因为 BaseCSP 将要验证刚刚计算的签名,并且将使用缓存中的公钥而不是卡中的公钥执行此操作)。

    缓存是否被认为是最新的取决于cardcf卡上文件的内容。MiniDriver API 的 5.4.3 节对此进行了描述。基本上,该文件将包含两个 16 位计数器,一个用于容器内容(即公钥),另一个用于文件。如果您修改任何容器(例如生成或导入新的密钥对)或任何文件(并且您这样做了!),那么您必须cardcf. 通常 BaseCSP 会在需要时自动执行,但是如果您重新初始化卡或永久修改卡,则可能必须手动执行(我也亲眼目睹了 SCardSvr 缓存中的一些错误,所以我最终强制cardcf更新使用卡特定的 APDU 命令)。

    或者,您可以尝试配置卡,使其报告不应为其信息维护缓存;CP_CARD_CACHE_MODE在 MiniDriver API 中查找。不过,并非所有卡都支持修改缓存模式。

  • 使本已复杂的情况更加复杂的是CNG这是一个全新的加密 API。CryptoAPI 和 CNG 之间有一些桥梁;例如CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG对于 CryptAcquireCertificatePrivateKey()。设置CERT_KEY_PROV_INFO_PROP_ID属性时,您可能希望将 CryptoAPI BaseCSP 名称(“Microsoft Base Smart Card Crypto Provider”)替换为相应的 CryptoNG 名称(“Microsoft Smart Card Key Storage Provider”)。这可能会使一些不了解 CNG 的应用程序感到困惑。

    在卡 PIN 处理方面,可能需要使用 CryptoAPI 和 CNG。为了简化用户操作,CryptoAPI 和 CNG 都在内存中维护用户 PIN 的每个进程缓存。但是,这两个缓存是不同的!此外,使用最近的卡(即支持 MiniDriver v7 的卡,而不是 MiniDriver v5),会自动生成“会话 PIN”,并且缓存将保留会话 PIN 的副本,而不是已知的 PIN人类用户。如果您的应用程序的两个子集使用该卡,一个通过 CryptoAPI,另一个通过 CNG,那么它们将竞争 PIN,每个都会触发新会话 PIN 的生成,从而使另一个会话 PIN 无效。结果是向用户抛出重复的“PIN 输入”弹出窗口,用户对此很少感到高兴。

    特别是 Windows 7 上的 Internet Explorer,以及更普遍的 SSL 客户端代码,当访问私钥以进行基于证书的客户端身份验证时,往往会强制使用 CNG。如果您在同一个应用程序中使用相同的密钥(例如,在 IE 中加载的 ActiveX 控件),那么您也应该使用 CNG —— 否则,PIN 战会如火如荼。