EternalWindows
PKCS #7 / メッセージとコンテンツ

PKCS #7形式の署名データや暗号化データを必要とするアプリケーションは、 Low-Level Message Functionsと呼ばれる関数群を利用することもできます。 これらの関数は、Simplified Message Functionsと異なり、 PKCS #7形式へのエンコードやデコードといった作業から行い、 その結果に対して署名の検証などを行うことになります。 今回は、PKCS #7形式のデータをデコードする際に呼び出すCryptMsgOpenToDecodeなどを取り上げます。

HCRYPTMSG WINAPI CryptMsgOpenToDecode(
  DWORD dwMsgEncodingType,
  DWORD dwFlags,
  DWORD dwMsgType,
  HCRYPTPROV hCryptProv,
  PCERT_INFO pRecipientInfo,
  PCMSG_STREAM_INFO pStreamInfo
);

dwMsgEncodingTypeは、エンコーディングタイプを指定します。 dwFlagsは、基本的に0を指定します。 dwMsgTypeは、メッセージタイプを指定します。 0を指定した場合、メッセージに設定されたコンテンツからメッセージタイプを判別します。 hCryptProvは、 pRecipientInfoは、予約されているためNULLを指定します。 pStreamInfoは、ストリーミングに関する引数ですが、基本的にNULLを指定します。 戻り値は、メッセージのハンドルが返ります。

HCRYPTMSG型が識別するメッセージは、コンテンツとコンテンツを エンコードまたはデコードするための情報を含むことができます。 コンテンツとは、エンコードまたはデコードされたデータのことであり、 CryptMsgOpenToDecodeの場合は、メッセージがデコードするための情報を含みますから、 そのメッセージに追加するのはPKCS #7形式にエンコードされたデータとなります。 データをコンテンツとしてメッセージに追加するには、CryptMsgUpdateを呼び出します。

BOOL WINAPI CryptMsgUpdate(
  HCRYPTMSG hCryptMsg,
  const BYTE *pbData,
  DWORD cbData,
  BOOL fFinal
);

hCryptMsgは、メッセージのハンドルを指定します。 pbDataは、エンコードまたはデコードしたいデータを指定します。 cbDataは、pbDataのサイズを指定します。 fFinalは、CryptMsgOpenToDecodeのdwFlagsにCMSG_DETACHED_FLAGを指定していない場合は、TRUEを指定します。 この関数が成功したということは、 pbDataに指定したデータがエンコードまたはデコードされて、 メッセージに追加されたということです。 この追加されたデータがコンテンツとなります。

ここまでの作業をCryptVerifySignatureやCryptDecryptMessageの動作で例えると、 いわば途中作業です。 これらの関数は、まずPKCS #7形式のデータをデコードし、 その後に複合化や署名の検証を行うわけであり、 デコードと目的の動作を同時に行っています。 一方、CryptMsgUpdateが行うのはデータのエンコードまたはデコードまでであり、 ここからどうのような動作を行うかは、アプリケーション次第となるわけです。 当然ながら、その何らかの動作を行う際には、 コンテンツを維持したメッセージが必要になります。 次に、メッセージから特定のパラメータを取得するCryptMsgGetParamを示します。

BOOL WINAPI CryptMsgGetParam(
  HCRYPTMSG hCryptMsg,
  DWORD dwParamType, 
  DWORD dwIndex,
  void *pvData,
  DWORD *pcbData
);

hCryptMsgは、メッセージのハンドルを指定します。 dwParamTypeは、取得したいパラメータを表す定数を指定します。 dwIndexは、どの情報を取得するかを表すインデックスを指定します。 dwParamTypeの値によって使用しない場合があります。 pvDataは、パラメータを受け取るバッファを指定します。 得られる形式は、dwParamTypeの値によって異なります。 pcbDataは、バッファのサイズを格納した変数のアドレスを指定します。 pvDataにNULLを指定した場合は、バッファのサイズが返ります。

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

定数 説明
CMSG_CERT_COUNT_PARAM メッセージに含まれる証明書の数を取得する。
CMSG_CERT_PARAM メッセージに含まれる証明書を取得する。
CMSG_CONTENT_PARAM メッセージのコンテンツを取得する。
CMSG_ENCODED_MESSAGE 既にデコードされたメッセージに何らかの変更を加えた場合、 その変更を考慮して再度エンコードし直すことができる。
CMSG_RECIPIENT_COUNT_PARAM 受取人の数を取得する。
CMSG_RECIPIENT_INFO_PARAM 受取人の情報を取得する。
CMSG_TYPE_PARAM メッセージのタイプを取得する。 タイプには、CMSG_SIGNEDとCMSG_ENVELOPEDがある。
CMSG_INNER_CONTENT_TYPE_PARAM メッセージのタイプを表すOIDを取得する。 このOIDは、実際にメッセージ上に格納されている。
CMSG_SIGNER_AUTH_ATTR_PARAM 認証済み属性を取得する。
CMSG_SIGNER_UNAUTH_ATTR_PARAM 認証されていない属性を取得する。

メッセージのハンドルは、不要になったらCryptMsgCloseでクローズすることになります。

BOOL WINAPI CryptMsgClose(
  HCRYPTMSG hCryptMsg
);

hCryptMsgは、クローズしたいメッセージのハンドルを指定します。

今回のプログラムは、PKCS #7形式のデータから証明書を取得します。 PKCS #7形式のデータは、以前の節で作成したsign.datや、 証明書をPKCS #7形式でエクスポートした.p7bファイルが当てはまります。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE         hFile;
	DWORD          dwReadByte;
	LPBYTE         lpData;
	DWORD          dwSize;
	LPBYTE         lpSignedBlob;
	DWORD          dwSignedBlobSize;
	BOOL           bResult;
	HCRYPTMSG      hMsg;
	DWORD          dwCertCount;
	DWORD          i;
	PCCERT_CONTEXT pContext;
	TCHAR          szBuf[256];

	hFile = CreateFile(TEXT("sign.dat"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	dwSignedBlobSize = GetFileSize(hFile, NULL);
	lpSignedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedBlobSize);
	ReadFile(hFile, lpSignedBlob, dwSignedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);	

	hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL);
	if (hMsg == NULL) {
		HeapFree(GetProcessHeap(), 0, lpSignedBlob);
		return 0;
	}
	
	bResult = CryptMsgUpdate(hMsg, lpSignedBlob, dwSignedBlobSize, TRUE);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpSignedBlob);
		CryptMsgClose(hMsg);
		return 0;
	}

	dwSize = sizeof(DWORD);
	CryptMsgGetParam(hMsg, CMSG_CERT_COUNT_PARAM, 0, &dwCertCount, &dwSize);
	
	for (i = 0; i < dwCertCount; i++) {
		CryptMsgGetParam(hMsg, CMSG_CERT_PARAM, i, NULL, &dwSize);
		lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
		CryptMsgGetParam(hMsg, CMSG_CERT_PARAM, i, lpData, &dwSize);

		pContext = CertCreateCertificateContext(X509_ASN_ENCODING, lpData, dwSize);
		CertNameToStr(X509_ASN_ENCODING, &pContext->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, szBuf, sizeof(szBuf));
		CertFreeCertificateContext(pContext);

		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	HeapFree(GetProcessHeap(), 0, lpSignedBlob);
	CryptMsgClose(hMsg);

	return 0;
}

ファイルから取得したPKCS #7形式のデータをデコードするために、 CryptMsgOpenToDecodeを呼び出してメッセージのハンドルを取得します。 次に、CryptMsgUpdateでメッセージにPKCS #7形式のデータを指定すれば、 メッセージのコンテンツはデコードされたデータとなります。 証明書を取得するコードは、次のようになっています。

dwSize = sizeof(DWORD);
CryptMsgGetParam(hMsg, CMSG_CERT_COUNT_PARAM, 0, &dwCertCount, &dwSize);

for (i = 0; i < dwCertCount; i++) {
	CryptMsgGetParam(hMsg, CMSG_CERT_PARAM, i, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptMsgGetParam(hMsg, CMSG_CERT_PARAM, i, lpData, &dwSize);

	pContext = CertCreateCertificateContext(X509_ASN_ENCODING, lpData, dwSize);
	CertNameToStr(X509_ASN_ENCODING, &pContext->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, szBuf, sizeof(szBuf));
	CertFreeCertificateContext(pContext);

	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
}

証明書の数は、CryptMsgGetParamにCMSG_CERT_COUNT_PARAMを指定することで取得できます。 数値が返るため、第4引数に指定するバッファはDWORD型で十分です。 初期化されたdwCertCountの数だけループを回し、 現在のカウントをインデックスとしてCryptMsgGetParamを呼び出します。 このとき、パラメータの値はCMSG_CERT_PARAMであり、 第4引数には証明書のバイトデータが返ります。 このサイズは事前に予測できないため、1回目の呼び出しではサイズを取得することに専念し、 そのサイズ分のメモリを確保してから2回目の呼び出しでデータを取得します。 取得したデータの使い道は、たとえばファイルに保存すれば、.cerファイルになりますから、 PKCS #7形式のデータから証明書を取り出すコードを書くことができるでしょう。 今回は、CertCreateCertificateContextで証明コンテキストを作成し、 証明書の発行先を表示しています。

Simplified Message Functionsの代用

多くの場合、Low-Level Message Functionsで行えることは、 Simplified Message Functionsでも可能であり、コードも少なくなる傾向があります。 たとえば、今回のPKCS #7形式のデータから証明書を取得するというコードは、 次のように記述することができます。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE         hFile;
	DWORD          dwReadByte;
	LPBYTE         lpSignedBlob;
	DWORD          dwSignedBlobSize;
	HCERTSTORE     hStore;
	PCCERT_CONTEXT pContext = NULL;
	TCHAR          szBuf[256];

	hFile = CreateFile(TEXT("sign.dat"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	dwSignedBlobSize = GetFileSize(hFile, NULL);
	lpSignedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedBlobSize);
	ReadFile(hFile, lpSignedBlob, dwSignedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);	

	hStore = CryptGetMessageCertificates(PKCS_7_ASN_ENCODING, 0, CERT_STORE_READONLY_FLAG, lpSignedBlob, dwSignedBlobSize);
	for (;;) {
		pContext = CertEnumCertificatesInStore(hStore, pContext);
		if (pContext == NULL)
			break;
	
		CertNameToStr(X509_ASN_ENCODING, &pContext->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, szBuf, sizeof(szBuf));

		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	HeapFree(GetProcessHeap(), 0, lpSignedBlob);

	return 0;
}

Simplified Message FunctionsのCryptGetMessageCertificatesは、 指定したPKCS#7形式のデータを証明書ストアと見立てることを可能とします。 これにより、アプリケーションはCertEnumCertificatesInStoreなどを呼び出すことができ、 証明書を取得を慣れ親しんだ方法で行えることになります。 ちなみに、CryptGetMessageCertificatesは内部でCertOpenStoreを呼び出し、 その第1引数はCERT_STORE_PROV_PKCS7を指定しています。



戻る