EternalWindows
スマートカード / スマートカードの検出

スマートカードを利用するアプリケーションが動作するためには、 システムにカードリーダが接続されているのはもちろんのこと、 そのカードリーダにカードがセットされている必要があります。 もし、カードがセットされていないような場合に、 ユーザーに対してカードのセットを促すメッセージを表示するのであれば、 カードリーダの状態変化を検出する処理は必須となるでしょう。 これには、SCardGetStatusChangeを呼び出します。

LONG SCardGetStatusChange(
  SCARDCONTEXT hContext,
  DWORD dwTimeout,
  LPSCARD_READERSTATE rgReaderStates,
  DWORD cReaders
);

hContextは、リソースマネージャのハンドルを指定します。 dwTimeoutは、何らかの変化が生じまるで待機する時間をミリ秒単位で指定します。 INFINITEを指定した場合は、タイムアウトは発生せず、変化が生じまるで待機します。 rgReaderStatesは、リーダの現在の状態を表すSCARD_READERSTATE構造体を指定します。 cReadersは、rgReaderStatesの要素数を指定します。

SCARD_READERSTATE構造体は、次のように定義されています。

typedef struct {
  LPCTSTR szReader;
  LPVOID pvUserData;
  DWORD dwCurrentState;
  DWORD dwEventState;
  DWORD cbAtr;
  BYTE rgbAtr[36];
} SCARD_READERSTATE,  *PSCARD_READERSTATE,  *LPSCARD_READERSTATE;

szReaderは、状態変化の検出対象とするリーダの名前を指定します。 pvUserDataは、アプリケーション固有のデータを指定します。 通常、SCardGetStatusChangeは、待機する関係上ワーカースレッドで実行されるため、 メインスレッドと何らかの通信が必要な場合は、この引数を利用します。 dwCurrentStateは、リーダの現在の状態を指定します。 dwEventStateは、リーダに生じた変化の内容を表す値が格納されます。 cbAtrは、既にセットされている、または新しくセットされたカードのATRのサイズが格納されます。 rgbAtrは、既にセットされている、または新しくセットされたカードのATRが格納されます。

今回のプログラムは、リーダにカードがセットされていない場合、 カードのセットをユーザーに促します。 そして、リーダの状態変化を検出します。

#include <windows.h>
#include <winscard.h>

#pragma comment (lib, "winscard.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	SCARDCONTEXT      hContext;
	LONG              lResult;
	SCARD_READERSTATE readerState;
	LPTSTR            lpszReaderName;
	DWORD             dwAutoAllocate = SCARD_AUTOALLOCATE;

	lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_E_NO_SERVICE)
			MessageBox(NULL, TEXT("Smart Cardサービスが起動されていません。"), NULL, MB_ICONWARNING);
		return 0;
	}

	lResult = SCardListReaders(hContext, NULL, (LPTSTR)&lpszReaderName, &dwAutoAllocate);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_E_NO_READERS_AVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが接続されていません。"), NULL, MB_ICONWARNING);
		SCardReleaseContext(hContext);
		return 0;
	}

	readerState.szReader = lpszReaderName;
	readerState.dwCurrentState = SCARD_STATE_UNAWARE;

	lResult = SCardGetStatusChange(hContext, 0, &readerState, 1);
	if (lResult != SCARD_S_SUCCESS) {
		SCardFreeMemory(hContext, lpszReaderName);
		SCardReleaseContext(hContext);
		return 0;
	}

	if (readerState.dwEventState & SCARD_STATE_EMPTY) {
		MessageBox(NULL, TEXT("カードをセットしてください。"), TEXT("OK"), MB_OK);
		readerState.dwCurrentState = readerState.dwEventState;
		lResult = SCardGetStatusChange(hContext, INFINITE, &readerState, 1);
		if (readerState.dwEventState & SCARD_STATE_PRESENT)
			MessageBox(NULL, TEXT("カードがセットされました。"), TEXT("OK"), MB_OK);
		else if (readerState.dwEventState & SCARD_STATE_UNAVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが外されました。"), NULL, MB_ICONWARNING);
		else
			;
	}
	else if (readerState.dwEventState & SCARD_STATE_PRESENT) {
		MessageBox(NULL, TEXT("カードは既にセットされています。"), TEXT("OK"), MB_OK);
	}
	else
		;

	SCardFreeMemory(hContext, lpszReaderName);
	SCardReleaseContext(hContext);

	return 0;
}

SCardGetStatusChangeは2回呼び出します。 1回目の呼び出しは、現在のリーダの状態を知ることが目的であり、 dwCurrentStateにSCARD_STATE_UNAWAREを指定しています。 この定数は現在のリーダの状態は分からないという意味であり、 これを指定した場合は関数が待機することなく、 単純にdwEventStateが初期化されることになります。 また、第2引数のタイムアウトに関する引数は考慮されません。 dwEventStateに格納されるのはリーダに生じた変化を示す値であり、 これは現在のリーダの状態を表していると考えることもできます。 SCARD_STATE_PRESENTが含まれる場合、リーダにカードがセットされていることを意味します。 等しいかどうかで判断してはならないのは、dwEventStateにSCARD_STATE_CHANGEDという定数が常に含まれるためです。 2回目のSCardGetStatusChangeの呼び出しは、次の要領で行われています。

if (readerState.dwEventState & SCARD_STATE_EMPTY) {
	MessageBox(NULL, TEXT("カードをセットしてください。"), TEXT("OK"), MB_OK);
	readerState.dwCurrentState = readerState.dwEventState;
	lResult = SCardGetStatusChange(hContext, INFINITE, &readerState, 1);
	if (readerState.dwEventState & SCARD_STATE_PRESENT)
		MessageBox(NULL, TEXT("カードがセットされました。"), TEXT("OK"), MB_OK);
	else if (readerState.dwEventState & SCARD_STATE_UNAVAILABLE)
		MessageBox(NULL, TEXT("カードリーダが外されました。"), NULL, MB_ICONWARNING);
	else
		;
}

dwEventStateにSCARD_STATE_EMPTYが含まれる場合、 リーダにカードがセットされていないことを意味します。 よって、このときにカードのセットを促すメッセージを表示します。 その後、リーダの変化を検出するためにSCardGetStatusChangeを呼び出すことになるのですが、 事前にdwCurrentStateにカードの現在の状態を指定しておく必要なります。 現在の状態というのはカードがセットされていない状態、 つまりSCARD_STATE_EMPTYということですが、この定数を直接指定してはならないので注意してください。 SCardGetStatusChangeから制御が返った場合、dwEventStateには新しいリーダの状態が含まれており、 これがSCARD_STATE_PRESENTであるならば、既に述べたようにカードがセットされていることを意味します。 一方、SCARD_STATE_UNAVAILABLEが含まれる場合は、リーダが外されたことを意味します。

目的のカードであるかの確認

スマートカードを利用するアプリケーションでは、 ある特定のカードのみを扱いたいような場合があるかもしれません。 たとえば、そのカードのATRを予め知っているような場合、 SCardGetStatusChangeで初期化されたSCARD_READERSTATE.rgbAtrと比較する方法が成立しますが、 実際にはもう少し簡単な方法があります。 SCardLocateCardsを呼び出せば、カード名とATRの比較が可能になります。

readerState.szReader = lpszReaderName;

SCardLocateCards(hContext, szCardName, &readerState, 1);
if (readerState.dwEventState & SCARD_STATE_ATRMATCH)
	MessageBox(NULL, TEXT("指定したカード名のカードです。"), TEXT("OK"), MB_OK);

SCardLocateCardsは、リーダにセットされているカードのATRを取得し、 それが第2引数に指定されたカード名とマッチするかを調べます。 より具体的には、スマートカードデータベースに書き込まれている各カード情報を走査し、 ATRエントリに指定されている値がカードのATRと一致しているかを調べるのです。 このため、第2引数に指定するカード名は、 スマートカードデータベースに書き込まれているものでなければなりません。 第3引数にはSCARD_READERSTATE構造体を指定しますが、szReaderを初期化するだけで問題ありません。 dwEventStateにSCARD_STATE_ATRMATCHが含まれる場合は、 カード名とカードのATRがマッチしたことを意味します。



戻る