Scard API 智能卡操作
一、概述
ICC是Integrated Circuit Card的缩写,意思是集成电路卡,我们通常把它称为智能卡(Smart Card)。智能卡应用广泛,它可以用来保存私人密码、银行账号、个人资料等。那么如何编写应用程序,从智能卡上读出或向其写入信息呢?其实在Windows 98或Windows NT 4.0及以上版本的Windows操作系统中,就已经能够解决该问题了。本文将就此展开讨论。
二、智能卡了系统
智能卡子系统(Smart Card Subsystem)的基本组件包括:
1.资源管理器(Resource Manager),它使用Win32 API。
2.用户界面(User Interface, UI)。
3.一些基本服务提供者(Base Service Provider),它们提供了对特定服务的访问。与资源管理器不同,服务提供者使用COM接口模型而不是Win32 API。
下图演示了这些组件在智能卡系统中的关系。
三、子系统中的部件
1.智能卡读卡机
智能卡读卡机用来从智能卡中读取或写入信息。读卡机能够被划分为逻辑上的“组”,这些组称为读卡机组(Reader Group)。这些组可以由智能卡子系统定义,也可以由管理员和用户定义。这里将介绍一些管理读卡机组的函数。一台读卡机能够属于一个或多个组。智能卡子系统已经定义了下列两个组:
Scard$AllReaders:这个组包括系统中所有的读卡机。当一个新的读卡机被添加到智能卡子系统时,它就会被自动添加到该组中。
Acard$DefaultReaders:这是个默认的组,每个终端对应一个这样的组,它包括所有被分配到终端的没有留作特殊用途的读卡机
2.智能卡接口
一个智能卡接口包括:智能卡中一组预定义的可用服务、用来调用这些服务的协议、以及所有关于这些服务描述表的假定。智能卡接口类似于COM中使用的接口,也是由GUID标识的。
3.将智能卡安装到系统中
智能卡必须安装到系统中,这样智能卡子系统才能找到它。安装过程通常由智能卡厂商提供的安装工具来完成。安装工具必须提供下列有关智能卡的信息:
(1)智能卡的ATR字符串和一个可选的标记,用来标识智能卡。
(2)被智能卡支持的一组智能卡接口的列表。
(3)智能卡的友好名称,用于将智能卡标识给用户。
(4)智能卡附带的主要服务提供者(可选),在通过COM接口访问智能卡时使用。
为了简化安装、并保证智能卡数据库(Smart Card Database)的完整性,智能卡子系统提供了两个辅助函数:ScardIntroduceCardType将一个智能卡引入数据库;ScardForgetCardType将一个智能卡从数据库中删除。
目的<TBODY> | 调用函数 |
智能卡数据库查询函数 用来查询智能卡数据库。 | |
获取给定的智能卡的主要服务提供者的标识符(GUID)。 | SCardGetProviderId |
获取以前被某个用户引入系统的所有智能卡列表。 | SCardListCards |
获取由一个给定的智能卡提供的接口的标识符(GUID)。 | SCardListInterfaces |
获取以前被引入系统的读卡机组列表。 | SCardListReaderGroups |
获取某个读卡机组中的读卡机的列表。 | SCardListReaders |
智能卡数据库管理函数 用来管理智能卡数据库,并使用特定的资源管理器描述表更新数据库。 | |
向一个读卡机组中添加一个读卡机。 | SCardAddReaderToGroup |
从系统中删除一个智能卡。 | SCardForgetCardType |
从系统中删除一个读卡机。 | SCardForgetReader |
从系统中删除一个读卡机组。 | SCardForgetReaderGroup |
向系统中引入一个新卡。 | SCardIntroduceCardType |
向系统引入一个新读卡机。 | SCardIntroduceReader |
向系统引入一个新读卡机组。 | SCardIntroduceReaderGroup |
从一个读卡机组中删除一个读卡机。 | SCardRemoveReaderFromGroup |
资源管理器描述表函数 用来建立并释放被数据库查询、管理函数所使用的资源管理器描述表。 | |
为访问智能卡数据库建立一个描述表。 | SCardEstablishContext |
关闭一个已经建立的描述表。 | SCardReleaseContext |
资源管理器支持函数 用来释放通过使用SCARD-AUTOALLOCATE标识符分配的内存,简化了其他资源管理器函数的使用。 | |
释放通过使用SCARD-AUTOALLCATE返回的内存。 | SCardFreeMemory |
智能卡追踪函数 用来寻找读卡机内的卡。 | |
寻找一个ATR字符串与提供的智能卡名称相符的卡。 | SCardLocateCards |
执行块,直到智能卡可用性改变为止。 | SCardGetStatusChange |
结束未完成的操作。 | SCardCancel</TBODY> |
智能卡及读卡机访问函数 用以连结到一个特定的智能卡并与之通讯。 | |
连接到一张卡。 | SCardConnect |
重新建立连接。 | SCardReconnect |
结束一个连接 | SCardDisconnect |
启动一个事务,阻止其它应用程序访问智能卡。 | SCardBeginTransaction |
结束一个事务,允许其它引用程序访问智能卡。 | SCardEndTransaction |
提供某个读卡机目前的状态。 | SCardStatus |
智能卡及读卡机访问函数 用以连结到一个特定的智能卡并与之通讯。 | |
连接到一张卡。 | SCardConnect |
重新建立连接。 | SCardReconnect |
结束一个连接。 | SCardDisconnect |
启动一个事务,阻止其它应用程序访问智能卡。 | SCardBeginTransaction |
结束一个事务。允许其它引用程序访问智能卡。 | SCardEndTransaction |
提供某个读卡机目前的状态。 | SCardStatus |
4.访问智能卡
智能卡子系统为应用程序及服务提供者提供了与智能卡连结的一些方法:
(1)应用程序可以调用ScardConnect函数来连结到一个放置在给定的读卡机里的卡。这是与智能卡建立通讯的最简单的方法。
(2)应用程序可以在一个给定的读卡机组中搜索一个特定的智能卡。应用程序使用智能卡的友好名称标识该卡,并指定一个该智能卡可能放入的读卡机组列表。资源管理器使用ATR字符串,在读卡机列表中搜索所有与给定智能卡相符的卡,并将该卡的壮态信息返回给应用程序。智能卡子系统从不提供CUI,也不在获取ATR字符串之后提供与智能卡的交互。但它们能够为用户定位在他们需要的智能卡或卡的类型上。这导致了将一个申请映射到一个指定的读卡机,并可以进一步映射到I/O的定向。
(3)应用程序能够申请一个支持某些给定智能卡接口的智能卡列表。应用程序可以在前面的情况中使申请服务并从一个使用T=0、T=1以及原始协议的卡上返回数据。
(T=0协议是一个异步的面向字符的半双工传输协议;T=1协议是一个异步的ScardTransmit面向块的半双工传输协议)
用该列表。这使得应用程序能够通过查询智能卡的功能找到相应的智能卡,而不需要知道它们的名称。
当应用程序查找一张卡时,它提供一组可能放有指定智能卡的读卡机名称。对于每一个在该序列中的读卡机,资源管理器提供下列信息:
1. 该读卡机是否能够被这个程序使用。
2. 是否有卡插入该读卡机,如果有的话,它的ATR字符串是什么。
3. 找到的智能卡ATR字符串是否符合所申请的智能卡ATR字符串。
应用程序使用返回的信息来进一步提供智能卡过滤装置,对所有找到的智能卡进行过滤,或者提示用户选择需要的智能卡。
注意:如果一个或多个读卡机列表已经被其他应用程序以独占方式打开,那么访问这些列表上的读卡机将会失败。
四、智能卡资源管理器
智能卡资源管理器(Smart Card Resource Manager)管理对读卡机和智能卡的访问。要管理这些资源,必须执行下列三个操作:
1. 识别并跟踪资源。
2. 在多个应用程序中分配读卡机和资源。
3. 为在一个给定卡上访问可能的服务支持原始事务处理。
注意:因为现有的智能卡是单线程设备,它们经常需要执行多个命令来完成一个单一功能。事务处理程序允许多个命令不中断地执行,并确定媒介状态信息没有被破坏。
资源管理器能够被资源管理器API直接访问,或者被任何智能卡服务提供者间接访问。资源管理器API是一组Win32函数,它们提供了直接对资源管理器服务的访问,但智能卡服务提供者则使用COM接口。资源管理器API中有很多Win32函数等同于智能卡服务提供者COM接口中的属性(Property)和方法(Method)。但是有些功能只有Win32函数提供。比如,当应用程序要操作智能卡数据库中的读卡机列表或读卡机组列表,或需要直接控制一个读卡机时,就必须使用资源管理器API。
资源管理器作为一个可信的服务运行在一个单进程中。所有智能卡访问的申请都被提交到资源管理器,由资源管理器将它们发送到包含目标卡的读卡机。智能卡经常被使用在机密和有关个人隐私的场合。无论什么情况下,当访问一个读卡机或智能卡时,资源管理器都使用现有操作系统底层的安全装置。
下表列出了智能卡资源管理器API中主要的函数以及使用这些函数的作用和目的。
五、用户界面
智能卡用户界面是一个单一的通用对话框,它让用户指定或者选择一个智能卡并将其打开(也就是连接到该智能卡,并在应用程序中使用它)。下面提供了使用该通用对话框的两种方法。
1.让用户自己选择一个智能卡并将其打开
(1)声明一个OPENCARDNAME类型的变量。
(2)指定lpstrGroupNames、lpstrCardNames、dwShareMode以及dwProtocols OPENCARDNAME的值,缩小智能卡的查询范围。
(3)调用GetOpenCardName函数,向用户显示通用对话框。事下图所示:
(4)用户选择一张卡,单南OK,就可以连接到该智能卡。
2.通过使用回叫函数查找某张特定的卡
(1)声明一个OPENCARDNAME类型的变量。
(2)指定lpstrGroupNames和lpstrCardNames的值,缩小智能卡的查询范围。
(3)寻立Connect、Check以及Disconnect回叫函数并适当设置lpfnConnect、lpfnCheck以及lpfnDisconnect OPENCARDNAME数据成员。
(4)调用GetOpenCardName通用对话框函数。
(5)通用对话框将寻找申请的卡。如果一张匹配的卡的名称或ATR字符串被找到了,那么Connect、check以及Disconnect回叫函数将会被依次调用。如果一张智能卡将例程传递给Check(也就是说,Check回叫函数返加TRUE),那么这张卡将会被高亮显示给用户。
(6)如果没有找到匹配的卡,那么上面的通用对话框将会出现。
scard API 操作错误码:
#region Error Codes |
public const int SCARD_F_INTERNAL_ERROR = -2146435071; |
public const int SCARD_E_CANCELLED = -2146435070; |
public const int SCARD_E_INVALID_HANDLE = -2146435069; |
public const int SCARD_E_INVALID_PARAMETER = -2146435068; |
public const int SCARD_E_INVALID_TARGET = -2146435067; |
public const int SCARD_E_NO_MEMORY = -2146435066; |
public const int SCARD_F_WAITED_TOO_LONG = -2146435065; |
public const int SCARD_E_INSUFFICIENT_BUFFER = -2146435064; |
public const int SCARD_E_UNKNOWN_READER = -2146435063; |
public const int SCARD_E_NO_READERS_AVAILABLE = -2146435026; |
public const int SCARD_E_TIMEOUT = -2146435062; |
public const int SCARD_E_SHARING_VIOLATION = -2146435061; |
public const int SCARD_E_NO_SMARTCARD = -2146435060; |
public const int SCARD_E_UNKNOWN_CARD = -2146435059; |
public const int SCARD_E_CANT_DISPOSE = -2146435058; |
public const int SCARD_E_PROTO_MISMATCH = -2146435057; |
public const int SCARD_E_NOT_READY = -2146435056; |
public const int SCARD_E_INVALID_VALUE = -2146435055; |
public const int SCARD_E_SYSTEM_CANCELLED = -2146435054; |
public const int SCARD_F_COMM_ERROR = -2146435053; |
public const int SCARD_F_UNKNOWN_ERROR = -2146435052; |
public const int SCARD_E_INVALID_ATR = -2146435051; |
public const int SCARD_E_NOT_TRANSACTED = -2146435050; |
public const int SCARD_E_READER_UNAVAILABLE = -2146435049; |
public const int SCARD_P_SHUTDOWN = -2146435048; |
public const int SCARD_E_PCI_TOO_SMALL = -2146435047; |
public const int SCARD_E_READER_UNSUPPORTED = -2146435046; |
public const int SCARD_E_DUPLICATE_READER = -2146435045; |
public const int SCARD_E_CARD_UNSUPPORTED = -2146435044; |
public const int SCARD_E_NO_SERVICE = -2146435043; |
public const int SCARD_E_SERVICE_STOPPED = -2146435042; |
public const int SCARD_W_UNSUPPORTED_CARD = -2146435041; |
public const int SCARD_W_UNRESPONSIVE_CARD = -2146435040; |
public const int SCARD_W_UNPOWERED_CARD = -2146435039; |
public const int SCARD_W_RESET_CARD = -2146435038; |
//From SCARD_W_REMOVED_CARD to SCARD_E_DIR_NOT_FOUND |
public const int SCARD_E_DIR_NOT_FOUND = -2146435037; |
public const int SCARD_W_REMOVED_CARD = -2146434967; |
#endregion |
一个使用例子:
SCARDCONTEXT m_hContext = 0; //读卡器是否存在句柄
SCARDHANDLE m_hCard = 0; //具体的读卡器通讯句柄
char mszReaders[ 100 ] = "";
void CDebugpcsccardDlg::OnButton1()
{
//相当于你的connect
//以下是所有的代码
//DisConnectCard(); //调试注释
//char s2[10];
//char s1[10];
//m_isCardConnected = true; //调试注释
LONG res;
//建立设备上下文
res = ::SCardEstablishContext(SCARD_SCOPE_USER,NULL,NULL,&m_hContext);
if(res != SCARD_S_SUCCESS)
{
//m_isCardConnected = false;
//errorMessage-> setErrorMessage("ConnectCard","建立设备上下文失败!","Error");
m_hContext = 0;
return;
//return false;
}
LPTSTR pmszReaders = NULL;
LPTSTR pReader = NULL;
DWORD cch = SCARD_AUTOALLOCATE;
DWORD dwAP;
bool bConnected = false;
//为了简化过程直接使用第一个读卡器
/\\\\\\\\\\\\\\\\\\\\\\\\\*
//如果传入的参数sReader为空,则默认选择一个读卡器
if(NULL == m_pReader)
{
res = ::SCardListReaders(m_hContext,NULL,(LPTSTR)&pmszReaders,&cch);
if(res == SCARD_S_SUCCESS)
res =::SCardConnect(m_hContext,pmszReaders,SCARD_SHARE_EXCLUSIVE,SCARD_PROTOCOL_T0 |SCARD_PROTOCOL_T1,&m_hCard,&dwAP);
}
else
{
res = ::SCardConnect(m_hContext,m_pReader,SCARD_SHARE_EXCLUSIVE,SCARD_PROTOCOL_T0 |SCARD_PROTOCOL_T1,&m_hCard,&dwAP);
}
*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\/
res = ::SCardListReaders(m_hContext,NULL,(LPTSTR)&pmszReaders,&cch);
if(res == SCARD_S_SUCCESS)
{
sprintf( mszReaders, "%s", pmszReaders );
SCardFreeMemory( m_hContext, pmszReaders );
OnButton2();
res = ::SCardConnect(m_hContext,mszReaders,SCARD_SHARE_EXCLUSIVE,SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1,&m_hCard,&dwAP);
if(res != SCARD_S_SUCCESS)
{
//m_isCardConnected = false;
//连接读卡器失败,释放设备上下文
::SCardReleaseContext(m_hContext);
m_hContext = 0;
m_hCard = 0;
//errorMessage-> setErrorMessage("ConnectCard","连接读卡器失败","Error");
}
//sprintf(s1,"%d",res);
//MessageBox(NULL,s1,s1,MB_OK);
}
return;
}
void CDebugpcsccardDlg::OnButton2()
{
//相当于你的disconnect
::SCardDisconnect(m_hCard,SCARD_EJECT_CARD);
}
void CDebugpcsccardDlg::OnButton3()
{
//相当于你的status
unsigned char pbAtr[ 100 ] = "";
DWORD dwState, dwProtocol;
DWORD dd = sizeof( mszReaders );
DWORD dwAtrLen = sizeof( pbAtr );
LONG result = ::SCardStatus( m_hCard, mszReaders, &dd, &dwState, &dwProtocol, pbAtr,&dwAtrLen);
if(result != SCARD_S_SUCCESS){
//errorMessage-> setErrorMessage("GetCardStatus","获取读卡器状态失败","Error");
//return false;
return;
}
char string[ 100 ] = "";
char tmp[ 10 ] = "";
for( int i = 0; i < ( int )dwAtrLen; i++ )
{
sprintf( tmp, "%.2X ", pbAtr[ i ] );
strcat( string, tmp );
}
AfxMessageBox( string );
//return true;
return;
}