本主题讨论适用于使用和/或开发智能卡微型驱动程序的开发人员的一般准则 - 让你知道智能卡及其相关应用程序的预期行为。
常规设计准则
有关如何分发卡微型驱动程序、如何映射逻辑卡文件系统和其他微型驱动程序设计指南的信息,请参阅智能卡微型驱动程序概述下的“常规设计指南”部分。
解除封锁智能卡 PIN 的挑战/响应方法
为使管理员能够成功使用此机制取消阻止用户的卡片,管理员必须能够识别和使用存储在卡上的管理员密钥,以便他们能够正确生成针对所发出的质询的响应数据。
执行此作的一种方法是使用卡片标识符来唯一标识卡。 (卡标识符是卡片的唯一标识符。这可以以某种形式表示给 UI 中的用户,但可以编写程序以向卡片发送相应的 APDU 命令来读取此信息。
然后,此信息可让管理员识别卡上的密钥,并计算对向用户发出的质询数据的适当响应。
假定存储于卡上的管理员密钥是使用一些安全机制保存的,该机制只能由有效和受信任的管理员访问(最好尽可能少)。 但是,这超出了此规范的范围。
有关详细信息,请参阅以下“质询/响应机制”部分。
增强的 PIN 支持
版本 6.0 支持灵活的体系结构来支持多个 PIN 支持。 此体系结构引入了角色的新概念,其中每个角色对应于 PIN 标识符。 PIN 标识符用于从卡中提取 PIN 信息,以及将 PIN 与密钥容器相关联。
标识符包含一个数字,当前限制为 0 到 7。 我们还引入了PIN_SET的概念,这是可以从 PIN 标识符生成的位掩码。 目前,只有较低的 8 位用于 PIN 码设置。 我们也可以选择利用剩余的比特位来指示条件,比如“与”、“或”或将来可能会发现的其他有用信息。 我们选择了这种方法,以便卡片可以容易地应用位掩码。
假定用户使用与 PIN #3 对应的角色 3 进行身份验证。 这转换为位掩码 0000 0100 (base 2)。 卡可以将此信息记录为当前已验证的 ID,通过执行按位与运算,可以轻松验证密钥和 PIN 上的访问控制规则。 此设计允许同时在卡上具有多个经过身份验证的标识,这是支持 v6 卡微型驱动程序的卡的要求。 例如,如果 PIN #1 已经过身份验证,随后 PIN #2 也被验证,则应允许执行由这些 PIN 控制的任何操作。
会话 PIN 和安全 PIN 通道
当 Windows 必须建立用于 PIN 身份验证的安全 PIN 通道时,以下作顺序是使用微型驱动程序执行的。 为了符合要求,微型驱动程序和卡必须与以下序列兼容。 具体而言,会话 PIN 应在进程之间传输,并且只能持续一定的时间。 (我们建议使用 CARD_AUTHENTICATE_SESSION_PIN 标志,以确保任何会话 PIN 在卡冷重置之前始终有效,即使调用 CardAuthenticateEx 时设置了 GENERATE_SESSION_PIN 标志。)
应支持以下行为:
- 应用程序 A(受信任的系统进程)获取智能卡的句柄并收集 PIN。
- 应用程序 A 随后调用卡片 CardAuthenticateEx 微型驱动程序函数,并传递收集的 PIN 并设置CARD_AUTHENTICATE_GENERATE_SESSION_PIN标志。 这不会导致卡解锁。
- 应用程序 A 存储生成的会话 PIN,并将句柄释放到卡和卡微型驱动程序。 卡不会冷启动。
- 应用程序 A 将会话 PIN 和在步骤 1 中获取的卡所在的读取器名称发送到应用程序 B。
- 应用程序 B 获取与 1 中相同的卡。
- 应用程序 B 调用 CardAuthenticateEx ,并传入会话 PIN 并设置CARD_AUTHENTICATE_SESSION_PIN标志。 如果会话 PIN 仍然有效,应对卡进行身份验证并有效以供使用。
- 应用程序 B 使用完卡后,它会调用 CardDeauthenticateEx 以取消对卡的授权。
此行为具有以下实际限制:
卡必须通过返回适用于CP_CARD_PIN_STRENGTH_VERIFY的相应值,来声明它们可以使用会话PIN的能力。
依赖于为每个验证提供 PIN 的卡与此系统不兼容。
多个应用程序可以在任何时候有被认定为有效的会话 PIN。 如果每个 PIN 只能有一个会话 PIN,建议以下实现:
- 卡片应记住生成的最近会话 PIN。
- 如果输入无效的会话 PIN,卡应无法通过身份验证,并且如果支持,则应减少会话 PIN 的重试计数器。 如果重试计数达到 0,并且下一次身份验证尝试无效,则会话 PIN 应失效。
- 在协商新的会话 PIN 之前,后续会话 PIN 处理应失败。
会话 PIN 必须能够从系统上的不同应用程序使用。
会话 PIN 不能只是 PIN 的编码。
此系统的安全性仅限于会话 PIN 的强度和用于生成它的协商协议。 实际的会话 PIN 协商超出了此规范的范围。 我们对此设计没有要求,只不过它按本部分所述工作。
会话 PIN 仍被视为有价值,应被视为机密。
卡应能够检测无效的会话 PIN。
只读卡
为了解决在基础 CSP/KSP 环境外部个性化且本质上是只读的卡片,我们引入了只读卡的新概念。 如果卡是只读的,则它必须通过 CardGetProperty 函数声明此信息(请参见本规范前面的相关部分)。
只读卡必须仅支持版本 7 卡微型驱动程序接口的子集,并且不需要支持管理员 PIN。
下表列出了只读卡片必须支持的函数。
| 传说 | |
|---|---|
| 是的 | 必须实现此函数。 |
| 否 | 入口点必须存在,并且必须返回SCARD_E_UNSUPPORTED_FEATURE。 |
| 否(可选) | 该操作不需要对只读卡提供支持,但如果卡支持此操作,则可以实现。 如果不支持,入口点必须返回SCARD_E_UNSUPPORTED_FEATURE。 |
| 是 (可选) | 无论卡是否为只读,都应根据此规范中的定义实现此函数。 |
为只读卡开发微型驱动程序时,应考虑以下要求:
- 除“msroots”文件(如“cardcf”和“cardid”)外,所有预期的基础 CSP/KSP 文件都必须存在于只读卡上(或者必须通过微型驱动程序接口虚拟化)。
- 只读卡必须至少包含一个密钥,该密钥由主卡(即 ROLE_USER)PIN 码保护。
- 允许只读卡不包含管理密钥。 如果出现这种情况,则预计微型驱动程序将不支持 CardGetChallenge、 CardAuthenticateChallenge 和 CardUnblockPin。
- 查询时,只读卡应返回 0 个可用字节和 0 个可用容器。
- 仅允许在只读卡片上设置CP_PARENT_WINDOW和CP_PIN_CONTEXT_STRING属性。
- 对于只读卡片,CP_SUPPORTS_WIN_X509_ENROLLMENT 属性应设置为 false。
缓存模式
基本 CSP/KSP 支持三种不同的缓存模式,这取决于使用参数 CP_CARD_CACHE_MODE 调用 CardGetProperty 所返回的缓存模式。
- 如果返回的标志是CP_CACHE_MODE_GLOBAL_CACHE,并且卡将CP_READ_ONLY_CARD属性报告为 TRUE,则基本CSP/KSP数据缓存是全局缓存。 如果卡是只读的,则基础加密服务提供程序/密钥存储提供程序不会写入 cardcf 文件。 如果卡可以写入基础 CSP/KSP,它将像今天一样运行。
- 有关CP_CARD_CACHE_MODE和CP_CACHE_MODE_GLOBAL_CACHE的详细信息,请参阅 CardGetProperty。
- 当返回的标志是CP_CACHE_MODE_SESSION_ONLY时,基础CSP/KSP会在检测到卡已删除或重新插入时,清除数据缓存。 换句话说,我们定义了一个会话,即卡片插入和删除之间的跨度。
- 缓存还针对每个进程实现,并且不是全局缓存。 此模式专为只读卡片而设计,它们不会在用户的个人电脑上更改,而是在某些政府机构或其他外部站点更改。 (读取/写入卡支持此模式,但我们建议对这些卡使用全局缓存。
- 如果卡是只读的,并且有可能该卡会在用户的电脑上通过除了基础 CSP/KSP 之外的方式更改,则应用程序应使用此规范中稍后介绍的无缓存模式,以避免缓存包含过时数据的可能性。
- 当标志为CP_CACHE_MODE_NO_CACHE时,CSP/KSP 不实现任何数据缓存。 此模式专为不支持写入 cardcf 文件但卡状态可能更改的卡微型驱动程序设计。 卡微型驱动程序决定它是否要在其层中执行任何缓存。
质询/响应机制
卡微型驱动程序接口支持质询/响应身份验证机制。 卡片必须生成一个或多个包含 8 字节的区块的挑战。 身份验证实体通过使用三重 DES (3DES) 加密质询来计算响应,该质询使用 168 位密钥(并忽略奇偶校验位)在 CBC 模式下运行。
卡片使用以下方法之一验证响应:
- 将以前颁发的质询重复进行加密操作,并进行比较结果。
- 解密响应并将结果与挑战进行比较。
如果生成的值相同,身份验证会成功。
卡和身份验证实体都必须使用相同的对称密钥。
以下示例代码详细说明了身份验证实体如何计算响应。 此代码不涵盖任何相关的保证,只是作为示例和指南提供。
#include <windows.h>
#include <wincrypt.h>
#include <winscard.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
int __cdecl wmain(int argc, __in_ecount(argc) WCHAR **wargv)
{
//Acquire the context Use CryptAcquireContext
HCRYPTPROV hProv= 0;
DWORD dwMode=CRYPT_MODE_ECB;
BYTE *pbLocData = NULL,tempbyte;
DWORD cbLocData = 8, count = 0;
HCRYPTKEY hKey = 0;
BYTE rgEncByte [] = {0xA8,0x92,0xD7,0x56,0x01,0x61,0x7C,0x5D };
BYTE DesKeyBlob [] = {
0x08, 0x02, 0x00, 0x00, 0x03, 0x66, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
pbLocData = (BYTE *) malloc (sizeof(BYTE)*cbLocData);
memcpy(pbLocData,rgEncByte,cbLocData);
if (!CryptAcquireContext(
&hProv,
NULL,
L"Microsoft Enhanced Cryptographic Provider V1.0",
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT))
{
printf("Acquire context failed with 0x%08x \n", GetLastError());
goto Cleanup;
}
if (!CryptImportKey(
hProv,
DesKeyBlob,
sizeof(DesKeyBlob),
0,
0,
&hKey ) )
{
printf("Error 0x%08x in importing the 3Des key \n", GetLastError());
goto Cleanup;
}
if (!CryptSetKeyParam(
hKey,
KP_MODE,
(BYTE *)&dwMode,
0))
{
printf("Error 0x%08x in CryptSetKeyParam \n", GetLastError());
goto Cleanup;
}
if (!CryptEncrypt(
hKey,
0,
FALSE,
0,
pbLocData,
&cbLocData,
cbLocData))
{
printf("Error 0x%08x in CryptEncrypt call \n", GetLastError());
goto Cleanup;
}
for (count=0; count < cbLocData; ++count)
{
printf("0x%02x",pbLocData[count]);
}
printf("\n");
Cleanup:
if (hKey)
{
CryptDestroyKey(hKey);
hKey = 0;
}
if (pbLocData)
{
free(pbLocData);
pbLocData = NULL;
}
if (hProv)
{
CryptReleaseContext(hProv,0);
}
return 0;
}
与 msroots 的互作性
msroots 文件是一个用于存储企业信任根证书的 PKCS #7 格式的证书存储。 (该文件是包含空内容的证书和空签名的包,由基础 CSP 编写和读取。卡片微型驱动程序开发人员无需在卡片微型驱动程序中编写任何特殊代码来处理此文件。 在 msroots 文件中存储证书时,CODE_SIGNING EKU 等属性不会传播到智能卡,因为 msroots 文件以不同于计算机存储的格式存储证书。 想要从其他应用程序读取或写入此文件的开发人员可以使用以下示例代码片段来访问数据。
读取操作
if (FALSE == CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
&dbStore,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
NULL,
NULL,
phCertStore,
NULL,
NULL))
{
dwSts = GetLastError();
}
写入操作
// Serialize the store
if (FALSE == CertSaveStore( hCertStore,
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
CERT_STORE_SAVE_AS_PKCS7,
CERT_STORE_SAVE_TO_MEMORY,
&dbStore,
0))
{
dwSts = GetLastError();
goto Ret;
}
dbStore.pbData = CspAllocH(dbStore.cbData);
if (NULL == dbStore.pbData)
{
dwSts = ERROR_NOT_ENOUGH_MEMORY;
goto Ret;
}
if (FALSE == CertSaveStore( hCertStore,
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
CERT_STORE_SAVE_AS_PKCS7,
CERT_STORE_SAVE_TO_MEMORY,
&dbStore,
0))
{
dwSts = GetLastError();
goto Ret;
}
Microsoft 基础智能卡 CSP 的组策略设置
Microsoft基智能卡加密服务提供商的组策略设置位于 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults \Provider\Microsoft Base Smart Card Crypto Provider]。
| Key | Description |
|---|---|
| DefaultPrivateKeyLenBits | dword:00000400 默认密钥生成参数 - 1024 位密钥。 |
| RequireOnCardPrivateKeyGen | dword:000000000 这会设置要求生成卡上私钥(默认值)的标志。 如果设置了此值,则可以将主机上生成的密钥导入卡片。 这用于不支持卡上密钥生成或需要密钥托管的卡。 |
| 交易超时毫秒数 (TransactionTimeoutMilliseconds) | dword:000005dc 1500,1.5 秒是保存卡交易的默认超时。 |
| 允许导入私有签名密钥 | dword:000000000 允许导入签名密钥,即密钥存档方案。 |
| 允许私密交换密钥导入 | dword:000000000 允许导入交换密钥,即密钥存档方案。 |
Microsoft CNG 智能卡 KSP 的组策略设置
Microsoft CNG 智能卡密钥存储提供程序的组策略设置位于 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Providers\Microsoft Smart Card Key Storage Provider]。
| Key | Description |
|---|---|
| DefaultPrivateKeyLenBits | dword:00000400 默认密钥生成参数 - 1024 位密钥。 |
| 卡片私钥生成要求 | dword:000000000 这会设置要求生成卡上私钥(默认值)的标志。 如果设置了此值,则可以将主机上生成的密钥导入卡片。 这用于不支持卡上密钥生成或需要密钥托管的卡。 |
| 事务超时毫秒数 | dword:000005dc 1500,1.5 秒是保存卡交易的默认超时。 |
| 允许私有签名密钥导入 | dword:000000000 允许导入签名密钥,即密钥存档方案。 |
| 允许私有交换密钥导入 | dword:000000000 允许导入交换密钥,即密钥存档方案。 |
| 分配私有ECDHE密钥导入 (AllocPrivateECDHEKeyImport) | dword:000000000 允许导入 ECDH 密钥,即密钥存档方案 |
| 允许私有ECDSA密钥导入 | dword:000000000 允许导入 ECDSA 密钥,即密钥存档方案 |
特殊注意事项
在 Windows Vista Service Pack 1(SP1)中,当操作系统以安全模式运行时,除 Windows 登录之外,无法进行需要 PIN 的智能卡操作。
使用以下标志之一调用 CryptAcquireContext 会提示使用 USER_PIN 进行 PIN 身份验证,而不考虑分配给容器的实际 PIN:
- CRYPT_NEWKEYSET
- 可选的默认加密容器
- CRYPT_DELETEKEYSET
- CRYPT_VERIFYCONTEXT
即使在使用 DLL_PROCESS_DETACH 调用 DllMain 之后,也可以调用 CardDeleteContext。