EternalWindows
スマートカード / スマートカードとCSP

スマートカードに格納されるデータは、カード所有者の個人情報などが多いと思われますが、 Windowsで利用可能なセキュリティデータが格納されることもあります。 たとえば、CryptoAPIで利用される公開鍵/秘密鍵の鍵ペアがその好例といえるでしょう。 CryptoAPIは、鍵ペアの格納場所を鍵コンテナという抽象化された名前で表現しており、 実際に鍵ペアをどこに格納するかはCSPが自由に決定してよいことになっています。 つまり、スマートカードに鍵ペアを格納するCSPが存在すれば、 アプリケーションはCryptoAPIを通じてスマートカードに鍵ペアを格納することができるようになります。 この点を考慮して、多くのカードベンダーは自社カード用のCSPを用意しています。

CryptoAPIにベンダーのCSP名を指定した場合、ベンダーのCSPに処理が渡ることになります。 ここで、ベンダーのCSPはWinSCard APIを呼び出して、鍵ペアの保存やアクセスを行います。 一方、CryptoAPIにBace CSPの名前を指定した場合、Bace CSPに処理が渡ることになります。 Bace CSPはSmart Card Moduleとも呼ばれ、 正式名称はMicrosoft Base Smart Card Cryptographic Service Providerです(以後、MS_SCARD_PROVと表記)。 このCSPはシステムに用意されており、 内部でCard MiniDriverと呼ばれるDLLの関数を呼び出しています。 Card MiniDriverはCSPと同じくベンダーが用意するものであり、 内部でWinSCard APIを呼び出す設計になっています。 以上のことからスマートカードへのアクセスには、 ベンダーのCSPを経由する方法とBase CSPを経由する方法の2種類が存在し、 そのどちらかを利用するかは、ベンダーがCSPを用意しているか、 あるいはCard MiniDriverを用意しているかで変化します。 まずは、前者の利用から検討していきます。

CryptoAPIを利用した既存のアプリケーションをスマートカードに対応させることは、 それほど難しいことではありません。 単純に、CryptAcquireContextにベンダーのCSP名を指定するようにすれば、鍵ペアはカード内に作成されることになります。 カードに対応するCSPはベンダーが用意したインストーラーによって、 スマートカードデータベースに書き込まれていると思われます。

SmartCardsキーの下に存在するサブキーの名前はカード名であり、 そのカードに関する情報を維持しています。 想像がつくように、Crypto Providerというエントリに書き込まれている文字列が、 カードにアクセスできるCSPの名前となります。 当然ながら、このCSPは既にインストールされているはずですから、 CryptEnumProvidersを呼び出せばその存在を確認できるはずです。 また、Crypto Providerエントリの値がMicrosoft Base Smart Card Crypto Providerとなっている場合は、 CSPの代わりにCard MiniDriverが用意されていることになります。 この場合、80000001という名前のエントリにCard MiniDriverの名前が書き込まれているはずです。

アプリケーションからカードに対応するプロバイダを取得するには、 SCardGetCardTypeProviderNameを呼び出すことになります。

LONG SCardGetCardTypeProviderName(
  SCARDCONTEXT hContext,
  LPCTSTR szCardName,
  DWORD dwProviderId,
  LPTSTR szProvider,
  LPDWORD *pcchProvider
);

hContextは、リソースマネージャのハンドルを指定します。 szCardNameは、カードの名前を指定します。 この名前は、スマートカードデータベースに存在している必要があります。 dwProviderIdは、取得するプロバイダの種類を表す定数を指定します。 szProviderは、プロバイダの名前を受け取るポインタ、またはバッファを指定します。 pcchProviderは、szProviderに指定したバッファのサイズを格納する変数のアドレス、 またはSCARD_AUTOALLOCATEを指定します。

dwProviderIdに指定できる定数を次に示します。

定数 意味
SCARD_PROVIDER_CSP Crypto Providerエントリを参照して、CSPの名前を取得する。
SCARD_PROVIDER_PRIMARY Primary Providerエントリを参照して、SCSPのGUID文字列を取得する。GUIDを文字列としてではなく、 構造体として取得したい場合は、SCardGetProviderIdを呼ぶようにする。
SCARD_PROVIDER_CARD_MODULE
(0x80000001)
80000001エントリを参照して、Card MiniDriverの名前を取得する。

SCARD_PROVIDER_CARD_MODULEという定数は、他の定数のようにwinscard.hに定義されていません。 これは、cardmod.hに定義されています。 cardmod.hは、Card MiniDriverを作成するために必要な各種情報が定義されており、 CNGSDKに含まれています。

今回のプログラムは、SCardGetCardTypeProviderNameを呼び出して、 カードに対応するCSPの名前を取得します。

#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
			;
	}

	if (readerState.dwEventState & SCARD_STATE_PRESENT) {
		LPTSTR lpszCardName;
		LPTSTR lpszProvName;
		
		dwAutoAllocate = SCARD_AUTOALLOCATE;
		lResult = SCardListCards(hContext, readerState.rgbAtr, NULL, 0, (LPTSTR)&lpszCardName, &dwAutoAllocate);
		if (lResult == SCARD_S_SUCCESS) {
			dwAutoAllocate = SCARD_AUTOALLOCATE;
			lResult = SCardGetCardTypeProviderName(hContext, lpszCardName, SCARD_PROVIDER_CSP, (LPTSTR)&lpszProvName, &dwAutoAllocate);
			if (lResult == SCARD_S_SUCCESS) {
				MessageBox(NULL, lpszProvName, TEXT("OK"), MB_OK);
				SCardFreeMemory(hContext, lpszProvName);
			}
			SCardFreeMemory(hContext, lpszCardName);
		}
	}

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

	return 0;
}

SCardGetCardTypeProviderNameを呼び出すためには、対象とするカードの名前が必要となります。 カードの名前を取得するには、SCardUIDlgSelectCardを呼び出す方法と、 ATRを取得してからSCardListCardsを呼ぶ方法がありますが、今回は後者の方法を使用しています。 ATRの取得には、SCardConnectの後にSCardStatusを呼ぶ方法と、SCardGetStatusChangeを呼び出す方法がありますが、 ATRを取得するためだけにカードに接続することはないと思い、後者の方法を使用しています。 カードがセットされたことを示すSCARD_STATE_PRESENTが含まれる場合、 目的の処理を実行します。

if (readerState.dwEventState & SCARD_STATE_PRESENT) {
	LPTSTR lpszCardName;
	LPTSTR lpszProvName;
	
	dwAutoAllocate = SCARD_AUTOALLOCATE;
	lResult = SCardListCards(hContext, readerState.rgbAtr, NULL, 0, (LPTSTR)&lpszCardName, &dwAutoAllocate);
	if (lResult == SCARD_S_SUCCESS) {
		dwAutoAllocate = SCARD_AUTOALLOCATE;
		lResult = SCardGetCardTypeProviderName(hContext, lpszCardName, SCARD_PROVIDER_CSP, (LPTSTR)&lpszProvName, &dwAutoAllocate);
		if (lResult == SCARD_S_SUCCESS) {
			MessageBox(NULL, lpszProvName, TEXT("OK"), MB_OK);
			SCardFreeMemory(hContext, lpszProvName);
		}
		SCardFreeMemory(hContext, lpszCardName);
	}
}

まず、SCardGetStatusChangeで取得したATRをSCardListCardsに指定し、カード名を取得します。 続いて、SCardGetCardTypeProviderNameに取得したカード名を指定し、 lpszProvNameを初期化するようにします。 第3引数にSCARD_PROVIDER_CSPを指定しているため、 関数が成功した場合はCSPの名前が格納されているはずです。


戻る