EternalWindows
スマートカード / ファイルの選択

スマートカード内のファイルを選択する機能は非常に有用です。 たとえば、あるファイルを選択できたということは、指定した識別子を持ったファイルが 存在するということですから、ファイルを列挙したい場合などは参考になります。 また、ファイルを選択した場合は、オプションとしてFCI(File Control Information)を 取得することができるため、選択したファイルの情報を知ることができます。 この他、ファイルを選択することでそのファイルをカレントファイルにし、 他のコマンドの実行対象とするような目的で利用されることもあります。

前節で述べたように、ファイルを選択するにはSELECT FILEコマンドを利用することになります。 このコマンドのINSは0xa4であり、P1は主に次の値を指定することになります。

P1の値 意味
0x00 ボディのデータフィールドにファイル識別子を指定する。
0x04 ボディのデータフィールドにDF名を指定する。

P1で指定する値は、ファイルを選択するための情報となります。 EFはファイル識別子で識別されているため、EFを選択する場合は、 0x00を指定することになります。 一方、DFは名前で識別されるため、0x04を指定することになります。 P2に指定できる値の一部を次に示します。

P2の値 意味
0x00 レスポンスとしてFCIを取得する。
0x0c レスポンスとしてFCIを取得しない。
0x02 ボディのデータフィールドにパーシャルDF名を指定する。

表が示すように、ファイルを選択してFCIを取得したい場合は0x00を指定することになります。 一方、ファイルを選択するだけでよい場合は、0x0cを指定することになるでしょう。 パーシャルDF名については後述します。

ではここで、SELECT FILEコマンドを使う例をいくつかを見ていきましょう。 まずは、EFを選択する例です。

BYTE command[] = {0x00, 0xa4, 0x00, 0x00, 2, 0x2f, 0x11};

P1に0x00を指定していることから、EFをファイル識別子で選択することになります。 ファイル識別子は2バイトであるためLcに2を指定し、 データ部にファイル識別子を指定しています。 この例では、0x2fと0x11になっていますから、0x2f11の識別子を持つファイルが選択されることになります。 次に、DF名を指定する例を示します。

BYTE command[] = {0x00, 0xa4, 0x04, 0x00, 3, 0x41, 0x42, 0x43};

DFを選択するため、P1に0xa4を指定します。また、ボディにDF名を指定しなければならないため、 Lcとデータフィールドを初期化する必要があります。 Lcが3であるため、データフィールドは3バイトであり、その値が0x41, 0x42, 0x43となっているため、 ANSI文字のABCであることが分かります。 つまり、ABCという名前を持つDFが選択されることになります。 次に、FCIを取得する例を示します。

BYTE command[] = {0x00, 0xa4, 0x00, 0x00, 2, 0x2f, 0x11, 100};

この例では、0x2f11のファイル識別子を持ったファイルのFCIを取得しようとしています。 FCIを取得するためP2に0x00を指定し、FCIがレスポンスとして返ることから、 Leに十分大きな値を指定しています。 上記の例であれば、Leに100を指定しているため、100バイト以下のFCIが返されることになります。 次に、FCIを取得しない例を示します。

BYTE command[] = {0x00, 0xa4, 0x00, 0x0c, 2, 0x2f, 0x11};

P2に0x0cを指定しているため、FCIがレスポンスのデータフィールドに格納されることはありません。 このため、leに取得したいFCIのサイズを指定する必要はありません。 ただし、P2に0x00を指定した場合でも、leを指定していなければFCIが返ることはないため、 FCIが不要な際に必ず0x0cを指定しなければならないわけではありません。 P2が0x00でleが指定されていない場合は、レスポンスに含まれるのは ステータスワード(SW1とSW2)のみとなります。 最後に、SELECT FILEコマンドの特別な使い方を示します。

BYTE command[] = {0x00, 0xa4, 0x00, 0x00};

P1とP2が共に0x00であり、さらにデータフィールドを指定していない場合は、 MFが選択されることになります。 このコマンドは現在のカレントEF(またはDF)の階層を問わず、 必ずMFを選択することを可能にします。

続いて、FCIの形式について説明します。 結論から述べると、FCIはTLV構造を持ったデータが複数個連結された形に過ぎません。 TLV構造とは、1つのデータをT(タグフィールド)、L(長さフィールド)、 V(値フィールド)の3つに分割して考える形式で、 Tの値を確認することで、データの種類を特定できる利点があります。 また、データの長さはLを確認することで分かります。 TとLは共に1バイトであり、Lの値はTによって変化します。 FCIに含まれるデータは、次のようなタグを持っています。

T(Tag)
1バイト
L(Length)
1バイト
V(Value)
Lで指定されたバイト
0x802 ファイルのデータバイト数。
0x812 ファイルのデータバイト数(構造化情報含む)。
0x821 ファイル記述子。
0x822 ファイル記述子とコーディングタイプ。
0x823または4 ファイル記述子とコーディングタイプ、レコード長。
0x832 ファイル識別子。
0x841から16 DF名。
0x86可変 セキュリティ属性。

ファイル記述子として指定される値には、次のようなものがあります。

ファイル記述子 意味
0x01 透過WEF
0x03 固定長順編成WEF
0x05 可変長順編成WEF
0x07 固定長循環編成WEF
0x08 IEF
0x38 DF

下位3ビットのどれか1つでも立っていればWEFであると考えられるため、 0x07と論理積でWEFであるかどうかの判定が可能になります。 IEFかどうかの判定は、4ビット目が立っており、 それ以外のビットが立っていない場合に成立します。

ではここで、実際にFCIの中身を解析してみることにします。 話を簡単にするため、バイナリ表示した状態で例を示します。

6f 07 80 02 00 10 82 01  08 90 00

最初の6fという値は、レスポンスのボディがFCIであることを示す指標です。 この値は、FCIテンプレートと呼ばれることもあります。 2バイト目の値は、FCIのサイズです。 このサイズには、先頭の6fと07、そして終端の90と00の計4バイトは含まれていません。 3バイト目の値は80であり、データバイト数を表すタグであることが分かります。 タグの次には1バイトのデータサイズがあり、ここが2となっていることから、 00 10という値が80のデータであると分かります。 10の後の82から分るように、ここからまた新しいタグについて情報が始まります。 タグの次の1バイトの値が1であることから、データのサイズが1であることが分かり、 そのデータ(8)とタグ(82)を先の表で比較してみれば、 ファイルの種類がIEFであると特定することができます。 終端の90と00はステータスワードであり、これはFCIの一部ではありません。

今回のプログラムは、特定のDF直下に存在するEFを列挙します。

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

#define TYPE_UNKNOWN 0
#define TYPE_WEF 1
#define TYPE_IEF 2
#define TYPE_DF 3

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

int EnumFileId(SCARDHANDLE hCard, LPWORD lpwId);
void DetailFile(SCARDHANDLE hCard, WORD wId, HWND hwndListBox);
int LookupFileType(BYTE fileDescriptor);
BOOL SelectSearchDf(SCARDHANDLE hCard);
BOOL ConnectCard(LPSCARDCONTEXT lphContext, LPSCARDHANDLE lphCard);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND         hwndListBoxLeft = NULL;
	static HWND         hwndListBoxRight = NULL;
	static SCARDCONTEXT hContext = NULL;
	static SCARDHANDLE  hCard = NULL;
	static WORD         wId[1024];

	switch (uMsg) {

	case WM_CREATE: {
		int   i;
		int   fileCount;
		TCHAR szBuf[256];

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		if (!ConnectCard(&hContext, &hCard))
			return -1;
		
		if (!SelectSearchDf(hCard))
			return -1;

		fileCount = EnumFileId(hCard, wId);
		for (i = 0; i < fileCount; i++) {
			wsprintf(szBuf, TEXT("%#02x"), wId[i]);
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		return 0;  
	}
	
	case WM_COMMAND: {
		int index;

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		index = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		DetailFile(hCard, wId[index], hwndListBoxRight);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (hCard != NULL)
			SCardDisconnect(hCard, SCARD_LEAVE_CARD);
		if (hContext != NULL)
			SCardReleaseContext(hContext);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int EnumFileId(SCARDHANDLE hCard, LPWORD lpwId)
{
	int   fileCount;
	BYTE  i, j;
	DWORD dwResponseSize;
	LONG  lResult;
	BYTE  response[2];
	BYTE  command[] = {0x00, 0xa4, 0x00, 0x00, 2, 0x00, 0x00};
	
	fileCount = 0;
	
	for (i = 0x00; i <= 0x02; i++) {
		command[sizeof(command) - 2] = i;
		for (j = 0x00; j <= 0x1f; j++) {
			if ((i == 0x00 && j == 0x00) || (i == 0x3f && j == 0x00))
				continue;
			command[sizeof(command) - 1] = j;
			dwResponseSize = sizeof(response);
			lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
			if (lResult != SCARD_S_SUCCESS)
				continue;
			if (response[dwResponseSize - 2] == 0x90 && response[dwResponseSize - 1] == 0x00)
				lpwId[fileCount++] = MAKEWORD(j, i);
		}
	}

	return fileCount;
}

void DetailFile(SCARDHANDLE hCard, WORD wId, HWND hwndListBox)
{
	BYTE   tag, len;
	BYTE   fileDescriptor;
	LPBYTE lp;
	DWORD  dwResponseSize;
	LONG   lResult;
	TCHAR  szBuf[256];
	BYTE   response[100 + 2];
	BYTE   command[] = {0x00, 0xa4, 0x00, 0x00, 2, HIBYTE(wId), LOBYTE(wId), 100};
	
	dwResponseSize = sizeof(response);
	lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
	if (lResult != SCARD_S_SUCCESS)
		return;
	if (response[dwResponseSize - 2] != 0x90 || response[dwResponseSize - 1] != 0x00)
		return;

	lp = response;
	lp += 2;
	fileDescriptor = 0;

	for (; lp[0] != 0x90; lp += len) {
		tag = lp[0];
		len = lp[1];
		lp += 2;

		switch (tag) {

		case 0x80: {
			WORD wDataSize = MAKEWORD(lp[1], lp[0]);
			wsprintf(szBuf, TEXT("データバイト数 : %d"), wDataSize);
			break;
		}
		case 0x81: {
			WORD wDataSize = MAKEWORD(lp[1], lp[0]);
			wsprintf(szBuf, TEXT("データバイト数(構造化情報含む) : %d"), wDataSize);
			break;
		}
		case 0x82: {
			int nType; 
			fileDescriptor = lp[0];

			nType = LookupFileType(fileDescriptor);
			
			if (nType == TYPE_WEF) {
				lstrcpy(szBuf, TEXT("ファイル記述子 : WEF"));
				if (len == 3 || len == 4)
					wsprintf(szBuf, TEXT("%s(レコード長 %d)"), szBuf, len == 3 ? lp[2] : MAKEWORD(lp[3], lp[2]));
			}
			else if (nType == TYPE_IEF)
				lstrcpy(szBuf, TEXT("ファイル記述子 : IEF"));
			else if (nType == TYPE_DF)
				lstrcpy(szBuf, TEXT("ファイル記述子 : DF"));
			else
				lstrcpy(szBuf, TEXT("ファイル記述子 : 不明"));
			break;
		}
		case 0x83: {
			WORD wFileId = MAKEWORD(lp[1], lp[0]);
			wsprintf(szBuf, TEXT("ファイルID : %#x"), wFileId);
			break;
		}
		case 0x86: {
			int  nType;
			BYTE i;
			BYTE accessMode;

			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("セキュリティ属性 :"));

			len = lp[0];
			lp++;
			
			nType = LookupFileType(fileDescriptor);

			for (i = 0; i < len;) {
				accessMode = lp[i + 2];
				if (nType == TYPE_WEF) {
					if (accessMode & 0x01)
						lstrcpy(szBuf, TEXT("READ RECORD"));
					else if (accessMode & 0x02)
						lstrcpy(szBuf, TEXT("UPDATE RECORD"));
					else if (accessMode & 0x04)
						lstrcpy(szBuf, TEXT("WRITE RECORD"));
					else
						;
				}
				else if (nType == TYPE_IEF) {
					if (accessMode & 0x02)
						lstrcpy(szBuf, TEXT("CHANGE REFERENCE DATA"));
					else if (accessMode & 0x10)
						lstrcpy(szBuf, TEXT("RESET RETRY COUNTER"));
					else if (accessMode & 0x20)
						lstrcpy(szBuf, TEXT("INTERNAL AUTHENTICATE"));
					else if (accessMode & 0x40)
						lstrcpy(szBuf, TEXT("VERIFY CERTIFICATE"));
					else
						;
				}
				else if (nType == TYPE_DF) {
					if (accessMode & 0x01)
						lstrcpy(szBuf, TEXT("DELETE FILE (子)"));
					else if (accessMode & 0x02)
						lstrcpy(szBuf, TEXT("CREATE FILE (EF)"));
					else if (accessMode & 0x04)
						lstrcpy(szBuf, TEXT("CREATE FILE (DF)"));
					else if (accessMode & 0x08)
						lstrcpy(szBuf, TEXT("DEACTIVATE FILE"));
					else if (accessMode & 0x10)
						lstrcpy(szBuf, TEXT("ACTIVATE FILE"));
					else if (accessMode & 0x40)
						lstrcpy(szBuf, TEXT("DELETE FILE (自己)"));
					else
						;
				}
				else
					lstrcpy(szBuf, TEXT("コマンド不明"));

				i += 3;
				if (lp[i] == 0x90) {
					lstrcat(szBuf, TEXT(" (フリー)"));
					i += 2;
				}
				else if (lp[i] == 0x97) {
					lstrcat(szBuf, TEXT(" (禁止)"));
					i += 2;
				}
				else if (lp[i] == 0xa0) {
					lstrcat(szBuf, TEXT(" (OR)"));
					i += lp[i + 1] + 2;
				}
				else if (lp[i] == 0xaf) {
					lstrcat(szBuf, TEXT(" (AND)"));
					i += lp[i + 1] + 2;
				}
				else
					return;
				
				SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			}
			
			continue;
		}

		default:
			wsprintf(szBuf, TEXT("%#x : %#x(len)"), tag, len);
			break;
		}

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	}
}

int LookupFileType(BYTE fileDescriptor)
{
	int nType;

	if (fileDescriptor & 0x08) {
		if (fileDescriptor & 0x30)
			nType = TYPE_DF;
		else
			nType = TYPE_IEF;

	}
	else if (fileDescriptor & 0x07)
		nType = TYPE_WEF;
	else
		nType = TYPE_UNKNOWN;

	return nType;
}

BOOL SelectSearchDf(SCARDHANDLE hCard)
{
	DWORD dwResponseSize;
	LONG  lResult;
	BYTE  response[2];
	BYTE  command[] = {0x00, 0xa4, 0x00, 0x00};

	dwResponseSize = sizeof(response);
	lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
	if (lResult == SCARD_S_SUCCESS) {
		if (response[dwResponseSize - 2] == 0x90 && response[dwResponseSize - 1] == 0x00) 
			return TRUE;
	}
	
	MessageBox(NULL, TEXT("DFの選択に失敗しました。"), NULL, MB_ICONWARNING);

	return FALSE;
}

BOOL ConnectCard(LPSCARDCONTEXT lphContext, LPSCARDHANDLE lphCard)
{
	LPTSTR lpszReaderName;
	LONG   lResult;
	DWORD  dwActiveProtocol;
	DWORD  dwAutoAllocate = SCARD_AUTOALLOCATE;

	lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, lphContext);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_E_NO_SERVICE)
			MessageBox(NULL, TEXT("Smart Cardサービスが起動されていません。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	lResult = SCardListReaders(*lphContext, NULL, (LPTSTR)&lpszReaderName, &dwAutoAllocate);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_E_NO_READERS_AVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが接続されていません。"), NULL, MB_ICONWARNING);
		return 0;
	}

	lResult = SCardConnect(*lphContext, lpszReaderName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, lphCard, &dwActiveProtocol);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_W_REMOVED_CARD)
			MessageBox(NULL, TEXT("カードがセットされていません。"), NULL, MB_ICONWARNING);
		SCardFreeMemory(*lphContext, lpszReaderName);
		return FALSE;
	}

	SCardFreeMemory(*lphContext, lpszReaderName);

	return TRUE;
}

SelectSearchDfという自作関数は、ファイル列挙の対象となるDFを選択します。 今回の処理ではMFを選択していますが、他のDFのファイルを列挙したい場合は、 そのDFを選択するコードを書くことになるでしょう。 DFの選択を終えたら、EnumFileIdでファイルを列挙します。

for (i = 0x00; i <= 0x02; i++) {
	command[sizeof(command) - 2] = i;
	for (j = 0x00; j <= 0x1f; j++) {
		if ((i == 0x00 && j == 0x00) || (i == 0x3f && j == 0x00))
			continue;
		command[sizeof(command) - 1] = j;
		dwResponseSize = sizeof(response);
		lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
		if (lResult != SCARD_S_SUCCESS)
			continue;
		if (response[dwResponseSize - 2] == 0x90 && response[dwResponseSize - 1] == 0x00)
			lpwId[fileCount++] = MAKEWORD(j, i);
	}
}

ファイルを列挙する仕組みは、ファイル識別子の最高値をカウントするまで、 ファイルを選択し続けることです。 ファイル識別子は2バイトであり、0x0000〜0xffffが範囲となるため、 これをi(上位バイト)とj(下位バイト)で適切に調整していきます。 0x0000と0x3f00の際に処理を省略しているのは、 これらがMFの選択となってしまうためです。 また、全てのファイル識別子を対象としては時間がかかるため、 今回は0x0000から0x021fまでを対象としています。 返されたレスポンスのステータスワードが成功を表す場合、 iとjをMAKEWORDで組み合わせて1つのファイル識別子を完成させ、 それをEnumFileIdの引数として受け取った配列に保存します。

EnumFileIdでファイルを1つ以上選択できた場合、 それらのファイル識別子が左のリストボックスに追加されることになります。 そのファイル識別子をリストボックス上で選択した場合、 右のリストボックスに詳細が表示されることになります。 この処理は、DetailFileという自作関数で行われています。 重要な処理を順に見ていきます。

BYTE response[100 + 2];
BYTE command[] = {0x00, 0xa4, 0x00, 0x00, 2, HIBYTE(wId), LOBYTE(wId), 100};
	
dwResponseSize = sizeof(response);
lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);

ファイル識別子を上位バイトと下位バイトに分割し、Lcの後に指定します。 また、ファイルの詳細はFCIから参照することになるため、 Leに十分大きなサイズを指定しておきます。

lp = response;
lp += 2;
fileDescriptor = 0;

返されたレスポンスをlpで指すようにし、ポインタを2つ進めます。 これにより、FCIテンプレートとサイズを格納したバイトをスキップしたことになるため、 次のバイトよりタグにアクセスすることができるようになります。 fileDescriptorは、ファイル記述子を表します。

for (; lp[0] != 0x90; lp += len) {
	tag = lp[0];
	len = lp[1];
	lp += 2;

lpは現在、TLVのタグフィールドを指しているため、lp[0]とすることでタグを参照できます。 lp[1]は値フィールドのサイズであり、これも後で必要になるため保存しておきます。 ポインタを2つ進めれば、lp[0]が値フィールドの先頭を指すようになるため、 後はタグフィールドの値に応じて値フィールドを解析することになります。 解析処理が終われば、lpを値フィールドのサイズ分だけ進め、 それがまだステータスワードの位置に到達していない場合は、 次のTLVデータを処理することになります。

パーシャルDF名について

パーシャルDF名とは、本来のDF名を短縮したDF名です。 たとえば、ABCという名前を持ったDFを選択する場合は、 ボディにABCを指定するのが通常の方法ですが、 AやABといったように、短縮した名前でDFを選択することも可能です。 次に、例を示します。

BYTE command[] = {0x00, 0xa4, 0x04, 0x02, 1, 0x41};

DFの選択にパーシャルDF名を指定する場合は、P2に0x02を指定します。 この例ではLcに1を指定し、データには0x41(A)を指定します。 これにより、先に述べたABCというDFが選択されることになります。 ただし、Aから始まるDFが他にも存在する場合は、 先に見つかった方が選択されるため、より正確に限定したいのであれば、 Lcに2を指定し、データに0x41と0x42を指定するべきでしょう。 なお、短縮したDF名をパーシャルDF名と呼ぶ時、 本来のDF名をフルDF名と呼ぶことがあります。

パーシャルDF名によってDFを選択できることは分かりましたが、 その選択されたDFのフルDF名を知ることはできないものでしょうか。 実は、これは可能です。 SELECT FILEコマンドが返すFCIの0x84タグにはフルDF名が格納されているため、 これを参照すればよいことになります。 この事から、わずか1文字のみでもフルDF名を取得できることになるため、 カードに存在するDFの列挙を簡単に行えることになります。 つまり、AからZまでの文字をパーシャルDF名として指定すればよいのです。

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

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

BOOL SendCommand(SCARDHANDLE hCard);
BOOL SelectMf(SCARDHANDLE hCard);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	SCARDCONTEXT hContext;
	SCARDHANDLE  hCard;
	LPTSTR       lpszReaderName;
	LONG         lResult;
	DWORD        dwActiveProtocol;
	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;
	}

	lResult = SCardConnect(hContext, lpszReaderName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
	if (lResult != SCARD_S_SUCCESS) {
		if (lResult == SCARD_W_REMOVED_CARD)
			MessageBox(NULL, TEXT("カードがセットされていません。"), NULL, MB_ICONWARNING);
		SCardFreeMemory(hContext, lpszReaderName);
		SCardReleaseContext(hContext);
		return 0;
	}

	if (SelectMf(hCard))
		SendCommand(hCard);
		
	SCardDisconnect(hCard, SCARD_LEAVE_CARD);

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

	return 0;
}

BOOL SendCommand(SCARDHANDLE hCard)
{
	DWORD  dwResponseSize;
	LONG   lResult;
	LPBYTE lp;
	BYTE   i, j;
	BYTE   len;
	BYTE   response[100 + 2];
	BYTE   command[] = {0x00, 0xa4, 0x04, 0x02, 1, 0, 100};
	CHAR   szDfName[16 + 1];
	
	for (i = 0x41; i <= 0x5a; i++) {
		command[5] = i;
		dwResponseSize = sizeof(response);
		lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
		if (lResult != SCARD_S_SUCCESS)
			break;
		if (response[dwResponseSize - 2] != 0x90 || response[dwResponseSize - 1] != 0x00)
			continue;

		for (lp = response; *lp != 0x90; lp++) {
			if (*lp == 0x84) {
				lp++;
				len = *lp;
				lp++;
				for (j = 0; j < len; j++)
					szDfName[j] = lp[j];
				szDfName[j] = '\0';
				MessageBoxA(NULL, szDfName, "OK", MB_OK);
				break;
			}
		}
	}
	
	return TRUE;
}

BOOL SelectMf(SCARDHANDLE hCard)
{
	DWORD dwResponseSize;
	LONG  lResult;
	BYTE  response[2];
	BYTE  command[] = {0x00, 0xa4, 0x00, 0x00};

	dwResponseSize = sizeof(response);
	lResult = SCardTransmit(hCard, SCARD_PCI_T1, command, sizeof(command), NULL, response, &dwResponseSize);
	if (lResult != SCARD_S_SUCCESS)
		return FALSE;

	return response[dwResponseSize - 2] == 0x90 && response[dwResponseSize - 1] == 0x00;
}

SendCommandという自作関数では、パーシャルDF名によってDFの選択を行います。 iは0x41(A)から0x5a(Z)までカウントされ、 その値はcommand変数のデータフィールドに指定されます。 パーシャルDF名として1バイト指定しているため、Lcの値も1となります。 SELECT FILEコマンドのレスポンスが成功を示す場合は、 FCIを解析しやすくするためresponseをlpで指します。 まず、TLVデータのタグが0x84(DF名を示す値)であるかを確認し、 そうである場合はデータ、つまりDF名のサイズを取得します。 続いて、そのサイズだけデータをszDfNameにコピーすれば、 DF名を取得できたことになります。



戻る