EternalWindows
スマートカード / カード識別子

スマートカードのファイル構造には、予約された識別子を持ったファイルが予め存在することがあります。 たとえば、JICSAP仕様のカードに存在するカード識別子と呼ばれるファイルは、0x1eという予約された識別子を持ち、 MFの直下に存在しています。 カード識別子はカード製造者が設定するものであり、 主にカード発行者がその値を確認しますが、 アプリケーションにとっても有用な情報が含まれています。 今回はこのカード識別子の詳細と、ファイルを読み取るREAD RECORD(S)コマンドについて説明します。

カード識別子と呼ばれるファイルは、可変長順編成のWEFとして格納されています。 IEFではなくWEFであるのは、カード識別子が鍵ではなくデータを格納するので当然といえますが、 可変長順編成というのは一体何でしょうか。 実は、WEFの構造には透過構造(バイナリ形式)とレコード構造の2種類が存在し、 さらにレコード構造にはレコードの格納方法が複数個あります。 その格納方法の1つとして存在するのが、可変長順編成です。 ただし、いずれのレコード構造のファイルであっても、データをレコード単位で維持する設計は変わりません。 たとえば、xxというデータは1番目のレコードに格納し、 zzというデータは2番目のレコードに格納するという要領になります。 カード識別子の1番目のレコードには、次のようなデータが格納されています。

T
1バイト
L
1バイト
V
3バイト
0x003 製造者識別子暗号関数識別子仕様書バージョン識別子

レコード内のデータはTLV構造で格納されています。 カード識別子の最初のレコードは製造者共通情報レコードと呼ばれ、 0x00というタグで識別されます。 このレコードの値フィールドには1バイトずつ意味があり、 最初の1バイト目は製造者識別子が格納されています。 これは、製造者を示す値です。 2バイト目は暗号関数識別子と呼ばれ、カードがサポートする暗号化関数の種類が格納されています。 JICSAP1.1では、暗号化関数と値の関係を次のように定義しています。

暗号化関数
0x01 DES
0x02 RSA
0x04 FEAL
0x08 3DES

これらの値は、複数組み合わせて格納されることが予想されます。 たとえば、0x11が格納されていたならば、それは0x01と0x10の論理和で得られますから、 DESとRSAをサポートしていることを意味します。

仕様書バージョン識別子は、カードが対応しているJICSAPのバージョンを示す値が格納されています。 値とバージョンの関係は次のようになります。

バージョン
0x01 JICSAP 1.0
0x02 JICSAP 1.1
0x03 JICSAP 2.0

JICSAPのコマンドには、2.0でしか利用できないものがあるため、 このバージョンを確認するのは非常に重要といえます。

カード識別子の2番目のレコードは、オプション機能情報レコードと呼ばれています。 オプションという言葉が含まれていますが、実際には必須のレコードとなっています。 このレコードの構造は、次のようになっています。

T
1バイト
L
1バイト
V
1バイト
0x011 オプション機能情報

タグの値は、0x01となります。 長さは、オプション機能情報を示す値が1バイトであるため、1となります。 オプション機能情報と値の関係を次に示します。

オプション機能
0x01 DFの削除機能
0x02 IEF創生制限機能
0x04 DF内メモリ残要領照会機能
0x08 セキュアメッセージング機能(隠蔽)
0x10 セキュアメッセージング機能(データの完全性)
0x20 セキュアメッセージング機能(隠蔽かつデータの完全性)
0x40 ECBモード(隠蔽機能搭載時の暗号利用モード)
0x80 CBCモード(隠蔽機能搭載時の暗号利用モード)

これらの値も複数組み合わされて指定されると予想されます。

カード識別子には、オプションとして3番目のレコードが存在することがあります。 このレコードは製造者固有情報レコードと呼ばれ、次のようなデータが格納されています。

T
1バイト
L
1バイト
V
1〜5バイト
0x021〜5 製造者固有情報

タグの値は、0x02となります。 長さは、1から5のいずれかの値が格納されます。 製造者固有情報は、カードの発行者が認識するための情報であるため、 アプリケーションが理解することはできないと思われます。

それでは次に、レコードに格納されているデータを読み取る方法について説明します。 これは本節の冒頭でも述べたように、READ RECORD(S)コマンドを利用します。 このコマンドのINSは0xb2であり、P1には読み取りの対象とするレコードの番号を指定します。 レコード番号は、0ではなく1から始まることに注意してください。 P2には次に示す値を指定することができます。

P2の値 意味
0x04 P1で指定したレコード番号のレコードを対象にする。
0x05 複数レコード読み出し。P1から最終レコードまでを対象にする。
0x06 複数レコード読み出し。最終レコードからP1までを対象にする。

基本的にREAD RECORD(S)コマンドを実行する場合は、 対象とするファイルを予めSELECT FILEコマンドで選択しておきますが、 実際はREAD RECORD(S)コマンドに対象とするファイル識別子を指定することもできます。 具体的には、P2の4バイト目から8バイト目での計5バイトに、 0x01から0x1eまでの短縮識別子を格納します。

今回のプログラムは、カード識別子に格納されているデータをREAD RECORD(S)コマンドで読み取ります。

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

#pragma comment (lib, "winscard.lib")
#pragma comment (lib, "crypt32.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)
{
	TCHAR  szBuf[256];
	DWORD  dwResponseSize;
	DWORD  dwBufferSize;
	LONG   lResult;
	LPBYTE lp;
	BYTE   i;
	BYTE   response[5 + 2];
	BYTE   commandSelect[] = {0x00, 0xa4, 0x00, 0x00, 2, 0x00, 0x1e};
	BYTE   commandRead[] = {0x00, 0xb2, 0x01, 0x04, 5};
	
	dwResponseSize = sizeof(response);
	lResult = SCardTransmit(hCard, SCARD_PCI_T1, commandSelect, sizeof(commandSelect), NULL, response, &dwResponseSize);
	if (lResult != SCARD_S_SUCCESS)
		return FALSE;
	if (response[dwResponseSize - 2] != 0x90 || response[dwResponseSize - 1] != 0x00)
		return FALSE;

	for (i = 0; i < 3; i++) {
		commandRead[2] = i + 1;
		dwResponseSize = sizeof(response);
		lResult = SCardTransmit(hCard, SCARD_PCI_T1, commandRead, sizeof(commandRead), NULL, response, &dwResponseSize);
		if (lResult != SCARD_S_SUCCESS)
			break;
		if (response[dwResponseSize - 2] != 0x90 || response[dwResponseSize - 1] != 0x00)
			break;

		lp = response;
		if (lp[0] == 0x00) {
			wsprintf(szBuf, TEXT("製造者識別子 : %#x"), lp[2]);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

			lstrcpy(szBuf, TEXT("暗号化識別子 : "));
			if (lp[3] & 0x01)
				lstrcat(szBuf, TEXT("DES "));
			if (lp[3] & 0x02)
				lstrcat(szBuf, TEXT("RSA "));
			if (lp[3] & 0x04)
				lstrcat(szBuf, TEXT("FEAL "));
			if (lp[3] & 0x08)
				lstrcat(szBuf, TEXT("3DES "));
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			
			lstrcpy(szBuf, TEXT("仕様書バージョン識別子 : "));
			if (lp[4] == 0x01)
				lstrcat(szBuf, TEXT("1.0"));
			else if (lp[4] == 0x02)
				lstrcat(szBuf, TEXT("1.1"));
			else if (lp[4] == 0x03)
				lstrcat(szBuf, TEXT("2.0"));
			else
				;
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
		}
		else if (lp[0] == 0x01) {
			wsprintf(szBuf, TEXT("オプション機能情報 : %#x"), lp[2]);	
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
		}
		else if (lp[0] == 0x02) {
			dwBufferSize = sizeof(szBuf);
			CryptBinaryToString(response, dwResponseSize, CRYPT_STRING_HEX, szBuf, &dwBufferSize);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
		}
		else
			;
	}
	
	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という自作関数は、まずSELECT FILEコマンドでカード識別子を選択します。 これによって、READ RECORD(S)コマンドが選択したファイルに対して実行されます。 カード識別子のレコードは製造者共通情報レコード、オプション機能情報レコード、 製造者固有情報レコードの3つが存在するため、for文で3回READ RECORD(S)コマンドを実行することになります。 具体的には、iに+1をしたものをレコード番号としてコマンドのP1に指定し、 その後レスポンスのタグからどのレコードかの判定を行います。


戻る