EternalWindows
スマートカード / スマートカードサブシステム

あるベンダーによって販売されたカードが、そのベンダーのカードリーダで動作するのは当然といえますが、 できれば他のベンダーのカードリーダにおいても動作を期待したいものです。 こうした、各ベンダーのカードやカードリーダをWindowsで相互運用するための規格として、 PC/SC(Personal Computer / Smart Card)があります。 この規格は、先に述べたカードの物理的な相互運用の他、 次のようなスマートカードサブシステムを形成しています。

図からもわかるように、PC/SCにおけるスマートカードの扱いは、 常にリソースマネージャを介する必要があります。 リソースマネージャにアクセスするためには、 WinSCard APIを直接呼び出す方法やユーザーインターフェースを経由する方法、 さらにSmart Card Service Providerを経由する方法などがありますが、 ベンダーによってはCSPやPKCS #11ライブラリからのアクセスも提供していることもあります。 スマートカードサブシステムを構成する各コンポーネントの役割を示します。

コンポーネント 説明
User Interface scarddlg.dllのことであり、リーダにカードのセットを促すためのダイアログを表示する。 また、内部でWinSCard APIを呼び出して、カードへの接続も行う。
Smart Card Service Provider SCSPとも呼ばれ、COMインターフェースからカードへのアクセスを提供するDLL。 これは、ベンダーが独自に提供することもできるが、 システムにデフォルトで存在するSCSPを利用することもできる。 しかし、Windows Vistaからはシステム提供のSCSPは廃止され、 ベンダー独自のSCSPを作成することも推奨されなくなった。
WinSCard API winscard.dllのことであり、Smart Card Resource Managerへのアクセスを提供する。 WinSCard APIは、Smart Card Resource Manager APIと呼ばれることもある。
Smart Card Resource Manager 複数のアプリケーションに対してリソースを割り当て、 トランザクションをサポートする。 Smart Card Resource Managerは、Smart Cardという名前を持ったサービスであり、 このサービスは必ず開始されている必要がある。
Reader Driver カードリーダを動作させるためのドライバであり、原則ベンダーが用意する。 これは、ドライバが存在しなければリーダが動作できないことを意味するが、 これではリーダを扱えるシステムが限定されてしまうという問題がある。 こうした問題を回避するために、USB CCID仕様というものが存在し、 この仕様に準拠したリーダにはベンダー固有のドライバは必要ない。 システムによって提供されている、usbccid.sysというドライバが利用されるからである。

本当の意味でのカードの相互運用というのは、物理的な側面と内部的な側面のどちらも考慮しておくべきといえます。 物理的な側面というのは先に述べたPC/SCのことであり、内部的な側面というのはカードに発行するコマンドのことです。 カードがISO 7816-4やJICSAP仕様 2.0に準拠したコマンドを理解できるのであれば、 アプリケーションはカード毎にコマンドを変えることなくアクセスできるようになります。 カードの物理的な性質はISO 7816-1/2/3に規格されており、 PC/SCはこれに準拠しています。

WinSCard APIでリソースマネージャと通信するためには、 最初にSCardEstablishContextでリソースマネージャに接続する必要があります。

LONG SCardEstablishContext(
  DWORD dwScope,
  LPCVOID pvReserved1,
  LPCVOID pvReserved2,
  LPSCARDCONTEXT phContext
);

dwScopeは、基本的にSCARD_SCOPE_USERを指定します。 pvReserved1は、予約されているためNULLを指定します。 pvReserved2も予約されているためNULLを指定します。 phContextは、リソースマネージャとの接続を確立したハンドルが返ります。 関数が成功した場合、SCARD_S_SUCCESSが返ることになります。

リソースマネージャとの接続に成功すれば、 SCardListReadersで現在接続されているカードリーダの名前を取得することができます。 これは、カードに接続する関数を呼び出す場合に必要になります。

LONG SCardListReaders(
  SCARDCONTEXT hContext,
  LPCTSTR mszGroups,
  LPTSTR mszReaders,
  LPDWORD pcchReaders
);

hContextは、リソースマネージャのハンドルを指定します。 0を指定した場合は、インストールされているリーダ名が列挙されることになります。 mszGroupsは、リーダが所属するグループを指定します。 グループの概念は、リーダ名を列挙する場合に意味をもつものと思われるため、 現在接続されているリーダ名を取得する場合はNULLで問題ありません。 NULLはSCARD_ALL_READERSと解釈され、これは全てのリーダが所属します。 mszReadersは、リーダの名前を受けとる変数のポインタまたは、バッファを指定します。 pcchReadersは、SCARD_AUTOALLOCATEという定数、 またはバッファのサイズを格納した変数のアドレスを指定します。

SCARD_AUTOALLOCATEを指定した場合、必要なメモリがWinSCardAPIによって確保されるため、 不要になった時点でSCardFreeMemoryによって開放することになります。

LONG SCardFreeMemory(
  SCARDCONTEXT hContext,
  LPCVOID pvMem
);

hContextは、リソースマネージャのハンドルを指定します。 pvMemは、開放したいメモリを指定します。

リソースマネージャの接続が不要になった場合は、SCardReleaseContextを呼び出します。 この関数を呼び出した場合、SCARD_AUTOALLOCATEを指定したことによって確保されたメモリも開放されます。

LONG SCardReleaseContext(
  SCARDCONTEXT hContext
);

hContextは、リソースマネージャのハンドルを指定します。

今回のプログラムは、システムに接続されているカードリーダの名前を取得します。 WinScard APIを使用する場合は、winscard.hとインクルードとwinscard.libへのリンクが必要になります。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	SCARDCONTEXT hContext;
	LPTSTR       lpszReaderName;
	LONG         lResult;
	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, SCARD_ALL_READERS, (LPTSTR)&lpszReaderName, &dwAutoAllocate);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_E_NO_READERS_AVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが接続されていません。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	MessageBox(NULL, lpszReaderName, TEXT("OK"), MB_OK);

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

	return 0;
}

SCardEstablishContextが失敗した場合、SCARD_E_NO_SERVICEが返ることがあります。 これはリソースマネージャのサービスが起動されていないことを意味するため、 その旨を表示しています。 SCardListReadersでは、第4引数に指定している変数にSCARD_AUTOALLOCATEを指定しているため、 内部でメモリを確保されることになり、そのメモリを第3引数で指すようになります。 ちなみに、バッファを予め確保しておく場合は次のようになります。

TCHAR szReaderName[256];
DWORD dwAutoAllocate = sizeof(szReaderName);

SCardListReaders(hContext, SCARD_ALL_READERS, szReaderName, &dwAutoAllocate);

第4引数に指定する変数にバッファのサイズを格納しておきます。 これにより、szReaderNameにリーダの名前が格納されることになります。 この方法の場合は、SCardFreeMemoryを呼び出す必要はありません。

リソースマネージャの起動を検出する

スマートカードを利用するアプリケーションは、常にリソースマネージャを必要とします。 リソースマネージャは、Smart Cardという名前を持ったサービスであり、 これが予め起動されていなければアプリケーションは動作することができません。 もし、ユーザーにサービスの起動を求めるのであれば、その起動を検出する処理が必要となりますが、 これはWindows XPから登場したSCardAccessStartedEventを呼び出すことで実現できます。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD  dwResult;
	HANDLE hEvent;
	
	hEvent = SCardAccessStartedEvent();

	dwResult = WaitForSingleObject(hEvent, 5000);
	if (dwResult == WAIT_OBJECT_0)
		MessageBox(NULL, TEXT("Smart Cardサービスが起動されました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("Smart Cardサービスが起動されませんでした。"), NULL, MB_ICONWARNING);
	
	SCardReleaseStartedEvent();

	return 0;
}

SCardAccessStartedEventは、イベントオブジェクトのハンドルを返すようになっています。 これがシグナル状態になれば、Smart Cardサービスが起動されたことになるため、 WaitForSingleObjectに指定して待機することになります。 戻り値がWAIT_OBJECT_0である場合は、シグナル状態になったことによって関数が制御を返したことを意味し、 それ以外の場合はタイムアウト(上記の例では5秒)などで関数が制御を返したことを意味します。 SCardAccessStartedEventが返したイベントオブジェクトは、CloseHandleに指定する必要はなく、 SCardReleaseStartedEventを呼び出すだけで閉じられることになります。



戻る